Laravel×Inertia×Vue3でファイルアップロード機能を作ってみた
5月も後半となり初夏の訪れを感じる時期となりましたが、みなさまはいかがお過ごしでしょうか?
私は、相変わらずバックエンドとフロントエンドを行き来するせわしない日々を送っています。
今回は、Laravel×Inertia×Vue3でファイルアップロードの機能を作ってみます。
前提
- ubuntu v22.04
- PHP v8.3
- Laravel v10.48
- Vite v5.1
- Vue v3.4
- Inertia v1.0
準備
こちらの記事での開発環境を用意した上で進んでください。
現状は以下の通りです。
routes
# /routes/web.php
// ...省略...
Route::resource('news', NewsController::class)
->except(['store', 'update']);
Route::post('news/create', [NewsController::class, 'store'])
->name('news.store');
Route::put('news/{news}/edit', [NewsController::class, 'update'])
->name('news.update');
コンポーネント
<!-- resources/js/Pages/News/Create.vue -->
<script setup>
import { useForm } from '@inertiajs/vue3'
const form = useForm({
title: null,
body: null,
})
const submit = () => {
form.post(route('news.store'));
}
</script>
<template>
<form @submit.prevent="submit">
<table>
<tr>
<th><label for="title">title:</label></th>
<td>
<div v-if="form.errors.title">{{ form.errors.title }}</div>
<input id="title" type="text" v-model="form.title">
</td>
</tr>
<tr>
<th><label for="body">body:</label></th>
<td>
<div v-if="form.errors.body">{{ form.errors.body }}</div>
<textarea id="body" cols="22" rows="20" v-model="form.body"></textarea>
</td>
</tr>
<tr>
<th></th>
<td>
<button type="submit" :disabled="form.processing">登録</button>
</td>
</tr>
</table>
</form>
</template>
<!-- resources/js/Pages/News/Edit.vue -->
<script setup>
import { useForm } from '@inertiajs/vue3'
const props = defineProps({
news: Object,
})
const form = useForm({
title: props.news.title,
body: props.news.body,
})
const submit = () => {
form.put(route('news.update', props.news.id))
}
</script>
<template>
<form @submit.prevent="submit">
<table>
<tr>
<th><label for="title">title:</label></th>
<td>
<div v-if="form.errors.title">{{ form.errors.title }}</div>
<input id="title" type="text" v-model="form.title">
</td>
</tr>
<tr>
<th><label for="body">body:</label></th>
<td>
<div v-if="form.errors.body">{{ form.errors.body }}</div>
<textarea id="body" cols="22" rows="20" v-model="form.body"></textarea>
</td>
</tr>
<tr>
<th></th>
<td>
<button type="submit" :disabled="form.processing">更新</button>
</td>
</tr>
</table>
</form>
</template>
Controller
# app/Http/Controllers/NewsController.php
class NewsController extends Controller
{
//...省略...
public function create()
{
return Inertia::render('News/Create', []);
}
public function store(Request $request)
{
$validated = $request->validate([
'title' => ['required'],
'body' => ['required'],
]);
News::create($validated);
return redirect()->route('news.index');
}
public function edit(News $news)
{
return Inertia::render('News/Edit', [
'news' => $news,
]);
}
public function update(Request $request, News $news)
{
$validated = $request->validate([
'title' =>'required',
'body' => 'required',
]);
$news->update($validated);
return redirect()->route('news.index');
}
//...省略...
実装
では実際にファイルアップロードの機能を実装していきます。
dbの対象tableの修正
image
カラムを追加します。
sail php artisan make:migration add_image_to_news_table --table=news
# database/migrations/YYYY_mm_dd_xxxxxx_add_image_to_news_table.php
//...省略...
public function up(): void
{
Schema::table('news', function (Blueprint $table) {
$table->string('image')->nullable(); //追加
});
}
public function down(): void
{
Schema::table('news', function (Blueprint $table) {
$table->dropColumn('image'); //追加
});
}
# app/Models/News.php
class News extends Model
{
use HasFactory;
protected $fillable = [
'title',
'body',
'image', //追加
];
}
sail php artisan migrate
Create/Store
コンポーネント、Controllerの順で修正していきます。
コンポーネントの修正
<!-- resources/js/Pages/News/Create.vue -->
<script setup>
import { useForm } from '@inertiajs/vue3';
const form = useForm({
title: null,
body: null,
image: null, //追加
})
const submit = () => {
form.post(route('news.store'));
}
</script>
<template>
<form @submit.prevent="submit">
<!-- ...省略... -->
<!-- 以下追加 -->
<tr>
<th><label for="image">image:</label></th>
<td>
<input type="file" id="image" @input="form.image = $event.target.files[0]">
</td>
</tr>
<!-- ...省略... -->
</table>
</form>
</template>
@input=""
のイベントリスナーで選択されたファイルを$event.target.files[0]
を通して取得しform.image
に格納しています。
Controllerの修正
# app/Http/Controllers/NewsController.php
class NewsController extends Controller
{
// ...省略...
public function create()
{
return Inertia::render('News/Create', []);
}
public function store(Request $request)
{
$validated = $request->validate([
'title' => ['required'],
'body' => ['required'],
]);
// 以下追加
if ($image = $request->file('image')) {
$validated['image'] = $image->store('upload');
}
News::create($validated);
return redirect()->route('news.index');
}
$request->file()
でファイルを取得し、$image->store()
でファイルを保存しています。また、ファイルパスをdbに登録したいので、$validated['image']
に$image->store()
の返り値を格納しています。
今回は割愛しますが、以下公式にもある通りdbに保存するファイルパスのサニタイズはしましょう。
Unprintable and invalid unicode characters will automatically be removed from file paths. Therefore, you may wish to sanitize your file paths before passing them to Laravel’s file storage methods. File paths are normalized using the
League\Flysystem\WhitespacePathNormalizer::normalizePath
method.
Edit/Update
編集画面で登録済みの画像を表示させたいので、まずは画像を読み込めるようにします。その後create/storeの工程と同じくコンポーネント、Controllerの順で修正していきます。
cd public
ln -s ../storage/app/upload
アップロードした画像を編集画面でアクセスできるように公開します。画像の格納先の storage/app/upload
からpublic/
にシンボリックリンクを貼ります。
コンポーネントの修正
<!-- resources/js/Pages/News/Edit.vue -->
<script setup>
import { router, useForm } from '@inertiajs/vue3' //修正
const props = defineProps({
news: Object,
})
const form = useForm({
title: props.news.title,
body: props.news.body,
image: null, //追加
registerd_image: props.news.image //追加
})
const submit = () => {
//以下修正
router.post(route('news.update', props.news.id), {
_method: 'put',
title: form.title,
body: form.body,
image: form.image,
registerd_image: form.registerd_image,
});
}
</script>
<template>
<form @submit.prevent="submit">
<!-- ...省略... -->
<!-- 以下追加 -->
<tr>
<th><label for="image">image:</label></th>
<td>
<img :src="`/${form.registerd_image}`" alt="">
</td>
<td>
<input type="file" id="image" @input="form.image = $event.target.files[0]">
</td>
</tr>
<!-- ...省略... -->
</form>
</template>
以前の記事ではform.put()
でPUTメソッドを使ってリクエストを送信していました。 しかし、今回の記事では router.post(<route>, { _method: 'put' })
でPOSTメソッドを使ってリクエストを送信しています。
この理由は、PUTメソッドを使用した場合、multipart/form-data
リクエストによるファイルアップロードが直接サポートされていないためです。それ故、POSTメソッドを使って送信し、_method属性
で本来のメソッドであるPUTメソッドを指定しています。
公式にも明記されています。
## Multipart limitations
Uploading files using a
multipart/form-data
request is not natively supported in some server-side frameworks when using thePUT
,PATCH,
orDELETE
HTTP methods. The simplest workaround for this limitation is to simply upload files using aPOST
request instead.However, some frameworks, such as Laravel and Rails, support form method spoofing, which allows you to upload the files using
POST
, but have the framework handle the request as aPUT
orPATCH
request. This is done by including a_method
attribute in the data of your request.
Controllerの修正
# app/Http/Controllers/NewsController.php
class NewsController extends Controller
{
// ...省略...
public function edit(News $news)
{
return Inertia::render('News/Edit', [
'news' => $news,
]);
}
public function update(Request $request, News $news)
{
$validated = $request->validate([
'title' =>'required',
'body' => 'required',
'registerd_image' => 'nullable'
]);
// 以下追加
if ($image = $request->file('image')) {
$validated['image'] = $image->store('upload');
if (!empty($validated['registerd_image'])) {
unlink(storage_path('app/'). $validated['registerd_image']);
}
}
unset($validated['registerd_image']);
$news->update($validated);
return redirect()->route('news.index');
}
新しい画像がアップロードされていたら、新しい画像を登録しながら、登録済みの旧画像のdbのデータと実ファイルを削除します。
また、dbへの登録にregisterd_image
カラムは不要なのでunset
してます。
おわりに
いかがだったでしょうか?
簡単にですが、InertiaとLaravelを使ったファイルアップロードの機能が実装できたと思います。
画像のバリデーションやリサイズなど他にも画像関連でやることはたくさんあると思いますので、順に確認していければと思います。
では、また。
この記事を書いた人
-
大学4年時春に文系就職を辞め、エンジニアになることを決意し、独学でRuby、Ruby on Railsを学習。
約1年間の独学期間を経てアーティスへWebエンジニアとして入社。現在はWebエンジニアとして、主にシステムの開発・運用に従事している。
抽象的なもの、複雑なものを言語化して文章にするのが好きで得意。
この執筆者の最新記事
- 2024年10月8日WEBVue3でjQueryのdatepickerを使いたい!実装手順と注意点を解説します。
- 2024年8月21日WEBVue3の非同期コンポーネントを使ってみる
- 2024年5月28日WEBLaravel×Inertia×Vue3でファイルアップロード機能を作ってみた
- 2024年4月15日WEBLaravel×Inertia×Vue3でCRUD機能を持つSPAを作ってみた
関連記事
最新記事
FOLLOW US
最新の情報をお届けします
- facebookでフォロー
- Twitterでフォロー
- Feedlyでフォロー