[PHP]クラスメソッドをキャメルケース、スネークケースのどちらでも呼び出せるようにする

PHP のクラスメソッドは基本的にキャメルケース(camelCase)で命名されることが多いのですが、プロジェクトによっては単語をアンダーバーで区切るスネークケース(snake_case)が使われることもあります。
どちらにするかは自由ですが、プロジェクト全体で統一するのが望ましいとされています。

自分で作ったクラスをほかのプロジェクトでも再利用する場合、必要に応じて命名記法を書き換える事がありますが、マジックメソッドの __call(), __callStatic() を利用すると存在しない存在しない名前のメソッドが呼び出されようとしたときの処理を定義できるため、キャメルケース、スネークケースのどちらで呼び出されても同じメソッドが実行される仕組みを作ることができます。

<?php
class Example
{
    public function __call($name, $args) {
        $name = str_replace('_', '', ucwords($name, '_'));
	    $name[0] = strtolower($name[0]);
        return call_user_func_array([$this, $name], $args);
    }

    public static function __callStatic($name, $args) {
        $name = str_replace('_', '', ucwords($name, '_'));
	    $name[0] = strtolower($name[0]);
        return call_user_func_array([self::class, $name], $args);
    }

    public function exampleMethod($str) {
        return "hello, " . $str;
    }

    static public function staticMethod($str){
        return "hello, " . $str;
    }
}

メソッドの呼び出し

$example = new Example();

echo $example->exampleMethod("World");
echo $example->example_method("World");

echo Example::staticMethod("World");
echo Example::static_method("World");
実行結果: Hello, World

[PHP]画像ファイルのアップロードとGDを使ったリサイズ

GD を使って画像のリサイズを行う場合は imagecopyresampled() を使うのですが、PNG や GIF などで透過情報を保つ場合は工夫が必要です。
そこでアップロードされた画像を指定したサイズに収まるようにリサイズする関数を作ってみました。

<?php
function uploadImage($tmpName, $dir, $maxWidth, $maxHeight){

    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mime = $finfo->file($tmpName);

    if($mime == 'image/jpeg' || $mime == 'image/pjpeg'){
        $ext = '.jpg';
        $image1 = imagecreatefromjpeg($tmpName);
    } elseif($mime == 'image/png' || $mime == 'image/x-png'){
        $ext = '.png';
        $image1 = imagecreatefrompng($tmpName);
    } elseif($mime == 'image/gif'){
        $ext = '.gif';
        $image1 = imagecreatefromgif($tmpName);
    } else {
        return false;
    }
    
    list($width1, $height1) = getimagesize($tmpName);

    if($width1 <= $maxWidth && $height1 <= $maxHeight){
        $scale = 1.0;
    } else {
        $scale = min($maxWidth / $width1, $maxHeight / $height1);
    }

    $width2 = $width1 * $scale;
    $height2 = $height1 * $scale;

    $image2 = imagecreatetruecolor($width2, $height2);

    if($ext == '.gif'){
        $transparent1 = imagecolortransparent($image1);
        if($transparent1 >= 0){
            $index = imagecolorsforindex($image1, $transparent1);
            $transparent2 = imagecolorallocate($image2, $index['red'], $index['green'], $index['blue']);
            imagefill($image2, 0, 0, $transparent2);
            imagecolortransparent($image2, $transparent2);
        }
    } elseif($ext == '.png'){
        imagealphablending($image2, false);
        $transparent = imagecolorallocatealpha($image2, 0, 0, 0, 127);
        imagefill($image2, 0, 0, $transparent);
        imagesavealpha($image2, true);
    }

    imagecopyresampled($image2, $image1, 0, 0, 0, 0, $width2, $height2, $width1, $height1);

    if(!file_exists($dir)){
        mkdir($dir, 0777, true);
    }

    $filename = sha1(microtime() . $_SERVER['REMOTE_ADDR'] . $tmpName) . $ext;
    $saveTo = rtrim($dir, '/\\') . '/' . $filename;

    if($ext == '.jpg'){
        $quality = 80;
        imagejpeg($image2, $saveTo, $quality);
    } else if($ext == '.png'){
        imagepng($image2, $saveTo);
    } else if($ext == '.gif'){
        imagegif($image2, $saveTo);
    }

    imagedestroy($image1);
    imagedestroy($image2);

    return $saveTo;
}

if($_SERVER["REQUEST_METHOD"] === 'POST'
    && !empty($_FILES['image']['tmp_name']))
{
    $now = new DateTime();

    $maxWidth = 300;    // 最大幅
    $maxHeight = 300;   // 最大高さ

    // 一時ファイルの場所
    $tmpName = $_FILES['image']['tmp_name'];

    // 保存先のディレクトリ
    $dir = __DIR__ . '/files/' . $now->format('Y/m/d');
    $path = uploadImage($tmpName, $dir, $maxWidth, $maxHeight);
    var_dump($path);
    exit;
}
?>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>image</title>
    </head>
    <body>
        <form method="POST" enctype="multipart/form-data">
            <input type="file" name="image">
            <input type="submit" value="submit">
        </form> 
    </body>
</html>

リサイズに成功すると保存先のファイルパスを返します。失敗した場合は false を返します。
拡張子偽装の可能性を考慮して finfo で MIME type を調べるようにしています。

[PHP]エラーの発生箇所を特定する手法

PHP は基本的にエラー発生時にメッセージでどの行に問題があるかを指摘してくれますが、発生しているはずのエラーが画面に表示されない場合や、エラーにはならないものの変数におかしな値が入ってしまう場合や、Cron で動作するものや API のエンドポイントのように画面表示のないものなどは原因を特定しにくくなります。
そこで問題箇所の絞り込み方について幾つかの手法を紹介します。

エラーメッセージを表示する

基本的には php.ini ファイルで「display_errors = On」のようにしておくのが基本なのですが、何らかの事情でそれが出来ない場合は PHP ファイル中の ini_set() を使ってエラーを表示させます。

// エラー表示を有効にする
ini_set( 'display_errors', 1 );

// 全てのエラーを表示する
ini_set('error_reporting', E_ALL);

エラーメッセージをファイルに保存する

ブラウザ上でメッセージを確認できれば良いのですが、低確率で発生する場合や自動実行される Cron ジョブなどのスクリプトの場合エラーログの保存先を変更して好きな場所に保存するという手があります。

ini_set('display_errors', 1);
ini_set('error_reporting', E_ALL);
ini_set('log_errors', 'On');

// ログの保存先
ini_set('error_log', __DIR__ . '/error.log');

変数の内容を確認する

var_dump() 関数を使うと変数に何が入っているか詳しく確認できます。null や空文字、 0 や false などもしっかりと区別されるので特定しやすくなります。

$foo = false;
var_dump($foo);

配列変数の内容をファイルに保存する

print_r() の第2引数に true を指定すると画面出力の代わりに結果を文字列として返す事ができます。
それを活用すると配列変数などの内容をファイルに保存することが出来ます。

file_put_contents("log.txt", print_r($_POST, true));

無関係の箇所をコメントアウトする

原始的ではありますが、「//」や「<!– –>」を使って無関係に思える箇所をコメントアウトしていくと思わぬ箇所に潜む問題が見つかるかもしれません。括弧の閉じ忘れ等による unexpected end of file などの構文エラーの発生箇所特定にも有効です。

PHPのバージョンやモジュールがインストールされているか確認する

phpinfo(); を使うと PHP のバージョンやエクステンション(拡張機能)の一覧が出力されます。
PHP 本体が古すぎたり、ImageMagick などの必要な拡張モジュールがインストールされていないと正しく動作しないことがあります。ほかにもタイムゾーンが正しく設定されているかなども調べることが出来ます。

文字コード、改行コードを確かめる

基本的に PHP ファイルは UTF-8 の文字コードで保存することが推奨されています。保存の際に BOM(Byte Order Mark)が含まれていてはいけません。改行コードも Unix 系のサーバーであれば LF に統一すべきです。テキストエディタの設定を確かめておきましょう。

メモリ上限を増やしたりタイムアウト時間を伸ばす

プログラムは正しく書けていてもサーバー側でメモリ上限や実行時間に制限がかけられている場合があります。
サーバーによっては禁止されている場合もありますが、変更できる場合は次のようにします。

// メモリの上限
ini_set('memory_limit', '256M');

// POST によって扱えるサイズの上限
ini_set('post_max_size', '16M');

// アップロードできるファイルサイズの上限
ini_set('upload_max_filesize', '8M');

// 同時にアップロードできるファイルの最大数
ini_set('max_file_uploads', 20);

// 実行時間の最大値(秒)
set_time_limit(90);