0
1

More than 3 years have passed since last update.

【Laravel】モデルを扱う -写真投稿-

Posted at

内容

「写真を投稿する機能を作る」ということを題材にモデルの扱い方を説明してみます。
おしながきは以下の通りです。

  • テーブルの作成
  • モデルの作成
  • コントローラーの作成

前提

  • 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、タイトル、写真の保存場所のパス、コメントをカラムとして作成します。

database/migrations/20XX_00_00_000000_create-pictures-table.php
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をオーバーライドすることで結びつけるテーブルを指定することができます。

app/Picture.php
    protected $table = 'pictures';

次に、\$fillableとバリデーションルールの設定です。
fill()やupdate()など、複数代入する際に、ホワイトリストとして働く\$fillableと、ブラックリストとして働く\$guardedがあります。この二つのうち一方しか利用できません。
今回は$fillableを使います。

app/Picture.php
    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

app/Http/Controllers/PictureController.php
    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

app/Http/Controllers/PictureController.php
    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

app/Http/Controllers/PictureController.php
    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

app/Http/Controllers/PictureController.php
    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(パス);
と記述すればいいです。
これに対応するルーティングも忘れずにセットしてください。

app/Http/Controllers/PictureController.php
    public function download($id) {
        $picture = Picture::find($id);
        return response()->download('storage/pictures/'.$picture->path);
    }

おわり

モデルの扱いを説明するつもりが、画像のアップロード・ダウンロードを説明している感じになってしまいましたね。savePicture()で画像を取得したりリサイズしたりするのにIntervention Imageを使いました。こちらと一緒に読んでみるといいかもしれません。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1