前回の記事の続きです。
前回は、
- アップロードする画像を選択したら、その場でプレビュー
- ドラッグ&ドロップで画像の位置を入れ替える
- 最終的な画像の位置情報と、画像を一緒に送信
までやりました。全てJavaScriptでの処理でしたが、今回はPHPのみの処理です。送信された位置情報を使って、アップロードされた画像をプレビューと同じ並びで表示します。
HTMLの構造が同じなので、前回作成したCSSファイルphotos_view.css
を読み込みます(前回の、「作成するファイル」のリンクからご覧ください)。
作成するファイル
今回やること
(1)位置情報photoOrderをJSON形式からデコード&画像の枚数を取得
(2)エラーチェック
(3)エラーが無ければ並び替えて画像を表示
<?php
//*********************************(1)*******************************
$photo_order = json_decode($_POST['photo-order'], true);
$count_files = count($_FILES['photo']['name']);
//*********************************************************************
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="./css/photos_view.css"/>
</head>
<body>
<div id="photos-view">
<div id="all-photos" class="layout-<?=$count_files;?>">
<?php
//*******************************(2)***************************************
for($i = 0; $i < $count_files; $i++){
if($_FILES['photo']['error'][$i] !== UPLOAD_ERR_OK){
$msg = [
UPLOAD_ERR_INI_SIZE => 'php.iniのupload_max_filesizeを超えています。',
UPLOAD_ERR_FORM_SIZE => 'HTMLのmax_file_sizeを超えています。',
UPLOAD_ERR_PARTIAL => 'ファイルが一部分しかアップロードされていません。',
UPLOAD_ERR_NO_FILE => 'ファイルが存在しません。',
UPLOAD_ERR_NO_TMP_DIR => '一時保存のためのフォルダが存在しません。',
UPLOAD_ERR_CANT_WRITE => 'ディスクへの書き込みに失敗しました。',
UPLOAD_ERR_EXTENSION => '拡張モジュールによってアップロードが中断されました。',
];
$error_msg = $msg[$_FILES['photo']['error'][$i]];
die('<div>'.$error_msg.'</div>');
}else if(!in_array(strtolower(pathinfo($_FILES['photo']['name'][$i])['extension']), ['gif', 'jpg', 'jpeg', 'png'])){
$error_msg = '拡張子が画像ではありません。';
die('<div>'.$error_msg.'</div>');
}else if(!in_array(finfo_file(finfo_open(FILEINFO_MIME_TYPE), $_FILES['photo']['tmp_name'][$i]),['image/gif', 'image/jpg', 'image/jpeg', 'image/png'])){
$error_msg = 'コンテンツタイプが画像ではありません。';
die('<div>'.$error_msg.'</div>');
}else{
//*******************************(3)**********************************
$fp = fopen($_FILES['photo']['tmp_name'][$photo_order[$i]], 'rb');
$get_photo = fread($fp, filesize($_FILES['photo']['tmp_name'][$photo_order[$i]]));
fclose($fp);
$encode_photo = base64_encode($get_photo);
$photo_info = getimagesize('data:application/octet-stream;base64,' . $encode_photo);
print '<div><img src="data:' . $photo_info['mime'] .';base64,' . $encode_photo . '"></div>';
}
}
?>
</div>
</div>
</body>
</html>
アップロードされたファイルはどこにある?
- アップロードされたファイルの情報は
$_FILES['photo']
にある。 -
['photo']
とは、<input type="file">に指定したname属性の値。 - 各項目ごとに「ファイル名の昇順」で格納される(ブラウザによって違う?)。

-
$_FILES['photo']['type']
・アップロードしたファイルのコンテンツタイプ -
$_FILES['photo']['tmp_name']
・アップロードしたファイルは、「サーバ上の一時的なファイル」に「仮保存」される。その一時ファイル名。つまり、アップロードしたファイルを取得するにはこの一時ファイルにアクセスする。例えば、
-
move_uploaded_file
関数を使って、一時ファイルから本来の保存先へファイルを移動。
保存先はドキュメントルートの外にする。不正なファイルがドキュメントルート下にアップロードされるとそのまま実行されてしまうから。 - 一時ファイルを
fopen
関数で開いて、アップロードしたファイルの内容をfread
関数で読み取る。【今回はこれによって画像を表示します】。
- 一時ファイルは、
move_uploaded_file
関数で処理されなかった場合、スクリプトの処理後に自動的に破棄される。
(1)位置情報photoOrderをJSON形式からデコード&画像の枚数を取得
<?php
$photo_order = json_decode($_POST['photo-order'], true);
$count_files = count($_FILES['photo']['name']);
?>
(2)エラーチェック
画像を1枚ずつ、3つのエラーチェックを行います。
①アップロードに関するエラーチェック
②拡張子をチェック
③コンテンツタイプをチェック(②より信頼性あり)
①アップロードに関するエラーチェック
アップロードされたファイルの情報は$_FILES['photo']
にあります。各項目ごとに「ファイル名の昇順」で格納されています(ブラウザによって違う?)。

つまり、for文を使って「画像を1枚ずつチェック」することができます。例えば$_FILES['photo']['name'][$i]
とすると、ファイル名を1つずつ取り出します。「$i
は各ファイル」です。
エラーチェックがしたいので、$_FILES['photo']['error']
を見ます。
[error] => Array ( [0] => 0 [1] => 0 [2] => 0 [3] => 0 )
$_FILES['photo']['error']
には、0~8(5は無い)の数字が格納されます。PHPに元から定義されている定数の値と一致します。これを使ってアップロード時のエラーチェックができます。
定数 | 値 | 概要 |
---|---|---|
UPLOAD_ERR_OK | 0 | アップロード成功 |
UPLOAD_ERR_INI_SIZE | 1 | php.iniのupload_max_filesizeで指定サイズを超えた |
UPLOAD_ERR_FORM_SIZE | 2 | HTMLのmax_file_sizeで指定したサイズを超えた |
UPLOAD_ERR_PARTIAL | 3 | ファイルが途中で切れている |
UPLOAD_ERR_NO_FILE | 4 | ファイルが存在しない |
UPLOAD_ERR_NO_TMP_DIR | 6 | 一時保存のためのフォルダーが存在しない |
UPLOAD_ERR_CANT_WRITE | 7 | ディスクへの書き込みに失敗した |
UPLOAD_ERR_EXTENSION | 8 | 拡張モジュールによってアップロードが中断された |
for($i = 0; $i < $count_files; $i++){
if($_FILES['photo']['error'][$i] !== UPLOAD_ERR_OK){
$msg = [
UPLOAD_ERR_INI_SIZE => 'php.iniのupload_max_filesizeを超えています。',
UPLOAD_ERR_FORM_SIZE => 'HTMLのmax_file_sizeを超えています。',
UPLOAD_ERR_PARTIAL => 'ファイルが一部分しかアップロードされていません。',
UPLOAD_ERR_NO_FILE => 'ファイルが存在しません。',
UPLOAD_ERR_NO_TMP_DIR => '一時保存のためのフォルダが存在しません。',
UPLOAD_ERR_CANT_WRITE => 'ディスクへの書き込みに失敗しました。',
UPLOAD_ERR_EXTENSION => '拡張モジュールによってアップロードが中断されました。',
];
$error_msg = $msg[$_FILES['photo']['error'][$i]];
die('<div>'.$error_msg.'</div>');
}
}
②拡張子をチェック
- 配列に、許可する拡張子のリストを用意する。
['gif', 'jpg', 'jpeg', 'png']
- pathinfo関数は、ファイルパスに関する様々な情報を連想配列で取得する。その中にある拡張子
['extension']
を取りだす。取り出した拡張子をstrtolower関数で小文字に変換。 - 拡張子が配列のリストの中になければエラー。
...}else if(!in_array(strtolower(pathinfo($_FILES['photo']['name'][$i])['extension']), ['gif', 'jpg', 'jpeg', 'png'])){
$error_msg = '拡張子が画像ではありません。';
die('<div>'.$error_msg.'</div>');
}
③コンテンツタイプをチェック(②より信頼性あり)
- 配列に、許可するコンテンツタイプのリストを用意する。
['image/gif', 'image/jpg', 'image/jpeg', 'image/png']
- finfo_file関数は、ファイルの内容を解析し、コンテンツタイプを取り出す。引数に「FileInfoインスタンス(finfo_open関数で生成)」と「ファイル名」を渡す。
- コンテンツタイプが配列のリストの中になければエラー。
else if(!in_array(finfo_file(finfo_open(FILEINFO_MIME_TYPE), $_FILES['photo']['tmp_name'][$i]),['image/gif', 'image/jpg', 'image/jpeg', 'image/png'])){
$error_msg = 'コンテンツタイプが画像ではありません。';
die('<div>'.$error_msg.'</div>');
}
-
FILEINFO_MIME_TYPE
はmimeタイプを返す。finfo_open(FILEINFO_MIME_TYPE)
はコンテンツタイプを取得することを意味する。 -
$_FILES['photo']['tmp_name']
は、アップロードされたファイルが仮保存されている一時ファイル名。
$_FILES['photo']['type']
でもアップロードしたファイルのコンテンツタイプを取得できる。しかし、悪意のあるユーザーにとって、typeキーの内容を偽装するのは簡単。予備的なものにとどめるべき。
(3)エラーが無ければ並び替えて画像を表示
プレビューと同じ並びで表示します。プレビュー時の画像の位置情報である、配列photoOrderの順番に表示処理をします。今まで$i
を使って処理していましたが、photoOrder[$i]
にします。

}else{
$fp = fopen($_FILES['photo']['tmp_name'][$photo_order[$i]], 'rb');
$get_photo = fread($fp, filesize($_FILES['photo']['tmp_name'][$photo_order[$i]]));
fclose($fp);
$encode_photo = base64_encode($get_photo);
$photo_info = getimagesize('data:application/octet-stream;base64,' . $encode_photo);
print '<div><img src="data:' . $photo_info['mime'] . ';base64,' . $encode_photo . '"></div>';
}
2つに分けて説明します。
$fp = fopen($_FILES['photo']['tmp_name'][$photo_order[$i]], 'rb');
$get_photo = fread($fp, filesize($_FILES['photo']['tmp_name'][$photo_order[$i]]));
fclose($fp);
-
fopen関数・・・ノートの表紙を開くイメージ。
・ファイルやURLを開き、「ファイルポインタリソース(ファイルハンドル)」を返す。
・ファイルハンドルとは、「ファイルを操作するためのキーとなる情報」。ファイルに対する読み書きはファイルハンドルに対して行う。
・引数に「開きたいファイル名」と「開くモード」を指定。
・このコードでは、$_FILES['photo']['tmp_name']
(アップロードされたファイルが仮保存されている一時ファイル)を、rb
モードで開く。 -
rb
モード
r
は読み込み専用。他にも種類あり。モードの後ろにt
(テキストモード)かb
(バイナリモード)を指定する。省略するとb
になる。t
はファイル内の改行を、OSに合ったものに変換する。b
は何もしない。t
に頼らず正しい改行コードを書き、b
を使うのが強く推奨。 -
fread関数
・「ファイルポインタリソース(ファイルハンドル)」から最高○バイトを読み込み、読み込んだ文字列を返す。引数に「ファイルポインタリソース」と「最大バイト」を指定。最大バイトは、filesize
関数で取得したファイルサイズ。つまり、ファイル全てを読み込む。 -
fclose
関数・・・開いていたノートを閉じるイメージ
・オープンしたファイルポインタをクローズする
$get_photo = fread($fp, filesize($_FILES['photo']['tmp_name'][$photo_order[$i]]));
//........省略
$encode_photo = base64_encode($get_photo);
$photo_info = getimagesize('data:application/octet-stream;base64,' . $encode_photo);
print '<div><img src="data:' . $photo_info['mime'] . ';base64,' . $encode_photo . '"></div>';
-
base64_encode
関数・・・指定した文字列をbase64でエンコードして返す。 -
getimagesize関数・・・画像の大きさを取得
・引数に「情報を取得したいファイル(名)」を指定。ここでは「データURL」で指定してる。
getimagesize関数の結果(例)
Array ( [0] => 1200 ...幅
[1] => 800 ...高さ
[2] => 2 ...画像の形式。PHPに定義されている定数の値
[3] => width="1200" height="800", ...<img>で使える
[bits] => 8 ...色のbit
[channels] => 3 ...RGB画像は3、CMYK画像は4
[mime] => image/jpeg ) ...(★)画像のMIMEタイプ
・<img>のsrc属性の値も「データURL」。データURLを表現するのに(★)画像のMIMEタイプを使う。
-
データURL(
'data:application/octet-stream;base64, . $encode_photo'
)
・「data:」がついたURL。小さなファイルをインラインで文書に埋め込むことができる。「data:MIMEタイプ;base64,データ本体」で表す。 -
MIMEタイプ
「タイプ/サブタイプ」。text/plain
、text/html
など。
タイプ
・text
・・・文字データ
・image
・・・画像
・video
・・・動画
・audio
・・・音声
・application
・・・上記以外。(例)圧縮ファイルは「application/zip」
サブタイプ
・application
のサブタイプで、「IANAメディアタイプ」に登録されていない形式のものはoctet-stream
とする。IANAメディアタイプとは、MIMEタイプの標準規格。
・つまりapplication/octet-stream
は、任意のバイナリファイルや、ファイル形式が不明な時に使う。 -
;base64
・テキストデータ以外の時に書く。続く「,」下のデータ本体には、base64エンコードしたバイナリデータを指定する。
upload_process.php【完成】
<?php
$photo_order = json_decode($_POST['photo-order'], true);
$count_files = count($_FILES['photo']['name']);
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="./css/photos_view.css"/>
</head>
<body>
<div id="photos-view">
<div id="all-photos" class="layout-<?=$count_files;?>">
<?php
for($i = 0; $i < $count_files; $i++){
if($_FILES['photo']['error'][$i] !== UPLOAD_ERR_OK){
$msg = [
UPLOAD_ERR_INI_SIZE => 'php.iniのupload_max_filesizeを超えています。',
UPLOAD_ERR_FORM_SIZE => 'HTMLのmax_file_sizeを超えています。',
UPLOAD_ERR_PARTIAL => 'ファイルが一部分しかアップロードされていません。',
UPLOAD_ERR_NO_FILE => 'ファイルが存在しません。',
UPLOAD_ERR_NO_TMP_DIR => '一時保存のためのフォルダが存在しません。',
UPLOAD_ERR_CANT_WRITE => 'ディスクへの書き込みに失敗しました。',
UPLOAD_ERR_EXTENSION => '拡張モジュールによってアップロードが中断されました。',
];
$error_msg = $msg[$_FILES['photo']['error'][$i]];
die('<div>'.$error_msg.'</div>');
}else if(!in_array(strtolower(pathinfo($_FILES['photo']['name'][$i])['extension']), ['gif', 'jpg', 'jpeg', 'png'])){
$error_msg = '拡張子が画像ではありません。';
die('<div>'.$error_msg.'</div>');
}else if(!in_array(finfo_file(finfo_open(FILEINFO_MIME_TYPE), $_FILES['photo']['tmp_name'][$i]),['image/gif', 'image/jpg', 'image/jpeg', 'image/png'])){
$error_msg = 'コンテンツタイプが画像ではありません。';
die('<div>'.$error_msg.'</div>');
}else{
$fp = fopen($_FILES['photo']['tmp_name'][$photo_order[$i]], 'rb');
$get_photo = fread($fp, filesize($_FILES['photo']['tmp_name'][$photo_order[$i]]));
fclose($fp);
$encode_photo = base64_encode($get_photo);
$photo_info = getimagesize('data:application/octet-stream;base64,' . $encode_photo);
print '<div><img src="data:' . $photo_info['mime'] .';base64,' . $encode_photo . '"></div>';
}
}
?>
</div>
</div>
</body>
</html>
【参考】
・『独習PHP 第4版』 山田祥寛著
・PHPでの画像の保存・表示方法まとめ