先日投稿したCRUDアプリに画像ファイルを1枚追加できる機能を実装する方法のメモ。
DBに画像ファイル用のカラム追加
マイグレーションファイル作成
sail artisan make:migration add_image_to_posts_table --table=posts
INFO Migration [database/migrations/2023_01_24_135530_add_image_to_posts_table.php] created successfully.
マイグレーションファイル編集
upメソッド編集
カラム追加
# /database/migrations/2023_01_24_135530_add_image_to_posts_table.php
// カラム追加(descriptionの後に追加、NULL許可)
$table->text('image')->after('description')->nullable();
編集後のupメソッド
# /database/migrations/2023_01_24_135530_add_image_to_posts_table.php
public function up()
{
Schema::table('posts', function (Blueprint $table) {
// カラム追加(descriptionの後に追加、NULL許可)
$table->text('image')->after('description')->nullable();
// カラム追加(テーブルの最後に追加)
// $table->text('image');
// カラム追加(指定したカラムの後に追加)
// $table->text('image')->after('description');
// NULL許可
// $table->string('image')->nullable();
});
}
downメソッド編集
カラム削除
# /database/migrations/2023_01_24_135530_add_image_to_posts_table.php
// カラム削除
$table->dropColumn('image');
編集後のdownメソッド
# /database/migrations/2023_01_24_135530_add_image_to_posts_table.php
public function down()
{
Schema::table('posts', function (Blueprint $table) {
// カラム削除
$table->dropColumn('image');
});
}
マイグレーション実行
sail artisan migrate
INFO Running migrations. 2023_01_24_135530_add_image_to_posts_table ............... 49ms DONE
ロールバック(*必要な場合のみ)
sail artisan migrate:rollback
INFO Rolling back migrations. 2023_01_24_135530_add_image_to_posts_table ............... 43ms DONE
シンボリックリンク作成
フォームから登録した画像ファイルが参照できるようシンボリックリンクを作成します。
sail artisan storage:link
INFO The [public/storage] link has been connected to [storage/app/public].
Postモデル編集
fillable設定
# /app/Models/Post.php
// fillable に image を追加
protected $fillable = ['title', 'description', 'image'];
PostRequest編集
rulesメソッド編集
バリデーションルール追加
# /app/Http/Requests/PostRequest.php
// 画像ファイル用バリデーションルールを追加する
'image' => ['nullable', 'max:1024', 'mimes:jpg,jpeg,png,gif'], // 画像
編集後のrulesメソッド
# /app/Http/Requests/PostRequest.php
public function rules()
{
return [
'title' => ['required', 'max:80'], // タイトル
'description' => ['required'], // 本文
'image' => ['nullable', 'max:1024', 'mimes:jpg,jpeg,png,gif'], // 画像
];
}
PostController編集
画像保存処理 store()
画像取得
# /app/Http/Controllers/PostController.php
// 画像取得
$image_path = $request->file('image')->store('image', 'public');
$data['image'] = $image_path;
編集後のstoreメソッド
# /app/Http/Controllers/PostController.php
public function store(PostRequest $request)
{
// 保存用データ配列
$data = $request->validated();
// 画像取得
$image_path = '';
if ($request->hasFile('image')) {
$image_path = $request->file('image')->store('image', 'public');
$data['image'] = $image_path;
}
// 保存
Post::create($data);
return redirect()->route('posts.index')->with('message', '投稿の作成が完了しました。');
}
画像更新処理 update()
use宣言
# /app/Http/Controllers/PostController.php
// use宣言(Storageクラス利用)
use Illuminate\Support\Facades\Storage;
既存画像削除と新しい画像取得
# /app/Http/Controllers/PostController.php
// 現在の画像ファイル削除
if ($iamge_cur !== '' && !is_null($iamge_cur)) {
Storage::disk('public')->delete($iamge_cur);
}
// 選択画像ファイルを保存してパスをセット
$image_path = $request->file('image')->store('image', 'public');
$data['image'] = $image_path;
編集後のupdateメソッド
# /app/Http/Controllers/PostController.php
public function update(PostRequest $request, Post $post)
{
//
$image_path = ''; // 選択された画像
$image_cur = $post->image; // 現在の画像ファイルパス
// 保存用データ配列
$data = [
'title' => $request->title,
'description' => $request->description,
];
//
if ($request->hasFile('image')) {
// 現在の画像ファイル削除
if ($image_cur !== '' && !is_null($image_cur)) {
Storage::disk('public')->delete($image_cur);
}
// 選択画像ファイルを保存してパスをセット
$image_path = $request->file('image')->store('image', 'public');
$data['image'] = $image_path;
}
// 更新
$post->update($data);
return redirect()->route('posts.index')->with('message', '投稿の更新が完了しました。');
}
画像削除処理 destroy()
画像削除
# /app/Http/Controllers/PostController.php
// 画像ファイルパスを取得
$image_cur = $post->image;
// 登録されていれば削除
if ($image_cur !== '' && !is_null($image_cur)) {
Storage::disk('public')->delete($image_cur);
}
編集後のdestroyメソッド
# /app/Http/Controllers/PostController.php
public function destroy(Post $post)
{
// 画像ファイルパスを取得
$image_cur = $post->image;
// 登録されていれば削除
if ($image_cur !== '' && !is_null($image_cur)) {
Storage::disk('public')->delete($image_cur);
}
// 削除
$post->delete();
return redirect()->route('posts.index')->with('message', '投稿の削除が完了しました。');
}
Bladeテンプレート編集
create.blade.php
<x-app>
<form method="POST" action="{{ route('posts.index') }}" enctype="multipart/form-data">
@csrf
<div>
<div>
<label>タイトル</label>
</div>
<div>
<input type="text" name="title" value="{{old('title')}}">
</div>
@error('title')
<span>{{ $message }}</span>
@enderror
</div>
<div>
<div>
<label>本文</label>
</div>
<div>
<textarea name="description" rows="4">{{old('description')}}</textarea>
</div>
@error('description')
<span>{{ $message }}</span>
@enderror
</div>
<div>
<div><label>画像</label></div>
<div>
<input type="file" name="image">
</div>
@error('image')
<span>{{ $message }}</span>
@enderror
</div>
<button type="submit">保存</button>
</form>
</x-app>
edit.blade.php
<x-app>
<form method="POST" action="{{ route('posts.update',$post->id) }}" enctype="multipart/form-data">
@csrf
@method('PUT')
<div>
<div>
<label>タイトル</label>
</div>
<div>
<input type="text" name="title" value="{{old('title',$post->title)}}">
</div>
@error('title')
<span>{{ $message }}</span>
@enderror
</div>
<div>
<div>
<label>本文</label>
</div>
<div>
<textarea name="description" rows="4">{{old('description',$post->description)}}</textarea>
</div>
@error('description')
<span>{{ $message }}</span>
@enderror
</div>
<div>
<div><label>画像</label></div>
<div>
<div>
@if ($post->image !=='')
<img src="{{ Storage::url($post->image) }}">
@else
@endif
</div>
<div>
<input type="file" name="image">
</div>
@error('image')
<span>{{ $message }}</span>
@enderror
</div>
<button type="submit">更新</button>
</form>
</x-app>
show.blade.php
<x-app>
<div>
<label>タイトル</label>
<p>{{old('title',$post->title)}}</p>
</div>
<div>
<label>本文</label>
<p>{{old('description',$post->description)}}</p>
</div>
<div>
<label>画像</label>
@if ($post->image !=='')
<div>
<img src="{{ Storage::url($post->image) }}">
</div>
@else
@endif
</div>
<button onclick="location.href='{{ route('posts.index') }}'">戻る</button>
</x-app>
index.blade.php
<x-app>
@if (session()->has('message'))
<div>{{ session('message') }}</div>
@endif
<div>
<button onclick="location.href='{{ route('posts.create') }}'" >新規作成</button>
</div>
<table>
<thead>
<tr>
<th>ID</th>
<th>タイトル</th>
<th>本文</th>
<th>画像</th>
<th>作成日時</th>
<th>更新日時</th>
<th colspan="2">編集・削除</th>
</tr>
</thead>
<tbody>
@foreach ($posts as $post)
<tr>
<td>{{ $post->id }}</td>
<td><a href="{{ route('posts.show', $post->id) }}">{{ $post->title }}</a></td>
<td>{!! nl2br( htmlspecialchars($post->description) ) !!}</td>
<td>@if ($post->image !=='')<img src="{{ Storage::url($post->image) }}" width="50%">@else @endif</td>
<td>{{ $post->created_at }}</td>
<td>{{ $post->updated_at }}</td>
<td>
<button onclick="location.href='{{ route('posts.edit', $post->id) }}'" >編集</button>
</td>
<td>
<form action="{{ route('posts.destroy',$post->id) }}" method="POST" onsubmit="return confirm('削除してもよろしいですか?');">
<input type="hidden" name="_method" value="DELETE">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<button type="submit">削除</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</x-app>
動作環境情報
"macOS Ventura" 13.1 "Docker Desktop" 4.15.0 "Laravel Sail" "Laravel Framework" 9.48.0
コメント