解説コードを読んでみよう
このサンプルでわかること
- ファイルを送信するフォームに必要な
enctype="multipart/form-data" - 画像の入力欄も
name="img[]"で増やせること - PHP の
$_FILESに届くデータの形(name・type・size・error)
行を増やす仕組みは前のサンプル(テキスト&テキスト)とまったく同じです。ここでは、2列目を画像ファイルの入力欄に変えたときに何が変わるのかを見ていきます。
使っているファイル
- index.php入力フォームと「追加」の JavaScript
- data.php送信されたテキストとファイルの情報を表示する PHP
1. ファイルを送れるフォームにする
<form action="data.php" method="post" enctype="multipart/form-data">
<table class="add-form">
<thead>
<tr>
<th>テキスト</th>
<th>画像</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="text" name="text[]"></td>
<td><input type="file" name="img[]" accept="image/*"></td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="2"><button id="add" class="btn btn-default" type="button">+ 追加</button></td>
</tr>
</tfoot>
</table>
<hr>
<input type="submit" class="btn btn-default" name="send" value="送信">
</form>
ポイントは2つです。
<form>にenctype="multipart/form-data"を付ける。- ファイルの入力欄
<input type="file">にも、名前の最後に[]を付けてname="img[]"にする。accept="image/*"は「画像ファイルだけ選べるようにする」という指定です。
enctype="multipart/form-data" を書き忘れると、ファイルの名前だけが文字として送られ、中身は届きません。$_FILES も空になります。ファイルを送るフォームでは必ず付けましょう。
2. 「追加」ボタンの JavaScript
追加する1行分の HTML に、ファイルの入力欄が入っただけです。
<script>
$(function() {
// 「追加」ボタンで、テキストと画像の入力欄を1行追加する
$('button#add').click(function(){
var tr_row = '' +
'<tr>' +
'<td><input type="text" name="text[]"></td>' +
'<td><input type="file" name="img[]" accept="image/*"></td>' +
'</tr>';
$(tr_row).appendTo($('.add-form > tbody'));
});
});
</script>
3. PHP で受け取る:$_FILES の形に注意
テキストは $_POST、ファイルは $_FILES に届きます。name="img[]" で複数送ったときの $_FILES は、少し変わった形をしています。
Array
(
[img] => Array
(
[name] => Array ( [0] => cat.jpg [1] => dog.png )
[type] => Array ( [0] => image/jpeg [1] => image/png )
[tmp_name] => Array ( [0] => /tmp/phpA1b2 [1] => /tmp/phpC3d4 )
[error] => Array ( [0] => 0 [1] => 0 )
[size] => Array ( [0] => 34567 [1] => 12345 )
)
)
「1つ目のファイルの情報」がひとまとまりになっているのではなく、項目ごと(name、type …)に配列になっています。そのため、1つ目のファイル名は $_FILES['img']['name'][0]、2つ目のサイズは $_FILES['img']['size'][1] のように書きます。
| 項目 | 中身 |
|---|---|
name | 選んだファイルの元の名前 |
type | ファイルの種類(ブラウザが申告した値なので、信用しすぎないこと) |
tmp_name | サーバー上に一時保存された場所。処理が終わると自動で消えます |
error | 受け取り結果の番号。0(UPLOAD_ERR_OK)なら成功、4 はファイル未選択 |
size | ファイルの大きさ(バイト) |
data.php では、テキストの番号 $i を使って、同じ番号のファイル情報を取り出しています。
<?php foreach ($text as $i => $value):
$err = (int)($files['error'][$i] ?? UPLOAD_ERR_NO_FILE); ?>
<tr>
<td><?= h($i) ?></td>
<td><?= h($value) ?></td>
<td><?= h($files['name'][$i] ?? '') ?></td>
<td><?= h($files['type'][$i] ?? '') ?></td>
<td><?= h(number_format((int)($files['size'][$i] ?? 0))) ?> バイト</td>
<td><?= h($upload_errors[$err] ?? 'エラー番号 ' . $err) ?></td>
</tr>
<?php endforeach; ?>
error の番号は、そのままでは意味が分かりにくいので、先に日本語の対応表を作っておき、番号から説明文を引けるようにしています。
// アップロードのエラー番号を日本語にする
$upload_errors = [
UPLOAD_ERR_OK => '正常に受け取りました',
UPLOAD_ERR_INI_SIZE => 'サーバーの上限サイズを超えています',
UPLOAD_ERR_FORM_SIZE => 'フォームで指定した上限サイズを超えています',
UPLOAD_ERR_PARTIAL => '途中までしか届きませんでした',
UPLOAD_ERR_NO_FILE => 'ファイルが選ばれていません',
];
このサンプルはファイルを受け取って情報を表示するだけです。tmp_name の一時ファイルは処理が終わると消えるので、サーバーには残りません。実際に保存する方法は「データベースに保存する」のサンプルで解説しています。
PHP 8.3 で書き直したポイント
送信せずに data.php を開いたときや、画像を選ばなかった行があるときに警告が出ないよう、?? [] や ?? '' で「値がないときの代わりの値」を決めてから使っています。
画像を選ばずに送信したり、画像ではないファイルを選んだりして、error や type がどう変わるか確かめてみましょう。