これまでの実装
今回はPHPのみです。送信された位置情報・配列photoOrderを使って、プレビューと同じ並びで画像を表示します。
目次
ファイル完成
upload.php【完成】
ポイント
-
$_FILES['photo'](送信した全ファイルの全情報)を使う
['photo']とは、<input type="file">に指定したname属性の値
今回やること
- 送信された
photoOrder = [1, 3, 2, 0];を受け取る(並び順は例です) - 配列
photoOrderの、インデックス0から順番に画像を指定
1つずつ 「3つのエラーチェック」 を行い、表示していく
・問題なし : 画像を表示
・問題あり : エラーメッセージを表示
この図では、$_FILES['photo']['name'][$photo_order[$i]]をしています($iはループのカウンタ変数で、0から)。
つまり、b.jpg、d.jpg、c.jpg、a.jpgの順に、処理(3つのエラーチェックと表示)が行われます。こうして、プレビューと同じ並びで画像を表示しています。
$_FILES['photo']について詳しく見ていきます。
$_FILES['photo']
先ほどの図で、$_FILES['photo']['name']を出しましたが、他にも様々な項目(赤字部分)があります。

- 項目の内容は、
nameのようにファイル名の昇順で格納 - しかし、項目の内容を取得するのは
[$photo_order[$i]]の順
(b.jpg、d.jpg、c.jpg、a.jpg)。取得したらその場で処理に使う
(例)カウンタ変数$i = 0の場合。[$photo_order[0]] = 1
$_FILES['photo']['name'][1]
$_FILES['photo']['full_path'][1]
$_FILES['photo']['type'][1]
………
これらは、全てb.jpgにまつわる情報です。
['項目'] の部分を変えることで、その画像にまつわる様々な情報を得ることができます。必要な情報を得たらその場で処理(3つのエラーチェックと表示)に使います。なので、結果的に処理と表示がb.jpg、d.jpg、c.jpg、a.jpgの順となります。
完成図は以下です。エラーチェックで「問題なし」と「問題あり」の時、表示はこうなります(例 : 画像4枚)。
完成図
実装していきます。
(7)PHPファイル作成
HTMLと、PHPのコードが2か所(★1)(★2)です。
<?php
$photo_order = json_decode($_POST['photo-order'], true); // (★1)
$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/preview.css"/>
</head>
<body>
<div id="preview">
<div id="all-photos" class="layout-<?=$count_files;?>"><!-- (★2) -->
<?php
for($i = 0; $i < $count_files; $i++){ // 画像の枚数分
// 3つのエラーチェック
//「<div><img>」または「<div><p>」を生成
}
?>
</div>
</div>
</body>
</html>
(★1)
- POST送信した配列
photoOrderはJSON形式。デコードして取得 - 画像の枚数を取得
(★2)<div id="all-photos">について
-
ここに全ての表示が入る。各画像に「3つのエラーチェック」を行い、
・問題なし :<div><img>を生成(画像を表示)
・問題あり :<div><p>を生成(エラーメッセージを表示) -
プレビューの並び順で表示。上記処理に必要な、各画像の情報は
[$photo_order[$i]]の順に取得(b.jpg、d.jpg、c.jpg、a.jpg)。取得してその場で処理に使う -
CSSで表示を整える。画像の枚数に応じた、「
layout-枚数」というクラス属性を付与
(例)画像4枚→layout-4。preview.cssは、こちらと同じものを読み込む
(8)3つのエラーチェック
①アップロード時
②拡張子
③コンテンツタイプ(②より信頼性あり)
【出典】『独習PHP 第4版』 山田 祥寛(著)
こちらの書籍を参考にさせていただきました。実装に合わせて一部変更をしております。
①アップロード時
-
$_FILES['photo']['error']
アップロード時に発生したエラーのコード(p.399)
さきほどの完成図では、エラーなし / ありの場合の表示を見てもらいました。このエラーは、ここで発生しています。
print_r($_FILES['photo']['error']);
Array ( [0] => 0 [1] => 0 [2] => 2 [3] => 2 )
↑ ↑ ↑ ↑
// a.jpg b.jpg c.jpg、 d.jpg
- 0~8(5は除く)の数字が入る(以下の表)
項目の内容は、ファイル名の昇順に並んでいるのでした。これを、[$photo_order[$i]]の順(b.jpg、d.jpg、c.jpg、a.jpg)にチェックします。つまり、エラーコードは「0、2、2、0」。2は、エラーメッセージを表示します。CSSにより、最終的にこのような表示となります。
| 定数 | 値 | 概要 |
|---|---|---|
| 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文に、以下の処理を追加します。
- 0(アップロード成功)の場合、次のエラーチェックに進む
- 0以外の場合、
<div><p>を生成し、エラーメッセージを表示して終了
<?php
$photo_order = json_decode($_POST['photo-order'], true);
$count_files = count($_FILES['photo']['name']);
?>
<!-- 省略 -->
<body>
<div id="preview">
<div id="all-photos" class="layout-<?=$count_files;?>">
<?php
for($i = 0; $i < $count_files; $i++){
// ここから追加
if($_FILES['photo']['error'][$photo_order[$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'][$photo_order[$i]]];
print '<div><p>'.$error_msg.'</p></div>';
}else if(){ // 次のエラーチェックへ
// 処理
}
}
?>
</div>
</div>
</body>
</html>
②拡張子
おおまかに、こんな処理です。
- 配列に、許可する拡張子のリストを用意する。
['gif', 'jpg', 'jpeg', 'png'] -
pathinfo関数で、各ファイルの拡張子(例 :
jpg)を取得。strtolower関数で小文字に変換 -
in_array関数を使い、この拡張子が配列のリストの中にあるかチェック
・ある(許可する拡張子)→次のエラーチェックに進む
・なし(許可しない拡張子)→エラーメッセージを表示して終了
}else if(!in_array(strtolower(pathinfo($_FILES['photo']['name'][$photo_order[$i]], PATHINFO_EXTENSION)), ['gif', 'jpg', 'jpeg', 'png'])){
$error_msg = '拡張子が画像ではありません。';
print '<div><p>'.$error_msg.'</p></div>';
}else if(){ // 次のエラーチェックへ
// 処理
}
if文の関数を確認していきます。
pathinfo関数
見やすいように、抜き出します。
pathinfo($_FILES['photo']['name'][$photo_order[$i]], PATHINFO_EXTENSION)
-
pathinfo(
$path, $flags)
$path(ファイル)について4つの情報を返す。「dirname」「basename」「extension」「filename」。$flagsは、この中からほしい情報を定数で指定。ファイルの拡張子「extension」のみ欲しいので、PATHINFO_EXTENSIONを指定
実際に確認していきます。まずは、$path = $_FILES['photo']['name'][$photo_order[$i]]について。
-
$_FILES['photo']['name']
print_r($_FILES['photo']['name']);
Array ( [0] => a.jpg [1] => b.jpg [2] => c.jpg [3] => d.jpg )
項目の内容は、ファイル名の昇順に格納されています。これを、[$photo_order[$i]]の順(b.jpg、d.jpg、c.jpg、a.jpg)に、pathinfo関数で使います。$flagsを省略すると、
for($i = 0; $i < $count_files; $i++){
print_r(pathinfo($_FILES['photo']['name'][$photo_order[$i]]));
}
Array ( [dirname] => . [basename] => b.jpg [extension] => jpg [filename] => b )
Array ( [dirname] => . [basename] => d.jpg [extension] => jpg [filename] => d )
Array ( [dirname] => . [basename] => c.jpg [extension] => jpg [filename] => c )
Array ( [dirname] => . [basename] => a.jpg [extension] => jpg [filename] => a )
各ファイルについて、dirname、basename、extension、filenameの4つの情報が出力されます。$flagsには、この4つの中でほしい情報を定数で指定します。今回の実装では、extension(ファイルの拡張子)が欲しいです。$flagsにPATHINFO_EXTENSIONを指定します。
// 先ほどのコードをこうする
print pathinfo($_FILES['photo']['name'][$photo_order[$i]], PATHINFO_EXTENSION);
jpg jpg jpg jpg
この拡張子をstrtolower関数で小文字に変換しています。
strtolower(pathinfo($_FILES['photo']['name'][$photo_order[$i]], PATHINFO_EXTENSION))
// jpg
ここでは元から小文字ですが、仮に大文字JPGだとすると、リスト['gif', 'jpg', 'jpeg', 'png']の中から探すときにjpgと一致しません。in_array関数は、大文字と小文字を区別するので、「許可されていない拡張子」と判断されてしまいます。
in_array関数
取得した拡張子(小文字)が、配列のリストの中にあるかチェック
!in_array(strtolower(省略), ['gif', 'jpg', 'jpeg', 'png']))
-
in_array(
$needle, $haystack)
配列$haystack内(['gif', 'jpg', 'jpeg', 'png'])から$needle(jpg)を探す。見つかればtrue、見つからなければfalseを返す。
「見つからない場合に」エラーメッセージを表示するので、if文は!in_array()です。
【参考】
③コンテンツタイプ(②より信頼性あり)
おおまかに、こんな処理です。
- 配列に、許可するコンテンツタイプ(MIMEタイプ)のリストを用意する。
['image/gif', 'image/jpg', 'image/jpeg', 'image/png'] - finfo_file関数で、各ファイルの内容を解析し、コンテンツタイプを取得
-
in_array関数を使い、このコンテンツタイプが配列のリストの中にあるかチェック
・ある(許可するコンテンツタイプ)→画像表示
・なし(許可しないコンテンツタイプ)→エラーメッセージを表示して終了
else if(!in_array(finfo_file(finfo_open(FILEINFO_MIME_TYPE), $_FILES['photo']['tmp_name'][$photo_order[$i]]),['image/gif', 'image/jpg', 'image/jpeg', 'image/png'])){
$error_msg = 'コンテンツタイプが画像ではありません。';
print '<div><p>'.$error_msg.'</p></div>';
}else{ // 画像表示
// 処理
}
if文の関数を確認していきます。
finfo_file関数
見やすいように、抜き出します。
finfo_file(finfo_open(FILEINFO_MIME_TYPE), $_FILES['photo']['tmp_name'][$photo_order[$i]]);
-
finfo_file(
$finfo, $filename) :$finfoに応じた、ファイルの情報を返す
$finfo:FileInfoインスタンス。finfo_open関数で作成(★1)
$filename: ファイルパス(★2)
(★1)
-
finfo_open(
$frags)
$frags: 「ファイルについて欲しい情報」を定数に指定。ファイルのMIMEタイプが欲しいので、FILEINFO_MIME_TYPE。
(★2)
-
$_FILES['photo']['tmp_name']
一時的なファイルの保存先(省略)(PHPでは、クライアントからファイルを受け取ると、暫定的に仮のフォルダーに保存しています)。(p.400)
print_r($_FILES['photo']['tmp_name']);
Array ( [0] => /tmp/phpXXXXXX [1] => /tmp/phpXXXXXX [2] => /tmp/phpXXXXXX …… )
↑ ↑ ↑
// a.jpg b.jpg c.jpg ……
各ファイルが保存されている、フォルダ名が表示されます。
項目の内容は、ファイル名の昇順に格納されています。これを、[$photo_order[$i]]の順(b.jpg、d.jpg、c.jpg、a.jpg)に、finfo_file関数で使います。
指定したFILEINFO_MIME_TYPE(MIMEタイプ)が返されるので、確認します。
for($i = 0; $i < $count_files; $i++){
print finfo_file(finfo_open(FILEINFO_MIME_TYPE), $_FILES['photo']['tmp_name'][$photo_order[$i]]);
}
image/jpeg image/jpeg image/jpeg image/jpeg
↑ ↑ ↑ ↑
// b.jpg d.jpg c.jpg a.jpg
in_array関数はこちらと同じです。
!in_array(finfo_file(省略),['image/gif', 'image/jpg', 'image/jpeg', 'image/png'])
['image/gif', 'image/jpg', 'image/jpeg', 'image/png']からimage/jpegを探します。見つかればtrue、見つからなければfalseを返します。
「見つからない場合に」エラーメッセージを表示するので、if文は!in_array()です。
3つのエラーチェックを全てクリアしたら、画像を表示します。
【参考】
画像の扱い
-
どこにも保存せず、そのまま表示する(今回はこれをします)
fopen関数で一時的な保存先を開き、ファイルの内容をfread関数で読み取る - サーバー側(ここではローカル環境・現在作業中のディレクトリ下)に保存&表示
move_uploaded_file関数を使って、画像ファイルを一時的な保存先→サーバー側へ移動
こちらの記事に詳しく書かれています。参考にさせていただきました。
【出典】
(9)画像を表示(どこにも保存せず、単に表示するだけ)
2つに分けて説明します。
}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>';
}
ファイルの読み取り
- 一時的なファイルの保存先
$_FILES['photo']['tmp_name'] から、ファイルをファイルサイズ分(= 全て)読み込む
$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関数
$fp = fopen($_FILES['photo']['tmp_name'][$photo_order[$i]], 'rb');
-
fopen(
$filename, $mode) : ファイルを開き、ファイルハンドルを返す
$filename: 開くファイルのパス
$mode: 開く際のモード
ファイルをノートにたとえるならば、実際にノートを手に取り、表紙を開くようなイメージです(p.239)
ファイルのオープンに成功すると、戻り値としてファイルハンドルを返します。ファイルハンドルとは、名前のとおり、そのファイルを操作(handle)するためのキーとなる情報です。(省略)resouce型に属する値です。今後、ファイルに対する読み書きは、このファイルハンドルに対して行うことになります。(p.239)
$filenameの$_FILES['photo']['tmp_name']は、一時的なファイルの保存場所(フォルダ名)。
print_r($_FILES['photo']['tmp_name']);
Array ( [0] => /tmp/phpXXXXXX [1] => /tmp/phpXXXXXX [2] => /tmp/phpXXXXXX …… )
↑ ↑ ↑
// a.jpg b.jpg c.jpg ……
項目の内容は、ファイル名の昇順に格納されています。これを、[$photo_order[$i]]の順(b.jpg、d.jpg、c.jpg、a.jpg)に、rbモードで開きます。
-
rbモード : 保存先からファイルを読み込む。その際、ファイル内の改行は勝手に変換しない
・r: 読み込み専用。この後ろに、t(テキストモード)かb(バイナリモード)を指定する。省略するとbになる
・t: ファイル内の改行を、OSに合わせて変換する
・b: 何もしない。tに頼らず正しい改行コードを書き、bを使うのが強く推奨。
fread関数
$get_photo = fread($fp, filesize($_FILES['photo']['tmp_name'][$photo_order[$i]]));
-
fread(
$stream, $length) : 読み込んだバイト列(=生データ)を返す
$stream: fopen()で得たファイルハンドル
$length: 読み込むサイズ(バイト)。ファイルサイズ分を指定する(= 全て読み込む)。ファイルサイズは、filesize関数で調べる。
filesize関数
-
filesize(
$filename) : ファイルのサイズを取得
$filename: ファイルパス
$filenameの$_FILES['photo']['tmp_name']は、一時的なファイルの保存場所(フォルダ名)です。項目の内容は、ファイル名の昇順に格納されています。これを、[$photo_order[$i]]の順(b.jpg、d.jpg、c.jpg、a.jpg)に指定し、各ファイルサイズを取得します。
fclose関数
ファイルを閉じる
ファイルに対する読み書きを追えたら、fclose関数でファイルをクローズします。ファイルのクローズとは、開いていたノートを閉じて、元あった場所に戻す行為とイメージしておけばよいでしょう。(p.240)
【参考】
表示処理
- <img>を完成させます。src属性は、データURLです
- データURLは、「data:MIMEタイプ;base64,データ本体」という文字列です
・MIMEタイプ : 現在ここだけ不明なので、これから取得
・データ本体 : 先ほど取得した生データを、データURLで使える形にします(※)
(※)取得した生データは「文字として読む前提がないもの(画像)」= バイナリデータです。base64でエンコードします。
base64 を指定して、base64 エンコードされたバイナリーデータを埋め込むことができます。
(出典 : data: URL)
$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_encode(
$string) :$string(データ)をbase64でエンコードして返す
Base64 の一般的な用途としては、以下のものがあります。(省略)バイナリーデータをエンコードして data: URL に含めることができるようにする場合
(出典 : Base64)
getimagesize関数
現在、<img>のsrc属性(データURL)を書くために必要な、MIMEタイプが不明です。エラーチェックの3つめでMIMEタイプを調べましたが、変数に保存していないので再度調べる必要があります。getimagesize関数を使って調べますが、これもまた引数にデータURLを使います。
$photo_info = getimagesize('data:application/octet-stream;base64,' . $encode_photo);
MIMEタイプを調べたいのに、MIMEタイプを書かなくてはいけません。何を書けばいいのか分からないですよね。MIMEタイプapplication/octet-streamは、まさにそのような状況で使用できます。
どのMIMEタイプを使うべきか分からないとき
(出典 : わわわIT用語辞典 - application/octet-stream【MIMEタイプ】)
クライアントソフトなどでは、データ形式不明の単なるバイト列として取り扱われる
(出典 : IT用語辞典 e-Words - application/octet-stream)
getimagesize関数が取得した情報を見てみます。(出力は画像1枚とします)
print_r($photo_info);
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タイプ
[mime]に、MIMEタイプがあります。これだけが必要です。
print $photo_info['mime'];
image/jpeg
<img>のsrc属性(データURL)の、MIMEタイプ部分に当てはめます。
print '<div><img src="data:' . $photo_info['mime'] . ';base64,' . $encode_photo . '"></div>';
完成です。
【参考】
upload.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/preview.css"/>
</head>
<body>
<div id="preview">
<div id="all-photos" class="layout-<?=$count_files;?>">
<?php
for($i = 0; $i < $count_files; $i++){
if($_FILES['photo']['error'][$photo_order[$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'][$photo_order[$i]]];
print '<div><p>'.$error_msg.'</p></div>';
}else if(!in_array(strtolower(pathinfo($_FILES['photo']['name'][$photo_order[$i]])['extension']), ['gif', 'jpg', 'jpeg', 'png'])){
$error_msg = '拡張子が画像ではありません。';
print '<div><p>'.$error_msg.'</p></div>';
}else if(!in_array(finfo_file(finfo_open(FILEINFO_MIME_TYPE), $_FILES['photo']['tmp_name'][$photo_order[$i]]),['image/gif', 'image/jpg', 'image/jpeg', 'image/png'])){
$error_msg = 'コンテンツタイプが画像ではありません。';
print '<div><p>'.$error_msg.'</p></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>