php5なuploaderをlaravelに移植してauroraで使うとか(3) - DBの読み書き含む一通りのapp実装
前回
storeメソッド
さて今こんな感じのダミーを作ってあった。このstoreは見ての通り何も保存せずに保存した事にして返している。
public function store(Request $request)
{
// dd($request->all());
return redirect(route('uploaders.index'))
->with(['status' => __('File uploaded')]);
}
つわけで「File uploaded」とか言い張っているが全く何もしていない。何かしていこう。
validationとfileの取得
public function store(Request $request)
{
$request->validate([
'file' => 'required|file',
]);
$file = $request->file('file');
dd($file);
exit;
validationはHttp Requestを自作してもいいけど、この程度の1フィールドで特殊なことをしないのであればこんなんで十分だろう。ただし、jpegとか画像に制限したりサイズがどうのこうのとかもっと特殊な事をしたいのであればForm Requestを作成する事をおすすめするぞい。
データーを整形する
$data = [
'original_name' => $originalName,
'saved_name' => $savedName,
'mime_type' => $mime,
'size' => $size,
];
こういったデーターを目指していく。取得した$file変数からある程度情報をgetできる(矢印をあわせてるのは趣味の領域なので別に合わせなくてもいいよ)。
でまあこんな感じとした
public function store(Request $request)
{
$request->validate([
'file' => 'required|file',
]);
$file = $request->file('file');
$originalName = $file->getClientOriginalName();
$mime = $file->getMimeType();
$size = $file->getSize();
$savedName = $savedName = \Str::random(10).md5($originalName);
$data = [
'original_name' => $originalName,
'saved_name' => $savedName,
'mime_type' => $mime,
'size' => $size,
];
dd($data);
このsaved_nameはとりあえず被らない文字列なら何だっていいんだけど、まあこんなのが被らないだろって感じのハッシュにしておいた。とりあえずこうなっている
array:4 [▼ // app/Http/Controllers/UploaderController.php:38
"original_name" => "
DALL·E 2024-02-18 12.11.47 - An anime girl character personifying the AWS Aurora service, represented by the instance type t4g.medium. She has been operational
▶
"
"saved_name" => "PJPemOKSr0399d9ffe238fdb2327de5f98c0998213"
"mime_type" => "image/webp"
"size" => 387050
]
これを保存していく。
保存と取り出し
public function store(Request $request)
{
$request->validate([
'file' => 'required|file',
]);
$file = $request->file('file');
$originalName = $file->getClientOriginalName();
$mime = $file->getMimeType();
$size = $file->getSize();
$savedName = $savedName = \Str::random(10).md5($originalName);
$data = [
'original_name' => $originalName,
'saved_name' => $savedName,
'mime_type' => $mime,
'size' => $size,
];
\DB::beginTransaction();
$uploadedFile = UploadedFile::create($data);
dd($uploadedFile);
exit;
このように保存しにいく。そうする
など言われるので、モデルを変更していく。
app/Models/UploadedFile.php
class UploadedFile extends Model
{
use HasFactory;
protected $fillable = [
'original_name',
'saved_name',
'mime_type',
'size',
];
}
みたいな$fillableを定義しておく。これでinsertできるはずだ
ここでは以前のphp5で作ったold style uploaderに仕様を合わせているので、これに基いて再度ファイル名を変更し、保存からのupdateを行う。
public function store(Request $request)
{
$request->validate([
'file' => 'required|file',
]);
$file = $request->file('file');
$originalName = $file->getClientOriginalName();
$mime = $file->getMimeType();
$size = $file->getSize();
$savedName = $savedName = \Str::random(10).md5($originalName);
$data = [
'original_name' => $originalName,
'saved_name' => $savedName,
'mime_type' => $mime,
'size' => $size,
];
\DB::beginTransaction();
$uploadedFile = UploadedFile::create($data);
$extension = $file->getClientOriginalExtension();
$savedName = sprintf('%05d.%s', $uploadedFile->id, $extension);
$path = $file->storeAs('uploaded_files', $savedName, 'public');
// saved_nameを更新
$uploadedFile->update(['saved_name' => $savedName]);
\DB::commit();
// dd($request->all());
return redirect(route('uploaders.index'))
->with(['status' => __('File uploaded')]);
}
こんな感じの奴が最終的なコードとなる
保存されたか確認
tinkerで掘っといてみよう。
artisan tinker
あるいは
% ./vendor/bin/sail tinker
Psy Shell v0.12.0 (PHP 8.3.3-1+ubuntu22.04.1+deb.sury.org+1 — cli) by Justin Hileman
>
みたいなPsy Shellを起動して確認する、なお
Psy Shell v0.12.0 (PHP 8.3.3-1+ubuntu22.04.1+deb.sury.org+1 — cli) by Justin Hileman
> UploadedFile::all()
Error Class "UploadedFile" not found.
とかいわれた場合はdump-autoloadする
% sudo ./vendor/bin/sail composer dump-autoload
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
INFO Discovering packages.
laravel/breeze ............................................................... DONE
laravel/sail ................................................................. DONE
laravel/sanctum .............................................................. DONE
laravel/tinker ............................................................... DONE
nesbot/carbon ................................................................ DONE
nunomaduro/collision ......................................................... DONE
nunomaduro/termwind .......................................................... DONE
spatie/laravel-ignition ...................................................... DONE
Generated optimized autoload files containing 6031 classes
再実行すると
> UploadedFile::all()
[!] Aliasing 'UploadedFile' to 'App\Models\UploadedFile' for this Tinker session.
= Illuminate\Database\Eloquent\Collection {#5984
all: [
App\Models\UploadedFile {#5986
id: 9,
original_name: "DALL·E 2024-02-18 12.11.47 - An anime girl character personifying the AWS Aurora service, represented by the instance type t4g.medium. She has been operational for several years, .webp",
saved_name: "00009.webp",
mime_type: "image/webp",
size: 387050,
created_at: "2024-02-18 18:22:34",
updated_at: "2024-02-18 18:22:34",
},
],
}
こんなんなってDBに入ってる事が確認できればok。さらにファイルシステムの確認として、
% find storage/app/public
storage/app/public
storage/app/public/.gitignore
storage/app/public/uploaded_files
storage/app/public/uploaded_files/00009.webp
こんなんが入ってればok
取り出し
indexでこのデーターを取ってくる。今回は雑に全部取る(前の奴の仕様がそうだったので)。とはいえall()を使うのもアレなのでlatest()で取る
public function index()
{
$uploadedFiles = UploadedFile::latest()->get();
return view('uploaders.index', ['uploadedFiles' => $uploadedFiles]);
}
viewの更新
<x-uploader-layout>
<div class="max-w-2xl mx-auto py-10 px-6 bg-white rounded-lg shadow-md">
<h1 class="text-2xl font-semibold text-gray-700 mb-5">
<a href="{{ route('uploaders.index') }}" class="hover:underline">Simple Uploader</a>
</h1>
<x-auth-session-status class="mb-4" :status="session('status')" />
<form method="POST" action="{{ route('uploaders.store') }}" enctype="multipart/form-data" class="space-y-6">
@csrf
<div>
<x-input-label for="file" :value="__('File')" class="block text-sm font-medium text-gray-700" />
<div class="mt-1 flex items-center">
<input type="file" id="file" class="block w-full text-sm text-gray-500
file:mr-4 file:py-2 file:px-4
file:rounded-full file:border-0
file:text-sm file:font-semibold
file:bg-violet-50 file:text-violet-700
hover:file:bg-violet-100" name="file" required autofocus />
</div>
<x-input-error :messages="$errors->get('file')" class="mt-2" />
</div>
<div class="flex items-center justify-end mt-4">
<x-primary-button>
{{ __('Upload') }}
</x-primary-button>
</div>
</form>
<div class="mb-8">
<h2 class="text-xl font-semibold text-gray-600 mb-3">Uploaded Files</h2>
<div class="overflow-x-auto">
<table class="min-w-full leading-normal">
<thead>
<tr>
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
{{ __('File Name') }}
</th>
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
{{ __('Size') }}
</th>
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100"></th>
</tr>
</thead>
<tbody>
@forelse($uploadedFiles as $file)
<tr>
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
<div class="flex items-center">
<div class="ml-3">
<p class="text-gray-900 whitespace-no-wrap">
<a href="{{ Storage::url('uploaded_files/'. $file->saved_name) }}" class="text-blue-600 hover:text-blue-900">{{ $file->saved_name }}</a>
</p>
</div>
</div>
</td>
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
<p class="text-gray-900 whitespace-no-wrap">
{{ $file->size }}
</p>
</td>
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm text-right">
<a>{{ __('Delete') }}</a>
</td>
</tr>
@empty
<tr>
<td colspan="3" class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
<p class="text-gray-900 whitespace-no-wrap text-center">
{{ __('No files uploaded yet.') }}
</p>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</x-uploader-layout>
とまあこんな感じとなるわけであるがDeleteは未実装であーる、し、リンクも機能してはいない。
シンボリックリンクの作成
まあ今回の件において、これはもうこれでいいだろ
php artisan storage:link
以下はsailでの実行例
% ./vendor/bin/sail artisan storage:link
INFO The [public/storage] link has been connected to [storage/app/public].
これでファイルにアクセスできるはず。
削除ボタン
既存のdanger buttonコンポを使った方が早いやな
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm text-right">
<form method="POST" action="{{ route('uploaders.destroy', $file->id) }}" onsubmit="return confirm('{{ __('Are you sure you want to delete this file?') }}')">
@csrf
@method('DELETE')
<x-danger-button>{{ __('Delete') }}</x-danger-button>
</form>
</td>
削除の実装
public function destroy(UploadedFile $uploader)
{
\Storage::delete('uploaded_files/' . $uploader->saved_name);
$uploader->delete();
return redirect()->route('uploaders.index')
->with('status', __('File deleted successfully.'));
}
とまあこんな感じで一通りの機能が移植出来たんじゃないかな。ところで最近laravelでテストの記事をよく目にするんで、その辺についても今回は書いてみようかな。