複数画像ファイルのアップロード①(HTML+PHPのみ)
画像ファイルのアップロード①②③では1つのファイルにのみ対応しました。顔写真とか免許証の提出ならこれで充分とはいえ、旅の写真とか、猫の写真をアップしてもらうとなると、一枚ごとにクリック→選択とか面倒だわ〜っ!!と関西のおばちゃん・おっさんに叫ばれそうですね。そんな時は複数枚のアップロードに対応しないとでしょう。ちなみに、今回からPHP7.x対応のプログラムです。
簡単なファイルアップロードを実現する画面の流れ
ファイルアップロードを簡潔に実現したWebページの画面は「画像ファイルのアップロード①」と全く同じで、次の様になります。
(1)files1.html:最初のページを開いた状態
ファイルアップロードの最初のページで [ファイル選択] ボタンをクリックすると、次の様なOSで用意されたファイル選択ダイアログが表示されます。
(2)files1.html:fileボタンをクリックしてファイルを選択中
ファイルの選択ダイアログでは、[Shift] キーや [Ctrl] キー(MacOSでは [Command] キー)を使うと、複数ファイル選べます。ご存知の様に [Shift] キーを押しながらファイルを選ぶと連続してファイルを選べ、[Ctrl] キー(MacOSでは[Command]キー)を押しながら選ぶと画面イメージの様に離れたファイルも複数枚選べます。ファイルを選んで、最後に [開く] ボタンをクリックするとダイアログが閉じるでしょう。
(3)files1.html:ファイルを選択した状態
ファイルを1つ選んだときにファイルの名前が出たのに対し、複数ファイルを選ぶと選んだファイルの数が表示されます。Chromeブラウザの場合、fileボタンへマウスを合わせると、ファイルの名前一覧がポップアップで表示されました。ファイルが選ばれた場合は、[アップロード] ボタンをクリックして次の画面へ進む。
「画像ファイルのアップロード①」と比べると、複数画像ファイルのアップロード後の画面は複数画像を表示しました。動作確認ができるサンプル・ページのアドレスは次となります。
https://openpne.sakura.ne.jp/note/files1.html
<input type="file">タグからPHPへファイルを送信
まず最初のページとなる基本的なコードを見ていきます。最も簡単な表現はHTMLファイルで、実際にファイルを選ぶための files1.html ファイルは次の様になりました。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>複数ファイル送信ページ1</title>
</head>
<body>
<form method="post" enctype="multipart/form-data" action="files1.php">
<input type="file" name="up[]" multiple>
<input type="submit" value="アップロード">
</form>
</body>
</html>
files1.html
前(ファイルのアップロード①)に説明した時と大きく違うのは、fileボタンとなる<input type="file">タグの9行目です。<input type="file">タグでは、name 属性で指定された up の後に「[]」が追加されて、配列という複数ファイルを受け取れる仕組みを指定しています。合わせて、multiple 属性というオプションも追加することで、複数ファイルへの対応となっています。その他の詳しい説明は前述したファイルのアップロード①をお読みください。
PHPプログラムでファイルを受信(前半)
ファイルデータの受け取りを担当するのはHTMLファイルではなくてPHPプログラムとなり、PHPのプログラムは前後の2つ(1〜22行目と23〜41行目)に大きく分かれています。前半部分は複数ファイルを安全なところに移動し、後半部分は複数ファイルの表示に対応しました。まず、ファイルを受け取る files1.php プログラムの前半部分の実装は次の様になります。
<?php
$files = [];
$MAXS = count($_FILES["up"]["tmp_name"] ?? []);
for($i=0,$j=0; $i < $MAXS; $i++)
{
$size = $_FILES["up"]["size"][$i] ?? "";
$tmp_file = $_FILES["up"]["tmp_name"][$i] ?? "";
$org_file = $_FILES["up"]["name"][$i] ?? "";
if( $tmp_file != "" && $org_file != "" && 0 < $size && $size < 1048576 &&
is_uploaded_file($tmp_file) )
{
$split = explode('.', $org_file); $ext = end($split);
if( $ext != "" && $ext != $org_file )
{
$up_file = "img/" .date("Ymd_His.").mt_rand(1000,9999). ".$ext";
if( move_uploaded_file( $tmp_file, $up_file) )
$files[$j++] = array('size' => $size, 'up_file' => $up_file,
'tmp_file' => $tmp_file, 'org_file' => $org_file);
}
}
}
?>
files1.php(前半)
前半部分を見ると、最初の2〜3行目は変数の初期化です。
$files = [];
$MAXS = count($_FILES["up"]["tmp_name"] ?? []);
2〜3行目(files1.php)
2行目は、連想配列の$filesをクリア(要素がない状態に)していて、この$files連想配列は受け取った複数ファイルの情報を保持するための配列です。3行目は受け取りファイルがあればファイル数、なければ0で$MAXS変数を初期化し、$MAXS変数はほぼ定数と考えてください。
3行目で使われている「??」はPHP7.0から取り入れられたnull合体演算子で、??の前の式が非NULL(存在)であれば??の前の式($_FILES["up"]["tmp_name"])、または、NULL(ない)であれば??の後の式([])を返します。ちなみに、「[]」は要素が0個の空配列です。PHP5.x以前をお使いなら、??の代わりに三項演算子(? :)とisset関数を組み合わせる必要があります。
for($i=0,$j=0; $i < $MAXS; $i++)
{
・
・
・
}
4,5行目と21行目(files1.php)
4行目は受け取りファイル数の回数分だけ繰り返すfor文の開始で、$i変数は受け取りファイルのインデックス、$j変数はコピーしたフェイルのインデックス、$MAXSは前述の様に受け取りファイル数です。ここで出てきたインデックスとは配列をアクセスするarray[key]のkeyとして使用され、以降の5行目から21行目は、ファイルのコピーを繰り返します。
$size = $_FILES["up"]["size"][$i] ?? "";
$tmp_file = $_FILES["up"]["tmp_name"][$i] ?? "";
$org_file = $_FILES["up"]["name"][$i] ?? "";
6〜8行目(files1.php)
6行目は$size変数に受け取りファイルのバイトサイズ、7行目は$tmp_file変数に受け取りファイルの一時的に保存したファイル名、8行目は$org_file変数にオリジナルなファイル名を受け取ります。受け取りファイルは一時的に/tmpなどの一時的な場所に置かれるため、時間が経つと(数時間〜数日:システムの設定で違う)消されてしまいがちです。受け取り後に再利用する場合は、他の保管場所へファイルをコピーまたは移動しましょう。
if( $tmp_file != "" && $org_file != "" && 0 < $size &&
is_uploaded_file($tmp_file) )
{
9〜11行目(files1.php)
9行目はファイルコピー前の1回目の確認で、受け取りファイル名の$tmp_fileが空文字列("")でないか、オリジナルファイル名の$org_fileが空文字列("")でないか、受け取りファイルサイズの$sizeが0より大きいかを条件判定し、9行目はis_uploaded_file()関数で受け取りファイルの存在するかを調べ、条件を満たすと以降の処理へ進みます。
$split = explode('.', $org_file); $ext = end($split);
if( $ext != "" && $ext != $org_file )
{
12〜14行目(files1.php)
12行目は元ファイル名から拡張子を取り出して$ext変数に代入します。13行目はファイルを移動して保存するかどうかの最終チェックです。
拡張子の$ext変数が空文字列("")でない
拡張子がある(拡張子の$ext変数が元ファイル名の$org_fileと違う)
2つの条件に合えば、15行目以降を実行します。
$up_file = "img/". date("Ymd_His.") . mt_rand(1000,9999) . ".$ext";
if( move_uploaded_file( $tmp_file, $up_file) )
$files[$j++] = array('size'=> $size, 'up_file' => $up_file,
'tmp_file' => $tmp_file, 'org_file' => $org_file);
15〜18行目(files1.php)
15行目はアップロードファイルの最終的なファイルパス(移動先)となる文字列の作成で、ファイルパスは、「img/」ディレクトリで始まり、date()関数で今日の日付時刻、さらにmt_rand()関数を使って4桁のランダムな数(1000〜9999)と、最後に拡張子の$ext変数をつなげて作りました。16行目はmove_uploaded_file()関数を使っての、一時ファイル($tmp_file)を最終的なファイルパス($up_file)への移動です。ファイル移動が成功すれば、17,18行目で受け取りファイルの情報を$files連想配列に登録しています。
PHPプログラムでファイルを受信(後半)
受け取りプログラムの後半は受け取った複数ファイルを表示しています。前半で、拡張子がないファイルやサイズが0のファイルなど、怪しい要因をすでに取り除いてあるため、表示だけに専念しています。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>複数ファイル受信ページ1</title>
</head>
<body>
<p><?php if( 0 < $MAXS=$j ){ ?>
アップロードされたファイルは<?php for($i = 0;$i < $MAXS;$i++){ ?>
<?= (0 < $i) ? "と" : "" ?>
<img src="<?= $files[$i]['up_file'] ?>"
alt="<?= $files[$i]['org_file'] ?>">
<?php } ?>です。
<?php }else{ ?>
アップロードはすべて失敗しました。
<?php } ?></p>
<a href="files1.html">アップロードページへ戻る</a>
</body>
</html>
files1.php(後半)
23行目〜29行目はHTML5ファイルの基礎ともいえるひな形となる部分なので、前に紹介したプログラムと同じく、タイトルを変更すれば他への流用もすぐにできるでしょう。
<p><?php if( 0 < $MAXS=$j ){ ?>
30行目(files1.php)
30行目のif文はアップロード数の判定しており、0より大きければアプロードファイルがあるので31〜35行を実行しています。
<?php }else{ ?>
アップロードはすべて失敗しました。
<?php } ?></p>
36〜38行目(files1.php)
36行目のelse文以降はアップロード数が0以下に当てはまり、37行目でエラーメッセージを表示しています。
アップロードされたファイルは<?php for($i = 0;$i < $MAXS;$i++){ ?>
<?= (0 < $i) ? "と" : "" ?>
<img src="<?= $files[$i]['up_file'] ?>"
alt="<?= $files[$i]['org_file'] ?>">
<?php } ?>です。
31〜35行目(files1.php)
ここで一旦、アップロード数が0より大きい時(ファイルアップロードがあった場合)に実行される31〜35行へ話を戻しましょう。31行目の後半にあるPHPプログラムのfor文から35行目の「}」で、アップロードされたファイル数回の繰り返しをしています。
ループの中で、32行目は画像と画像の間の区切りとなる「と」を表示し、33,34行目は<img>タグのsrc属性でアップロードされたファイルを指定し、さらに、alt属性で元ファイルの名前を指定しています。画像が普通にWebブラウザで表示されるのに対し、alt属性のファイル名は画像の上へマウスを持って行くと対応Webブラウザであればポップアップして表示されようです。
<a href="files1.html">アップロードページへ戻る</a>
最後の39行目は最初のファイル転送ページへ戻るリンク、40,41行目は最初のひな形となるHTMLを閉じる</body>タグと</html>タグです。
まとめ
今回の複数画像ファイルのアップロードは、<input type="file">タグで受け取りの名前を配列に対応したのと、multiple属性が追加ありました。そして、受け取りプログラムも配列に対応した繰り返しの処理がおこなわれます。また、PHP7.xに対応する??(null合体演算子)を使ってみました。以前に紹介した『画像ファイルのアップロード①』よりはプログラムらしくなったのではないでしょうか?次回は、単数ファイルのアップロードの流れと同じく、アップロードする前に選択された複数ファイルを表示してみましょう。