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を使ったファイルアップロードの機能が実装できたと思います。
画像のバリデーションやリサイズなど他にも画像関連でやることはたくさんあると思いますので、順に確認していければと思います。
では、また。
この記事を書いた人

- 創造性を最大限に発揮するとともに、インターネットに代表されるITを活用し、みんなの生活が便利で、豊かで、楽しいものになるようなサービスやコンテンツを考え、創り出し提供しています。
この執筆者の最新記事
- 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でフォロー
- Xでフォロー
- Feedlyでフォロー