🦓

Laravel Breezeに画像のアップロード機能を追加する

2023/10/30に公開

はじめに

Laravel Breezeにユーザーのプロフィール画像のアップロード機能を追加していきます。
ユーザー認証機能はすでに実装しています。

https://laravel.com/docs/10.x/filesystem

環境

PHP 8.x
Laravel 10.x
MySQL 8.x

tl;dr

  1. プロフィール画像用カラムを追加する
  2. ビューを作成する
  3. $fillableプロパティにimageカラムを追加する
  4. ProfileUpdateRequestにバリデーションを追加する
  5. ProfileControllerのupdateアクションを編集する
  6. 画像を表示する

画像用カラムを追加する

ユーザーテーブルにプロフィール画像用カラムを追加します。

➜   php artisan make:migration add_image_to_users

   INFO  Migration [database/migrations/2023_10_30_141136_add_image_to_users.php] created successfully.  
database/migrations/2023_10_30_141136_add_image_to_users.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('image')->nullable();
        });
    }
};
➜   php artisan migrate

   INFO  Running migrations.  

  2023_10_30_141136_add_image_to_users ............................ 559ms DONE

アップロード用ビューを作成する

➜   php artisan make:component user-image --view

画像をアップロードする用ビューを作成します。
alpineJSを使ってアップロードされた画像のプレビューができるようにします。
また、画像がある場合画像を表示し、ない場合デフォルトのno-image.pngを表示するように設定しました。

resources/components/user-image.blade.php
<div x-data="imagePreview()">
    <input @change="showPreview(event)" type="file" id="image" name="image">
    <img src="{{ isset(Auth::user()->image) ? asset('storage/' . Auth::user()->image) : asset('images/no-image.png') }}" alt="" id="preview">

    <script>
        function imagePreview(){
            return {
                showPreview: (event) => {
                    if (event.target.files.length > 0){
                        var src = URL.createObjectURL(event.target.files[0]);
                        document.getElementById('preview').src = src;
                    }
                }
            }
        }
    </script>
</div>

コンポーネントを読み込みます。

resources/views/profile/partials/update-profile-information-blade.php
<div>
     <x-input-label for="image" :value="__('Image')" />
     <x-user-image />
     <x-input-error class="mt-2" :messages="$errors->get('image')" />
</div>

画像をアップロードするために<form>タグに enctype="multipart/form-data" を追加します!

<form method="post" action="{{ route('profile.update') }}" 
+     enctype="multipart/form-data" >

この部分がないと画像をアップロードできないので忘れないでくださいー

$fillableプロパティにimageを追加する

マスアサインメントの対策で$fillableプロパティにimageを追加します。

app/models/user.php
<?php

namespace App\Models;
...

class User extends Authenticatable
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
+       'image',
    ];
}

バリデーションを追加する

ProfileUpdateRequest.phpにプロフィール画像のバリデーションを追加します。

app/http/requests/ProfileUpdateRequest.php
<?php

...

class ProfileUpdateRequest extends FormRequest
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
     */
    public function rules(): array
    {
        return [
            'name' => ['string', 'max:255'],
            'email' => ['email', 'max:255', Rule::unique(User::class)->ignore($this->user()->id)],
+           'image' => ['file', 'mimes:gif,png,jpg,webp', 'max:1024'],
        ];
    }
}

ProfileControllerのupdateアクションを編集する

画像を保存するパスを指定します。

app/http/controller/ProfileController.php
<?php

namespace App\Http\Controllers;

...

class ProfileController extends Controller
{
...
    /**
     * Update the user's profile information.
     */
    public function update(ProfileUpdateRequest $request): RedirectResponse
    {
        $request->user()->fill($request->safe()->only(['name', 'email']));

        // 画像があれば/uploadsに保存する、なければnull
        $path = null;
        if ($request->hasFile('image')) {
            $path = $request->file('image')->store('uploads', 'public');
            $request->user()->image = $path;
        }

        $request->user()->save();

        return Redirect::route('profile.edit')->with('status', 'profile-updated');
    }

...
}

https://laravel.com/docs/10.x/filesystem#file-uploads

画像のアップロード機能ができました。
public/uploadsにアップロードされたことを確認します。

➜   ls -n storage/app/public/uploads
total 80
-rw-r--r--  1 501  20  40371 Oct 30 19:30 zxIGDT4sUjV8cwvciANZCQQ7sKLLLAaVeQVwXwVg.png

また、指定されたファイルタイプ以外のファイルをアップロードするとバリデーションエラーが表示されます。

新しい画像をアップロードされた場合、古い画像を削除する処理も追加していきます。

<?php

namespace App\Http\Controllers;

// Storageファサードをインポートする
use Illuminate\Support\Facades\Storage;

class ProfileController extends Controller
{

    /**
     * Update the user's profile information.
     */
    public function update(ProfileUpdateRequest $request): RedirectResponse
    {

        if($currentImage = $request->user()->image){
            Storage::disk('public')->delete($currentImage);
        }
    }

}

https://laravel.com/docs/10.x/filesystem#deleting-files

画像を表示する

➜   php artisan storage:link

   INFO  The [public/storage] link has been connected to [storage/app/public].  

Laravelのアプリでphp artisan storage:linkコマンドを実行すると、通常、アプリケーションのパブリックディレクトリにあるファイルへのアクセスを簡単に設定するために使用されるシンボリックリンクが作成されます。
このようなリンクを作成することで、Webブラウザなどからアプリケーションのアップロードされたファイルにアクセスできるようになります。

public/storageというディレクトリへのシンボリックリンク(シンボリックリンクはファイルシステム内の別の場所への参照のようなもので、リアルなデータは別の場所に格納されています)が、storage/app/publicというディレクトリに接続されました。

下記のコマンドで確認できます。

➜  ls -la public

リンクの参照する場所を変えたい場合、config/filesystems.phpを編集することができます。

config/filesystems.php
<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Symbolic Links
    |--------------------------------------------------------------------------
    |
    | Here you may configure the symbolic links that will be created when the
    | `storage:link` Artisan command is executed. The array keys should be
    | the locations of the links and the values should be their targets.
    |
    */

    'links' => [
        public_path('storage') => storage_path('app/public'),
    ],

];

ナビゲーションにユーザーのプロフィール画像を表示させます。

resources/views/layouts/navigation.php
<img src="{{ isset(Auth::user()->image) ? asset('storage/' . Auth::user()->image) : asset('images/no-image.png') }}">

終わりに

Laravel Breezeにユーザーのプロフィール画像をアップロードできるようになりましたー

Discussion