[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);

[PHP]文字列の内容に応じて自動で色分けする

ユーザー名などの任意の文字列に対して文字色を自動で指定するサンプルを作りました。
同じ文字列に対しては常に同じ色が返ります。色の選択肢はあらかじめ用意し、その中から色が選ばれます。
色の選択肢が少ないと文字が異なっても同じ色が重複することになります。

<?php
$colors = ['#ee0000', '#3a9b00', '#006ecc', '#ff9000', '#000000'];

function stringToColor($str, $colors){
    $index = hexdec( substr(md5($str), 0, 15) ) % count($colors);
    return $colors[$index];
}
?>
<span style="color: <?php echo stringToColor('hello', $colors);?>">hello</span>