画像ファイルのアップロード①(HTML+PHPのみ)
このHowTo文章(『画像ファイルのアップロード』)ではHTMLページから送信した画像ファイルなどをPHPプログラムで受け取る処理の手順を説明しています。何回かの掲載で、最終的には皆さんがよく見かける様なドラッグ&ドロップなどにも対応する実用的なプログラムへ改造していきましょう。ちなみに、サンプルを置いてあるレンタルサーバ(さくらのレンタルサーバ)のPHPのバージョンは 7.4.10 です。
簡単なファイルアップロードを実現する画面の流れ
Webプログラミングは何が面白いかと言うと、作り込んだ結果がすぐに見えるところではないでしょうか?そのため、完成する前でもちょこちょこと小さな充実感が味わえるという要素、パブロフの犬的なご褒美欲しさにプログラム作る訳?と小馬鹿にされたとしても、モチベーションが持ち続けられるため苦のない作業の継続へつながるため、それはそれでありがたい流れではないでしょうか。さて、ファイルアップロードを簡潔に作ったWebページの画面は次の様になります。
ファイルアップロードの最初のページには [ファイルを選択] ボタンと [アップロード] ボタンが必ず必要です。[ファイルを選択] のボタンはWebブラウザによって表示が結構違います(けれど誰しもがわかりますけれどね)。この画面で最初に、[ファイルの選択] ボタンをクリックすると、次のようにOSで用意された様なファイルを選択するダイアログが表示されます(このダイアログはWindowsとMacOSで見た目や操作に多少の違いがあります)。
この画面キャプチャーはMacOSで、Windowsでもそれなりのダイアログが表示されるので、ユーザにとって操作がわからないという恐れはないでしょう。MacOSの場合は左列の Pictures などを選んで表示されるファイル一覧から画像ファイルの(ここではキャット.png)などを選び、右下の [開く] ボタンをクリックすると、まだファイルの転送は始まらないで、次の様な画面が表示されます。
よく見るとわかる様に、[ファイルを選択] ボタンの横に選んだファイル名(ここでは キャット.png)が表示されました。ここで、選ばれた画像が表示される様にするには、JavaScriptプログラムの記述が必要です。画像が選ばれた状態で、ウィンドウ右にある [アップロード] ボタンをクリックすると、実際のアップロード処理であるファイル転送が行われて次の画面となります。
ファイルを受け取る画面は裏こっそりとプログラムがファイルを処理して終わるのが基本です。それだけでは少し物足りないため、ここではアップロードした画像(ここでは キャット.png)がわかる様に、受け取った画像ファイル(猫の絵)を表示してみました。動作確認ができるサンプル・ページのアドレスは次となります。
https://openpne.sakura.ne.jp/note/file1.html
<input type="file">タグからPHPへファイルを送信
まず最初のページの一番基本的なコードを見ていきます。最も簡単な表現はhtmlファイルで、ユーザがファイルを選ぶための file1.html ファイルは次の様になります。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>ファイル送信ページ1</title>
</head>
<body>
<form method="post" enctype="multipart/form-data" action="file1.php">
<input type="file" name="up">
<input type="submit" value="アップロード">
</form>
</body>
</html>
雛形的な<!DOCTYPE html>や<html>タグなどはさて置き、<form>タグと2つの<input>タグが肝となり、まずは<form>タグから見ると、enctype属性とmethod属性とaction属性が必要となります。ファイルを送信するにはmethod属性で "post" を指定し、enctype属性で ”multipart/form-data” の指定が必要です。フォームでデータを送る手法としてPOSTメソッドを使いたいため、method属性に "post" を指定します。POSTメソッドは、WebブラウザからWebサーバへ送るコマンド(HTTPリクエストの本文)の後にファイルのデータを追加して送る方式。enctype属性で ”multipart/form-data” を指定すると、ファイル名やファイルデータを複数のデータがフォーム本体に含めて送ります。最後のaction属性はデータの送り先であるアドレスを指定しました。送り先のphpファイルがあるサーバとディレクトリ(WindosやMacOSであればフォルダと呼ぶ)がhtmlファイルと同じなので、「https://〜/…/」は省略されています。
次に、<input>タグでtype属性に "file" を指定すると、ファイルを選ぶ部品となります。昔はできなかった様な気がするのですが、HTML5対応のブラウザではファイルのドラッグ&ドロップをいつの間にかサポートしていました。更にname属性で受け取るプログラムから参照するための名前を指定しておきます。
最後に、<input>タグでtype属性に "submit" を指定すると、サブミット・ボタンとなります。サブミット・ボタンをクリックすると、action属性で指定したアドレスへデータを送信するトリガとなります。value属性に "アップロード" 文字列を指定すると、ボタン内に表示されるので、わかりやすいです。余計なことですが、今回の場合は、どのサブミット・ボタンが押されたか知る必要がないので、name属性を指定する必要はないです。
PHPプログラムでファイルを受信(前半①)
ファイルデータを受け取るのはHTMLファイルではなくてPHPプログラムとなり、ファイルを受け取る file1.php プログラムの最小限の記述は次の様になります。
<?php
$up_file = "";
$up_ok = false;
$tmp_file = isset($_FILES["up"]["tmp_name"]) ? $_FILES["up"]["tmp_name"] : "";
$org_file = isset($_FILES["up"]["name"]) ? $_FILES["up"]["name"] : "";
if( $tmp_file != "" &&
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";
$up_ok = move_uploaded_file( $tmp_file, $up_file);
}
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>ファイル受信ページ1</title>
</head>
<body>
<p><?php if( $up_ok ){ ?>
アップロードされたファイルは <img src="<?= $up_file ?>"> です。
<?php }else{ ?>
アップロードは失敗しました。
<?php } ?></p>
<a href="file1.html">アップロードページへ戻る</a>
</body>
</html>
ファイルを受け取るPHPのプログラムは前後の2つ(1〜16行目と17〜31行目)に大きく分かれており、前半はファイルを安全なところにコピーし、後半では画面への情報表示に対応しています。
前半から見ていくと、最初の2〜5行目は変数の初期化です。$up_file変数は最終的なファイルのパスを空文字列("")で初期化し、ファイルがまだ無いこととしています。$up_ok変数はファイルのコピーの状態をfalseにして読み込みが終わっていない状態として初期化。$tmp_file変数は送られてきたファイルを保存するファイルの相対パス(相対なのでPHPプログラムがある場所からたどる相対ディレクトリ+ファイル名)となります。ファイルの情報は$_FILES配列から受け取るため、isset()関数で$_FILES配列が存在するか確認しつつ受け取り、空だった場合は空文字列("")で初期化しました。$org_file変数は送られた元ファイルの名前を保存する変数で、$_FILES配列があれば受け取りつつ、空だった場合は空文字列("")で初期化します。
ファイル情報を受け取る$_FILES配列
ここで少し補足しておくと、$_FILES配列は2次元の配列で、何処からでも見えるスーパーグローバルな定義済み変数でもあります。データを送信する<input type="file">タグのname属性が具体的に "UP" であれば、
$_FILES["UP"]["〜"]
という記述で参照し、送られたファイルの情報を受け取れるでしょう。送信されたファイル自体は既に一時的な作業領域となる /tmp ディレクトリなどに保存されたファイルです。$_FILESから取得できる情報は次の様な項目となります。
$_FILES["UP"]["name"] ・・・・・元ファイルのファイル名
$_FILES["UP"]["tmp_name"]・・・保存されたファイルの絶対パス
$_FILES["UP"]["type"] ・・・・・元ファイルのファイルタイプ
$_FILES["UP"]["size"]・・・・・・元ファイルのサイズ
$_FILES["UP"]["error"] ・・・・・エラー
保存されたファイルの絶対パスはディレクトリ+ファイル名で、「/var/tmp/phpcmMDTH」などの様なシステムが用意した/(ルート)ディレクトリからの絶対パスのディレクトリとランダムに作成されたファイル名(拡張子なし)を結合した文字列です。ランダムな文字列というのは、ファイル名の重複を避けているのでしょう。また、拡張子無しというのは、システムが用意した誰でもが読み書きできる場所に受け取ったファイルが一時的に置かれるので、誰かに見られたとしても何のファイルか想像い辛い様にしているのかもしれません。ファイルは一定時間が経過すると消えてしまうため、受け取ったプログラムは即座に自分が管理している領域へファイルをコピーする必要があります。エラーに関しては、また機会があれば、触れていきましょう。
PHPプログラムでファイルを受信(前半②)
少し離れてしまったので、もう一度、PHPプログラムの前半をもう一度載せておくと(1〜16行目)、
<?php
$up_file = "";
$up_ok = false;
$tmp_file = isset($_FILES["up"]["tmp_name"]) ? $_FILES["up"]["tmp_name"] : "";
$org_file = isset($_FILES["up"]["name"]) ? $_FILES["up"]["name"] : "";
if( $tmp_file != "" &&
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";
$up_ok = move_uploaded_file( $tmp_file, $up_file);
}
}
6行目と7行目の判定は、
受け取りファイルの絶対パス($tmp_file)が空文字列("")でなく
かつ
受け取りファイル($tmp_file)がアップロードされたファイルである
という2つの条件が成立すれば9行目へ進みます。7行目の is_uploaded_file()関数が本当にアップロードされたファイルなのか調べる関数です。9行目は元ファイル名($org_file)を '.' (ドット)で分割し、最後の文字列(拡張子)を取り出して$ext変数へ代入します。
10行目と11行目の判定は、
元ファイル名の拡張子($ext)が空文字列("")でなく
かつ
元ファイル名の拡張子($ext)が元ファイル名($org_file)でない
という2つの条件が成立すれば13行目へ進みます。13行目では保存するファイルの相対パスを作成して$up_file変数へ代入しています。相対パスはimgディレクトリの下( img/ )に、「年月日_時分秒.」( date("Ymd_His.") )と「数字4桁.」( mt_rand(1000,9999)→mt_rand()関数で1000以上9999以下の乱数を生成)と拡張子($ext)をつなげました。
例:img/20201009_010203.1234.jpg
15行目では、受け取りファイルの絶対パス($tmp_file)から保存ファイルの相対パス($up_file)へmove_uploaded_file()関数で移動しています(ファイルを移動すると、元のファイルはなくなり、移動先のファイル名が指定されたファイル名となります)。
PHPプログラムでファイルを受信(後半)
以降で後半の表示部分を見ていきましょう(17行目〜31行目)。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>ファイル受信ページ1</title>
</head>
<body>
<p><?php if( $up_ok ){ ?>
アップロードされたファイルは <img src="<?= $up_file ?>"> です。
<?php }else{ ?>
アップロードは失敗しました。
<?php } ?></p>
<a href="file1.html">アップロードページへ戻る</a>
</body>
</html>
24行目の「<?php if( $up_ok ){ ?>」はファイル移動の結果($up_ok)が成功(true)しているか確認するif文です。ファイル移動が成功していれば、25行目へ進み、文字列とsrc属性でコピーしたファイルの相対パス($up_file)を指定した<img>タグを表示。ファイル移動が成功していなければ、26行目の「<?php }else{ ?>」の else が当てはまるので、27行目のエラーメッセージが表示されます。そして28行目の「<?php } ?>」の } でif〜else文が終了です。
まとめ
アップロードファイル(転送ファイル)は一時的なファイル(テンポラリ・ファイル)として保存されるため、すぐにでもファイルのコピーや移動が必要です。また、コピー先のディレクトリが存在し、WebサーバのプログラムであるApache HTTP Serverなどを動かしているユーザ(Linuxの場合はapache)がディレクトリの書き込み権限を持っている必要もあります。ここいら辺の設定はLinuxシステムの管理知識が必要な場合もあるでしょう。
以上で、ファイルアップロードの説明は終わりましたが、世間一般で見るファイルアップロードと比べるとかなりお粗末と言うか初歩的な段階です。みなさんがよく見かけるサービスであればファイルのドラッグ&ドロップや、ドロップした画像が表示されるなどの拡張があるでしょう。次回は、その辺を少しバージョンアップしていきたいかと考えています。