【Laravel】Laravel Breeze で「プロフィールアイコン画像」を追加する方法

## Laravel Breeze に「プロフィールアイコン画像」機能を追加する

本来、プロフィールアイコンの機能を使用する場合は Laravel の別の認証パッケージである「Laravel Jetstream」を使用すれば良いのですが、そこまで多機能な認証機能を使用しない場合、Breeze の方でも意外と簡単に実装できたので備忘録として記事にしたいと思います。

**完成イメージ**
![Screenshot 2023-01-23 at 20-03-15 Laravel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106716/6b944107-38b9-4fc8-5845-3cedb1bab1e2.png)

## プロフィール画像を追加する手順

### Step 1, Laravel & Breeze のインストール

まずは 素のLaravel と Laravel Breeze をインストールするところから。Laravel をインストールするフォルダに移動して Laravel をインストールします（今回は Laravel のインストーラでインストールしています）。

```
laravel new project_name
```

インストールした Laravel のプロジェクトルートに移動します。 

```
cd project_name
```
Laravel Breeze をインストール。

```
composer require laravel/breeze --dev
```

認証に必要なリソースを公開して、そのまま続けて必要な依存パッケージを npm からダウンロードしてフロント環境をビルドします。

```
php artisan breeze:install
```

マイグレーション処理を行います。

```
php artisan migrate
```

これで Breeze の認証機能を使用できるようになりました！ここで一度ブラウザで認証機能が正常に動作するかを確かめると良いでしょう。

### Step 2, プロフィールページの編集

`resources\views\profile\partials\update-profile-information-form.blade.php` の20行目あたりに、画像をアップロードするコンポーネントを表示するための `x-picture-input` タグ及びエラー時の `x-input-error` タグを追加します。また、form タグにも画像をアップロードするために `enctype="multipart/form-data"` を追記します。

```diff:update-profile-information-form.blade.php
    // ...
 
+   <form method="post" action="{{ route('profile.update') }}" class="mt-6 space-y-6" enctype="multipart/form-data">
        @csrf
        @method('patch')
    
+       <div>
+           <x-picture-input />
+           <x-input-error class="mt-2" :messages="$errors->get('picture')" />
+       </div>
    
        <div>
            <x-input-label for="name" :value="__('Name')" />
            <x-text-input id="name" name="name" type="text" class="mt-1 block w-full" :value="old('name', $user->name)" required autofocus autocomplete="name" />
            <x-input-error class="mt-2" :messages="$errors->get('name')" />
        </div>

        // ...
```

`x-picture-input` タグに対応する Blade コンポーネントを作成します。以下のようにコマンドで作成しましょう。

```
php artisan make:component picture-input --view
```

すると `resources\views\components\picture-input.blade.php` という新しいファイルが作成されますので、このファイルを以下のように編集します。

```php:picture-input.blade.php
<div class="flex mb-4" x-data="picturePreview()">
    <div class="mr-3">
        <img
             id="preview"
             src="{{ isset(Auth::user()->profile_photo_path) ? asset('storage/' . Auth::user()->profile_photo_path) : asset('images/user_icon.png') }}"
             alt=""
             class="w-16 h-16 rounded-full object-cover border-none bg-gray-200">
    </div>
    <div class="flex items-center">
        <button
                x-on:click="document.getElementById('picture').click()"
                type="button"
                class="inline-flex items-center uppercase rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
            SELECT A NEW PHOTO
        </button>
        <input @change="showPreview(event)" type="file" name="picture" id="picture" class="hidden">
        <script>
            function picturePreview() { 
                return {
                    showPreview: (event) =>{
                        if(event.target.files.length > 0){
                            var src = URL.createObjectURL(event.target.files[0]);
                            document.getElementById('preview').src = src;
                        }
                    }
                }
             }
        </script>
    </div>
</div>
```

上記を記入して保存したら、あなた好みのユーザーアイコンの素材をDLして `public/images` フォルダ内に入れます。上記の img タグにある `src` 属性のURLに則って画像のファイル名は `user_icon.png` としています。今回のサンプルでは、[こちらのサイト](https://www.shoshinsha-design.com/2022/03/ユーザーアイコン色ベタ/.html) からユーザーアイコンを DL しました。アイコン画像を保存したフォルダ階層は以下のような感じです。

```
public/
　├ images/
　│　  └ user_icon.png
　├ ...
```

今の状態でブラウザで表示を確認してみると、うまく表示できていないと思います。そのときは `npm run build` でフロント周りのビルド作業を行ってください。

```
npm run build
```

そうするとうまくブラウザで表示できるようになったと思います。

### Step 3, User モデルとマイグレーションファイルの編集

uesrs テーブルのマイグレーションファイルである `database\migrations\2014_10_12_000000_create_users_table.php` を編集して、プロフィールアイコンのパスを保存するカラムを追加します。`profile_photo_path` カラムを追加する記述 1行を追加します。

```diff:2014_10_12_000000_create_users_table.php
    // ...
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
+           $table->string('profile_photo_path')->nullable(); // この行を追加
            $table->rememberToken();
            $table->timestamps();
        });
    }
    //...
```

`profile_photo_path` カラムにデータを登録できるようにするために User モデルの `app\Models\User.php` ファイル `protected $fillable` の部分を以下のように編集します。

```diff:User.php
    // ...
    protected $fillable = [
        'name',
        'email',
        'password',
+       'profile_photo_path', // この行を追加
    ];
    // ...
```

ここで一度、マイグレート処理を最初からやり直してみましょう。

```
php artisan migrate:fresh
```

こうすることで users テーブルに `profile_photo_path` 列が新たに登録されました。（登録されていたユーザーは初期化されてしまうので再度ユーザー情報を登録し直してください）

### Step 4, アイコン画像を登録する処理を記述する

まず、アップロードされる画像のバリデーション設定を `app\Http\Requests\ProfileUpdateRequest.php` に追記します。

```diff:app\Http\Requests\ProfileUpdateRequest.php
    // ...

    public function rules(): array
    {
        return [
            'name' => ['string', 'max:255'],
            'email' => ['email', 'max:255', Rule::unique(User::class)->ignore($this->user()->id)],
+           'picture' => ['file', 'mimes:gif,png,jpg,webp', 'max:3072'],
        ];
    }

    // ...
```

ユーザーアイコンを保存するための処理を `app\Http\Controllers\ProfileController.php`　の `update` アクションに記述します。以下のように編集します。

```diff:app\Http\Controllers\ProfileController.php
    // ...

    public function update(ProfileUpdateRequest $request): RedirectResponse
    {
-       $request->user()->fill($request->validated());
+       $request->user()->fill($request->safe()->only(['name', 'email']));
    
        if ($request->user()->isDirty('email')) {
            $request->user()->email_verified_at = null;
        }
    
+       $path = null;
+       if ($request->hasFile('picture')) {
+           $path = $request->file('picture')->store('profile-icons', 'public');
+           $request->user()->profile_photo_path = $path;
+       }
    
        $request->user()->save();
    
        return Redirect::route('profile.edit')->with('status', 'profile-updated');
    }

    // ...
```

アップロードされた画像を表示するため `public/storage` から `storage/app/public` へのシンボリックリンクを張ります。以下のコマンドを実行します。

```
php artisan storage:link
```

## ブラウザで確認してみましょう！

お疲れさまでした！これでプロフィールページから画像のアップロード処理を行うことができるようになっているかと思います。もう一度、会員登録をやり直してプロフィールページ上でアップロードを正常に行うことができるかご確認ください。