PHPでのFile Upload のやりかた
今はやり方をしっかり知らなくてもCMSとかFrameworkのライブラリで簡単にできちゃうのがUpload。
Uploadの方法を書いて!ってAIに言ったらできちゃうしね。
でも、これをあえてPUREなPHPで書いて仕組みをちゃんと知りたいねと。
基本の基本を書いていこうと思います。
まずはPHPの設定から
php.iniというPHPの設定ファイルが存在します。
その中でPHPのアップロードが許可されているかを確認します。
また、アップロードが許可されていても、ファイルサイズ・アップロード時間の指定もあります。
その辺をしっかり確認してみましょう。
// アップロードの許可
file_uploads = On
// アップロードできる最大サイズ
upload_max_filesize = 128M
// postファイルの最大サイズ
post_max_size = 128M
// メモリサイズ
memory_limit = 128M
//タイムアウト時間
max_execution_time = 360
よく、PHPMyAdminなどでDBがインポートできないときの原因は大概この辺の設定にあります。
自分の所感としては、memory_limit=>post_max_size=>upload_max_filesize です。
全部同じでも問題ないです。必要な数値を設定します。
処理中に止まらないように、タイムアウト時間も余裕を持たせて設定します。
ファイルアップロード用のフォームを作成
ファイルアップロードのformタグを書いた、index.php、post先にprocess.php、成功した画面用にsuccess.phpを作成します。
<?php
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Upload</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 min-h-screen flex items-center justify-center">
<div class="w-full max-w-md">
<form action="process.php" method="POST" enctype="multipart/form-data" class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
<h2 class="text-2xl font-bold mb-6 text-center text-gray-700">ファイルアップロード</h2>
<div class="mb-6">
<label for="up-file" class="block text-gray-700 text-sm font-bold mb-2"> アップロードするファイル:</label>
<input type="file" name="up-file" id="up-file"
class="block w-full text-sm text-gray-500
file:mr-4 file:py-2 file:px-4
file:rounded-md file:border-0
file:text-sm file:font-semibold
file:bg-blue-50 file:text-blue-700
hover:file:bg-blue-100">
</div>
<div class="flex items-center justify-center">
<button type="submit"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline transition duration-150 ease-in-out">アップロード</button>
</div>
</form>
</div>
</body>
</html>
こんな感じの画面になります。(見た目を整えるのにTailwindcssを使っています)
大事なのは、inputタグのenctype="multipart/form-data"。これが無いとファイルのアップロードができません。
あとファイルアップロードの時は、inputタグのtypeはfileになります。
<?php
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>処理完了</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 min-h-screen">
<div class="container mx-auto px-4 py-16">
<div class="max-w-md mx-auto bg-white rounded-lg shadow-lg p-8">
<!-- 成功アイコン -->
<div class="flex justify-center mb-6">
<div class="bg-green-100 rounded-full p-3">
<svg class="w-12 h-12 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
</div>
</div>
<h1 class="text-3xl font-bold text-center text-gray-800 mb-4">処理が完了しました</h1>
<div class="text-center mb-8">
<p class="text-gray-600">
<?php echo htmlspecialchars($success_message); ?>
</p>
</div>
<div class="text-center">
<a href="index.php"
class="inline-block bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-6 rounded-lg transition duration-300">
トップページに戻る
</a>
</div>
</div>
</div>
</body>
</html>
success.phpは成功時に$success_messageを受け取って表示する仕様にしておきます。
Sessionを使って便利にしちゃおう
セッションを使ってメッセージやトークンの設定ができるようにします。
全てのphpファイルの先頭に追加します。
//session start
session_start();
index.phpではフォームのトークンの生成をしたいので、index.phpに以下を追加します。
<?php
session_start();
//token create
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$csrf_token = $_SESSION['csrf_token'];
?>
...(略)...
<form>
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf_token) ?>">
<input type="hidden" name="action" value="uploadFile">
これでPOSTの際に、トークンが一致すると処理が行われるフォームになります。
安全なフォームには必要な処理です。
POST先のprocess.phpの処理
index.phpからポストされたファイルの処理を行います。
まずはリクエストの確認から、トークンの検証。それが終わるとようやく、ポストデータの処理になります。
ポストデータの処理は、アップロード先の取得 → エラーチェック → 拡張子チェック → ファイル名変更の順になります。
全て成功すると、successに渡すセッションを代入してリダイレクトします。
<?php
/**
* post process
*/
session_start();
// POSTリクエストの確認
if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'uploadFile') {
// POSTデータの取得と検証
$data = array_map(function($value) {
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}, $_POST);
// CSRFトークンの検証
if (!isset($data['csrf_token']) || !validateCsrfToken($data['csrf_token'])) {
die('不正なリクエストです');
}
//アップロードを許可する拡張子
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'webp'];
try {
// ここでPOSTデータの処理を行う
// Upload Dir
$UpDir = __DIR__ . '/uploads/';
// ディレクトリが存在しない場合は作成
if (!file_exists($UpDir)) {
mkdir($UpDir, 0777, true);
}
if (
!isset($_FILES['up-file']['error']) ||
is_array($_FILES['up-file']['error'])
) {
throw new RuntimeException('無効なパラメータです。');
}
//error check
switch ($_FILES['up-file']['error']) {
case UPLOAD_ERR_OK:
break;
case UPLOAD_ERR_NO_FILE:
throw new RuntimeException('ファイルは送信されませんでした。');
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
throw new RuntimeException('ファイルサイズの制限を超えました。');
default:
throw new RuntimeException('不明なエラー。');
}
// You should also check filesize here. (128MB = 134217728B )
//PHPの設定ではなくサイトで5MBにしたなら5242880になる
if ($_FILES['up-file']['size'] > 134217728) {
throw new RuntimeException('ファイルサイズの制限を超えました。');
}
//拡張子チェック
$finfo = pathinfo($_FILES['up-file']['name']);
$ext = strtolower($finfo['extension']);
if (in_array($ext, $allowedExtensions) === false) {
throw new RuntimeException('ファイル形式が無効です。');
}
//ファイル名の変更をするとき
//日本語ファイル名を日時に変えるならsha1_file($_FILES['up-file']['tmp_name'])をtime()などに
$newFileName = sprintf( $UpDir . '%s.%s',
sha1_file($_FILES['up-file']['tmp_name']),
$ext
);
if (!move_uploaded_file( $_FILES['up-file']['tmp_name'], $newFileName)) {
throw new RuntimeException('アップロードしたファイルの移動に失敗しました。');
}
// セッションに成功メッセージを保存
$_SESSION['success_message'] = 'ファイルのアップロードが完了しました。';
// 処理成功時のリダイレクト
header('Location: success.php');
exit;
} catch (Exception $e) {
// エラー処理
$error = $e->getMessage();
}
}
function validateCsrfToken($token) {
return hash_equals($_SESSION['csrf_token'], $token);
}
indexとsuccessにsession追加
proccessでエラーになったときの戻り先がindexです。
セッションエラーを記述できるようにコードを追加します。
<?php
//session start
session_start();
//token create
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$csrf_token = $_SESSION['csrf_token'];
// エラーメッセージを取得して、セッションから削除
$error = $_SESSION['error'] ?? null;
$success = $_SESSION['success'] ?? null;
unset($_SESSION['error']);
unset($_SESSION['success']);
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Upload</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 min-h-screen flex items-center justify-center">
<div class="w-full max-w-md">
<?php if ($error): ?>
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-4" role="alert">
<span class="block sm:inline"><?= htmlspecialchars($error) ?></span>
</div>
<?php endif; ?>
<?php if ($success): ?>
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative mb-4" role="alert">
<span class="block sm:inline"><?= htmlspecialchars($success) ?></span>
</div>
<?php endif; ?>
success.phpはSUCCESSメッセージを受け取れるように追加します。
session_start();
// セッションに成功メッセージがない場合はホームページにリダイレクト
if (!isset($_SESSION['success_message'])) {
header('Location: index.php');
exit();
}
// 成功メッセージを取得して、セッションから削除
$success_message = $_SESSION['success_message'];
unset($_SESSION['success_message']);
?>
<!DOCTYPE html>
...略
できあがり
こんな感じになります。
あとはご自身の環境に合わせて、ImageMagicで適時ファイルの整形をしたりすると良いかと思います。
完成形のファイルは、Githubに上げておきますので必要であればDLどうぞ。
Discussion