PHPの画像処理は苦手とする方が多いみたいで、リクエストをいただいたので、解説していきます!
他にもリクエストがあったら是非お願いします!
期待にお応えできるかはわからないですが笑
フロントエンド側の処理は簡単なので省略します笑
一応補足しておくと
post
送信でenctype='multipart/form-data'
これを指定するだけです!
<?php session_start(); ?>
<!DOCTYPE html>
<html>
<head>
<meta charset='UTF-8'>
<title>ユーザー情報編集</title>
</head>
<body>
<h1>ユーザー情報編集</h1>
<form method='post' enctype='multipart/form-data' action='upload.php'>
<input type='hidden' name='member_id' value='<?= $_GET['member_id']; ?>'>
<p>アップロード画像</p>
<input type='file' name='photo'>
<input type='submit' name='upload' value='アップロード'>
</form>
<form method='post' action='user_detail.php'>
<p>一言メッセージ<p>
<input type='hidden' name='member_id' value='<?= $_GET['member_id']; ?>'>
<input type='text' name='message' value='<?php if ($_GET['message'] != '未入力') echo $_GET['message']; ?>'>
<input type='submit' name='submit' value='送信'>
</form>
<?php endif; ?>
</body>
</html>
<?php
session_start();
if (isset($_POST['upload']) && isset($_FILES['photo']) && isset($_SESSION['id']) && isset($_POST['member_id']) && $_SESSION['id'] == $_POST['member_id']) {
if (empty($_FILES['photo']['name'])) {
$msg = 'ファイルを入れてください';
} else {
$photo = Date('Ymdhis');
$photo .= uniqid(mt_rand(), true);
$tempfile = $_FILES['photo']['tmp_name'];
switch (@exif_imagetype($tempfile)) {
case 1:
$photo .= '.gif';
break;
case 2:
$photo .= '.jpg';
break;
case 3:
$photo .= '.png';
break;
default:
header('Location: upload.php');
exit();
}
$filemove = './photo/' . $photo;
try {
$dbh = new PDO('mysql:host=localhost; dbname=dbname;', 'username', 'password');
} catch (PDOException $e) {
echo '接続失敗:' . $e->getMesssage();
exit();
}
$upload = 'UPDATE members SET photo = :photo WHERE id = :id';
$stmt = $dbh->prepare($upload);
$params = array(
':photo' => $photo,
':id' => $_SESSION['id'],
);
if (move_uploaded_file($tempfile, $filemove)) {
$msg = '画像アップロード完了';
$stmt->execute($params);
} else {
$msg = '画像ファイルをアップロードできませんでした';
}
}
} else {
$msg = '画像ファイルを入れてください';
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset='UTF-8'>
<title>画像アップロード</title>
</head>
<body>
<?php echo $msg; ?>
<p><a href='board.php'>ホームに戻る</a></p>
</body>
</html>
処理の流れはざっとこんな感じ
- アップロード写真が存在するかどうか
- 存在した場合は拡張子のチェックをする
- ユニークな名前に変更して、
- ファイル名をDBに保存し、
- ファイルを指定のディレクトリに保存する
こんな感じ!
まず最初から作るときはバリデーションはガン無視でいいです。
とりあえず最低限動くように作ってデバッグしながら追加していくのがおすすめー
自分でテストするときは悪意のある操作はしないのでね笑
では一つずつ解説していきましょう
まずアップロードされたファイルは$_FILES
というグローバル変数に格納されて、サーバーに仮アップロードされます上で一時的に保存される
連想配列の中身 | 解説 |
---|---|
name | inputのname属性 |
type | ファイル形式が保存されるが、ユーザー任意のMINEタイプになるので、多分$_SERVER と同じ感じで信用できない |
tmp_name | サーバーへ仮アップロードされたディレクトリとファイル名 |
error | エラー情報 |
size | ファイルサイズ(単位はバイト) |
まず拡張子を確認するために、$_FILES['input属性']['tmp_name'] を変数に格納して |
|
exif_imagetype を使って拡張子を判断していきます |
|
exif_imagetype が返す値は |
値 | 定数 |
---|---|
1 | IMAGETYPE_GIF |
2 | IMAGETYPE_JPEG |
3 | IMAGETYPE_PNG |
4 | IMAGETYPE_SWF |
5 | IMAGETYPE_PSD |
6 | IMAGETYPE_BMP |
7 | IMAGETYPE_TIFF_II (intel byte order) |
8 | IMAGETYPE_TIFF_MM (motorola byte order) |
9 | IMAGETYPE_JPC |
10 | IMAGETYPE_JP2 |
11 | IMAGETYPE_JPX |
12 | IMAGETYPE_JB2 |
13 | IMAGETYPE_SWC |
14 | IMAGETYPE_IFF |
15 | IMAGETYPE_WBMP |
16 | IMAGETYPE_XBM |
17 | IMAGETYPE_ICO |
18 | IMAGETYPE_WEBP |
以上のような値を返すので、それをswitch
文で判別しているだけです
switch (@exif_imagetype($tempfile)) {
case 1:
$photo .= '.gif';
break;
case 2:
$photo .= '.jpg';
break;
case 3:
$photo .= '.png';
break;
default:
header('Location: upload.php');
exit();
}
これは以下のようにも書き換えられます
switch (@exif_imagetype($tempfile)) {
case IMAGETYPE_GIF:
$photo .= '.gif';
break;
case IMAGETYPE_JPEG:
$photo .= '.jpg';
break;
case IMAGETYPE_PNG:
$photo .= '.png';
break;
default:
header('Location: upload.php');
exit();
}
このように.=
をつけると文字列の末尾に文字列を追加することができるので結果として拡張子をアップデートすることができますねー
拡張子がこの3タイプ以外の場合はリダイレクトされるようになってます
ここで@
を付けているのはテキストファイルの拡張子を.jpg
にするとWarning級のエラー
が発生するのでそれを表示させないためです
エラー制御演算子@
で画像の形式を判別するために必要なだけのバイト数を読み込めない場合にエラーを発生するのを抑制してます
この前にファイルサイズでバリデーションをかけるのもいいと思います
やろうと思えばどこまででもできてしまうのでもっと詳しく知りたい方はこちらも参考にしてみてください
https://qiita.com/mpyw/items/939964377766a54d4682
拡張子の判断が済んだら、DBにファイル名を保存していきましょう
DBに保存する際には
ファイル名を保存する方法
と、
ファイルをそのままDBに保存する方法
があります。
ファイルをそのままDBに保存する方法をとってしまうと重たくなってしまうので、ファイル名をDBに保存する方法をとります
詳しくは以下の記事を参考にしてください
https://qiita.com/7coco/items/1706a561131363d2c418
DBの接続は負荷がかかるため最低限にしたいのでここで接続しています
PDO
には例外が発生するので、try, catch
を使ってエラーハンドリングしています
ここら辺は以下の記事を参考にするといいと思います
https://qiita.com/mpyw/items/b00b72c5c95aac573b71
https://qiita.com/7968/items/6f089fec8dde676abb5b
ユーザーが入力した情報はエスケープする必要があるので、プリペアドステートメント
を使ってください
注意して欲しいのはexecute()
しているのはファイルを移動したのが確認できてからですかね。
move_uploaded_file()
を使って、一時的に保存されたファイルを自分が保存したいディレクトリに移します
ユーザー任意のファイル名を使用するとバグを生む可能性があるのでuniqid(mt_rand(), true)
を使ってランダムな文字列でファイル名を作成します
date()
関数を使っているのはファイル名の重複を防止するためとデバッグ時の判別をしやすくするためです。
これには以前解説したことのあるmd5(uniqid(mt_rand(), true))
を使ってもいいし、user_id
をファイル名にすることもあります
そちらの解説はこちらから
https://qiita.com/satorunooshie/items/de494a25e7da4da16dcf
書き忘れていましたが移動先のディレクトリの権限を変更しておいてください!
chmod 755 ./photo
詳しくは以下のサイトを参考にしてください
https://qiita.com/shisama/items/5f4c4fa768642aad9e06
https://www.php.net/manual/ja/function.chmod.php
では続いて複数ファイルをアップロードする方法についてみていきましょう
今回は少し違うアプローチで崩していきましょう
<?php
$dir = __DIR__ . './photo';
const MAX_SIZE = 102400;
//php.iniのpost_max_sizeを超えたデータが送信されていないか確認
if (!checkPostMaxSize()) echo 'ファイルサイズは100MB以下にしてください';
if (isset($_FILES['uploadfile']) {
for ($i = 0; $i < count($_FILES['uploadfile']['name']); $i++) {
list($result, $ext, $error_msg) = checkFile($i);
if ($result) {
$name = $_FILES['uploadfile']['name'][$i];
$tmp_name = $_FILES['uploadfile']['tmp_name'][$i];
//"upfile_" + 現在のタイムスタンプ + 連番 + "_" + マイクロ秒 + 元のファイル名 + アクセス元IPアドレスに基づくMD5 + 拡張子
$move_to = $dir . '/upfile_' . time() . $i . '_'
. md5(microtime() . $name . $_SERVER['REMOTE_ADDR']) . '.' . $ext;
if (!move_upload_file($tmp_name, $move_to)) {
$error_msg[] = '画像のアップロードに失敗しました';
} else {
echo '<img src="' . $move_to . '>';
}
}
if (count($error_msg) > 0) {
foreach ($error_msg as $msg) {
echo $msg;
}
}
}
}
function checkFile($i)
{
$error_msg = [];
$ext = '';
$size = $_FILES['uploadfile']['size'][$i];
$error = $_FILES['uploadfile']['error'][$i];
$img_type = $_FILES['uploadfile']['type'][$i];
$tmp_name = $_FILES['uploadfile']['tmp_name'][$i];
if ($error != UPLOAD_ERR_OK) {
switch ($error) {
case UPLOAD_ERR_NO_FILE:
//アップロードされなかった場合はエラー処理はなし
break;
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
$error_msg = 'ファイルサイズは100KB以下にしてください';
break;
default:
$error_msg = 'アップロードエラーです';
}
return [false, $ext, $error_msg];
} else {
switch ($img_type) {
case 'image/gif':
$ext = 'gif';
break;
case 'image/jpeg':
case 'image/pjpeg':
$ext = 'jpg';
break;
case 'image/png':
case 'image/x-png':
$ext = 'png';
break;
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
$finfo_type = $finfo->file($tmp_name);
if ($size == 0) {
$error_msg[] = 'ファイルが存在しないか空のファイルです';
} elseif ($size > MAX_SIZE) {
$error_msg[] = 'ファイルサイズは100KB以下にしてください';
} elseif ($img_type != $finfo_type) {
$error_msg[] = 'MIMEタイプが一致しません';
} elseif ($ext != 'gif' && $ext != 'jpg' && $ext != 'png') {
$error_msg[] = 'アップロード可能なファイルはgif, jpg, pngのみです';
} else {
return [true, $ext, $error_msg];
}
}
return [false, $ext, $error_msg];
}
//php.iniのpost_max_sizeを超えたデータが送信されていないかチェックする関数
function checkPostMaxSize()
{
$max_size = ini_get('post_max_size');
//post_max_sizeにMが入っていた場合に整数にする
$unit = substr($max_size, -1);
switch ($unit) {
case 'M':
$multiple = 1024 ** 2; //pow(1024, 2);
break;
case 'K':
$multiple = 1024;
break;
case 'G':
$multiple = 1024 ** 3; //pow(1024, 3);
break;
default:
$multiple = 1;
}
$max_size = substr($max_size, 0, strlen($max_size) - 1) * $multiple;
//post_max_sizeを超えたデータがPOSTされたかをチェック
if ($_SERVER['REQUEST_METHOD'] == 'POST'
&& $_SERVER['CONTENT_LENGTH'] > $max_size) {
return false;
}
return true;
}
こんな感じですかねー
拡張子を判断するにはFileInfo
拡張モジュールを使うこともできます
ファイル内の特定の位置からマジックバイトシーケンスを見つけることでファイルのMIMEタイプを推測しているみたいなので、確実な方法ではないようです。
ただしWindows環境のPHPではデフォルトで拡張モジュールが有効になっていないので、
extension=php_fileinfo.dll #;extension=php_fileinfo.dll
のようにphp.ini
を変更しApache
を再起動してください
補足ですが累乗の計算はpow()
関数を使うことも**
を使うこともできるみたいですー