内容
「写真を投稿する機能を作る」ということを題材にモデルの扱い方を説明してみます。
おしながきは以下の通りです。
- テーブルの作成
- モデルの作成
- コントローラーの作成
前提
- Laravel7
- マイグレーション済み(usersテーブル作成済み)
- public/storageからstorage/app/public/へシンボリックリンクを張っておき、picturesディレクトリを作っておく
テーブルの作成
まずは、モデルで扱うテーブルをデータベースに用意します。
% php artisan make:migration create-pictures-table
ファイル名からpicturesというテーブルを用意してくれますが、明示的に--tabelオプションで指定できます。
% php artisan make:migration create-pictures-table --table=pictures
作成されたファイルを編集します。
今回はidとtimestamps以外に、外部キーのuser_id、タイトル、写真の保存場所のパス、コメントをカラムとして作成します。
class CreatePicturesTable extends Migration
{
public function up()
{
Schema::create('pictures', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id');
$table->string('title', 20);
$table->string('path', 30)->unique();
$table->string('comment', 100)->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('pictures');
}
}
マイグレーションを忘れずに。
% php artisan migrate
モデルの作成
% php artisan make:model Picture
テーブル名が複数形に対し、モデル名は単数形にするという命名規則があります。
これは絶対というわけではなく、$tableをオーバーライドすることで結びつけるテーブルを指定することができます。
protected $table = 'pictures';
次に、$fillableとバリデーションルールの設定です。
fill()やupdate()など、複数代入する際に、ホワイトリストとして働く$fillableと、ブラックリストとして働く$guardedがあります。この二つのうち一方しか利用できません。
今回は$fillableを使います。
protected $fillable = [
'user_id', 'title', 'path', 'comment',
];
public static $rules = [
'title' => ['string', 'max:20'],
'picture' => ['image', 'max:2048'],
'title' => ['string', 'max:100'],
];
バリデーションでpathではなくpictureを設定しているのは、送られてくる画像ファイルをバリデーションするためです。保存する際は、pictureのファイルをは別で保存して、pathにコントローラーで作るファイル名を保存します。
コントローラーの作成
% php artisan make:controller PictureController --resource --model=Picture
index、show
public function index()
{
$pictures = Picture::all();
return view('picture.index', ['pictures' => $pictures]);
}
public function show($id)
{
$picture = Picture::find($id);
return view('picture.show', ['picture' => $picture]);
}
表示する画像の分を$pictureに格納してビューに渡しています。
create、store
public function create()
{
$user = Auth::user();
return view('picture.create', ['user' => $user]);
}
public function store(Request $request)
{
$this->validate($request, Picture::$rules);
$form = $request->all();
$pictureFile = $request->file('picture');
$extension = $pictureFile->getClientOriginalExtension();
do {
$file_name = Str::random(20).'.'.$extension;
} while (Picture::where('path', $file_name)->first() != null);
$this->savePicture($pictureFile, $file_name);
$form['path'] = $file_name;
unset($form['_token']);
unset($form['_method']);
unset($form['picture']);
$picture = Picture::create($form);
return redirect(route('picture.show', ['picture' => $picture->id]));
}
private function savePicture($image, $file_name) {
// get instance
$img = \Image::make($image);
// resize
$img->fit(512, null, function($constraint){
$constraint->upsize();
});
// save
$save_path = 'storage/pictures/'.$file_name;
$img->save($save_path);
}
POSTで渡されるデータは、_token、_method、user_id、title、picture(ファイル)、commentです。
バリデーション後、do~while分で重複しないファイル名を生成し、savePictureでpicture(ファイル)をstorage以下に保存しています。
createで複数の項目をセットするのに必要なpathをセットし、不必要な項目をunsetしています。
edit、update
public function edit($id)
{
$picture = Picture::find($id);
return view('picture.edit', ['picture' => $picture]);
}
public function update($id, Request $request)
{
$this->validate($request, [
'title' => ['required', 'string', 'max:20'],
'comment' => ['max:100'],
]);
$picture = Picture::find($id);
$form = $request->all();
unset($form['_token']);
unset($form['_method']);
$picture->fill($form)->save();
return redirect(route('picture.show', ['picture' => $picture->id]));
}
画像以外編集可能という仕様にしています。よって、updateのバリデーションは、titleとcommentだけにしています。他はstoreとほぼ一緒です。
destroy
public function destroy($id)
{
$picture = Picture::find($id);
if (Storage::exists('public/pictures/'.$picture->path)) {
Storage::delete('public/pictures/'.$picture->path);
}
$picture->delete();
return redirect('/home');
}
Storageを利用して、削除するpicture(ファイル)が存在しているか確認して、削除しています。
ダウンロード
アップロードを実装したらダウンロードも実装したくなりますよね。
ダウンロードはコントローラーで以下のように
return response()->download(パス);
と記述すればいいです。
これに対応するルーティングも忘れずにセットしてください。
public function download($id) {
$picture = Picture::find($id);
return response()->download('storage/pictures/'.$picture->path);
}
おわり
モデルの扱いを説明するつもりが、画像のアップロード・ダウンロードを説明している感じになってしまいましたね。savePicture()で画像を取得したりリサイズしたりするのにIntervention Imageを使いました。こちらと一緒に読んでみるといいかもしれません。