Laravelで画像投稿機能をつくる

2024-10-23

laravel
Example Image

LaravelのBreezeを使うと簡単に認証機能を追加できます。

name、email、passwordからなるシンプルなプロフィール。

ここに、プロフィール画像を投稿する機能を追加してみましょう!

テーブルを変更する

まず、User テーブルに 画像の名前 を保存するカラムを追加します。

マイグレーションファイルを編集し、以下のように image_name を追加します。

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('image_name')->nullable(); // これ
    $table->rememberToken();
    $table->timestamps();
});

モデルを変更する

app/Models/User.php に image_name を追加します。

protected $fillable = [
        'name',
        'email',
        'password',
        'image_name', // これ
    ];

リクエストを変更する

app/Http/Requests/ProfileUpdateRequest.php にも image_name を追加します。

これによって、ファイルの拡張子や、データのサイズを指定できます。

public function rules(): array
    {
        return [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'lowercase', 'email', 'max:255'], 
            'image' => ['file', 'mimes:gif,png,jpg,webp', 'max:3072'], // これ
            
        ];
    }

画像投稿フォームを作る

Pages/Profile/Partials/UpdateProfileInformationForm.tsxのフォームの中に、画像ファイル選択用のinputと、プレビュー表示用のimgを追加します。

inputにはtype="file"を指定します。

なお、ファイルの送信を行う際には、formにはencType="multipart/form-data"を指定します。

<form
                onSubmit={submit}
                className="mt-6 space-y-6"
                encType="multipart/form-data"
            >
                <div>
                    <InputLabel htmlFor="image" value="Image" />

                    <div className="flex items-center gap-4 pt-2">
                        <img
                            src={previewImage}
                            alt="プレビュー画像"
                            className="w-16 h-16 rounded-full object-cover border-none"
                        ></img>
                        <input
                            type="file"
                            id="image"
                            name="image"
                            onChange={handleImageChange}
                        />
                    </div>
                </div>

プレビュー画像の表示とハンドリング

プレビュー画像用のstateと、handleImageChange関数を用意します。

const { data, setData, post, errors, processing, recentlySuccessful } =
        useForm<any>({
            name: user.name,
            email: user.email,
            _method: "patch", // 追加
        });

    const [previewImage, setPreviewImage] = useState(
        user.image_name
            ? `/storage/${user.image_name}`
            : "/images/default_image.png"
    );

    const submit: FormEventHandler = (e) => {
        e.preventDefault();
        post(route("profile.update")); // patchからpostに変更
    };

    const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const file = e.target.files?.[0];
        if (file) {
            const imageUrl = URL.createObjectURL(file);
            setPreviewImage(imageUrl); // 選択された画像のプレビューを表示
            setData("image", file); // 画像データをセット
        } else {
            setData("image", null); // 画像が選択されていない場合にnullを設定
        }
    };

プレビュー画像用のstateには登録済みの画像のパスを入れます。

未登録であればpublicフォルダに入れたデフォルト画像のパスを入れます。

URL.createObjectURL(file)で選択された画像の一時的なURLを生成し、プレビューに使用します。

Submitの処理

フォーム送信時のsubmit関数のメソッドは、デフォルトではpatchなっているのですが、multipart/form-dataを使用するためpostに変更します。

useFormに_method: "patch"を加えることで、POSTリクエスト内でもPATCH処理が行われます。

もともとHTMLの仕様でPOSTかGETしか指定ません。

Inetiaのおかげでどのメソッドも柔軟に扱えるようになっていました。

Inetiaってすごいね

formにencType="multipart/form-data"を指定する場合はInetiaのこの機能が働かなくなるので、postで代用する必要があります。

コントローラーを修正する

app/Http/Controllers/ProfileController.php を下記のように修正します。

public function update(ProfileUpdateRequest $request): RedirectResponse
    {
        $request->user()->fill($request->safe()->only(['name', 'email']));

        if ($request->hasFile('image')) {
            $path = $request->file('image')->store('images', 'public');
            $request->user()->image_name = $path;
        }

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

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

リクエストにファイルが含まれている場合、そのファイルをstoreメソッドでpublic/images フォルダに保存します。

保存先のパスはDBのimage_nameに保存します。

完成した

Breezeは基本的な認証機能をサクッと実装できるし、カスタマイズもしやすいのが魅力ですね!

share