📩

【図解Laravel】これできっといつでも簡単カスタム【初心者向け実践オブジェクト指向】

2023/11/20に公開

大抵のことはフレームワークがいい感じにしてくれます。
でも、デフォルト設定のままではなくちょっとカスタマイズしたい!ということもある。(ほとんどそうだと思う)
というわけで、MVCフレームワーク初心者が認証周りのカスタム実装を通して学んだ、 いつでもどこでも使える「カスタム実装の考え方」 を備忘録としてまとめます。Laravel以外でもきっと同じように考えられると思います。

話すこと

  • オブジェクト指向にまつわる概念の実践について
  • フレームワーク(Laravel)のコードの話
  • フレームワークを使用した実装の考え方

話さないこと

  • オブジェクト指向の詳しい話
  • Laravel、Breezeの環境構築や各機能の詳しい話

この記事の題材

環境

  • Laravel10
  • breeze

実装する機能

  • ユーザー登録時のメール認証で送信されるメールのviewを任意のファイルで作成する
    Laravel/breezeは基本的な認証機能が実装できるパッケージです。breezeを導入するだけで【ユーザー登録+メール認証、ログイン、パスワードの再設定、ログアウト、退会】がデフォルトで実装されます。
    便利なことにUIも生成されます。しかし、UIは特にカスタムしたいところ。この記事では、ユーザー登録時に送信されるメールのUIを変更=独自のviewを使用するというテーマで話していきます。

前提(実装箇所の「オブジェクト」を見る)

クラス、継承、オーバーライドを改めて確認。

まず図解

MVCフレームワーク(Laravel)における「クラスの継承」をまず図解。

MVCにおけるクラスの継承

オブジェクト指向の「クラス」

オブジェクト指向プログラミングにおいて最も基本となる「クラス」とは、「モノ」のデータや振る舞い(メソッド)などを定義したものです。
では「モノ」とは何でしょう?オブジェクト指向を説明する時に、リアル生活に存在する動物や車などの例え話はとても分かりやすく理解を助けてくれます。そこで理解した上で、いざコードを書こうとすると「モノ」って何のこと?と思いました。

【Userモデル】

app/Models/User.php
class User extends Authenticatable
{
    
    // 中略

}

ここではUserユーザーという「モノ」が定義されています。(モデルを定義する、という言い回しをよく使うため正確に理解できていませんでした)
さらにAuthenticatableというクラスを継承しています。(実際はIlluminate\Foundation\Auth\User as Authenticatableとしてインポートしています。)

継承

「継承」とは、とあるクラスで定義されているものを引き継ぐことです。
上記コードでいうextends AuthenticatableによってAuthenticatableIlluminate\Foundation\Auth\User)で定義されているデータや振る舞い(メソッド)をUserクラスでそのまま使用することができます。
以下のように、継承元のクラス内でさらに様々なオブジェクトが使用されています。このクラスを継承しているUserモデル内で、以下の定義が全て適用されます。

Illuminate/Foundation/Auth/User.php
<?php

namespace Illuminate\Foundation\Auth;

use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\MustVerifyEmail;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\Access\Authorizable;

class User extends Model implements
    AuthenticatableContract,
    AuthorizableContract,
    CanResetPasswordContract
{
    use Authenticatable, Authorizable, CanResetPassword, MustVerifyEmail;
}

オーバーライド

「オーバーライド」は継承したクラスの振る舞い(メソッド)を継承先の子クラスで再定義=上書きすることです。
上記例で言うとIlluminate\Foundation\Auth\Userを継承するUserモデル内でIlluminate\Foundation\Auth\Userで定義されたメソッドを上書きすることができます。
下記コードのように、Userモデル内でsendEmailVerificationNotification()sendPasswordResetNotification($token)は継承元AuthenticatableIlluminate\Foundation\Auth\Userで定義されているメソッドです。
メソッドの内容には触れませんが、継承元の実装をこちらのUserモデルの実装で上書きしています。

app/Models/User.php
class User extends Authenticatable implements CanResetPassword, MustVerifyEmail
{
    // 中略

    public function sendEmailVerificationNotification()
    {
        $this->notify(new CustomVerifyEmail());
    }

    public function sendPasswordResetNotification($token)
    {
        $this->notify(new CustomResetPassword($token));
    }
}

具体例

いい感じにしてくれるフレームワークのデフォルト実装を実際にカスタムしていきます。
(どう考えたか、の備忘録)

やりたいこと

上記の通り。
breezeで簡単に実装された認証機能のうち、送信されるメールのUIを変更します。

中身を見る

まずメール送信を実装するにはデフォルトで実装されているUserモデル内でimplements MustVerifyEmailを追記。
では、このMustVerifyEmailの中身に変更を加えるには?ということで継承元を見にいきます。
Illuminateディレクトリ=./vendor/laravel/framework/src/下)

中身1

ここでもMustVerifyEmailはインポートされているので、さらに見にいく。

中身2

このあたりからは情報も揃ってきている+それっぽいメソッド名から調べたりChatGPTに聞いたりしながらですが(ここが一番大変ではある)、どこをいじればいいか探していきます。

中身3

toMail()メソッド内で確認メールの生成が行われているのでここをカスタムします。
最終的に辿り着いたVerifyEmailクラスを継承して、CustomVerifyEmailクラスを作成。
php artisan make:notification CustomVerifyEmailでファイルを作成しました。(調査の上で決定)
最終的にはこうなります。

完成

コードはこちら。

app/Notifications/CustomVerifyEmail.php
<?php

namespace App\Notifications;

use Illuminate\Auth\Notifications\VerifyEmail;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;

class CustomVerifyEmail extends VerifyEmail
{
    use Queueable;

    /**
     * Get the mail representation of the notification.
     */
    public function toMail($notifiable)
    {
        $verificationUrl = $this->verificationUrl($notifiable);

        if (static::$toMailCallback) {
            return call_user_func(static::$toMailCallback, $notifiable, $verificationUrl);
        }

        return (new MailMessage)
            ->subject('メールアドレスのご確認')
            ->view('emails.verify-email', [
                'verify_url' => $verificationUrl,
            ]);
    }
}

そして、これを元にさらにUserモデル内でsendEmailVerificationNotification()を上書きしていきます。辿ったところを戻っていくイメージ。(ゴールのコードはこちら

実際、どうしたか

こちらの記事を参考にしました。

https://specially198.com/customize-email-verification-email-in-laravel9/

あれこれ調べて見つけた上記記事を参考にして実装したところうまくいきました。
そして改めて自分が何をしたのかを振り返ると上記で図解した手順を踏んでいたことが分かったという流れです。
中身を辿っていくことで必要な情報が揃ってくるので、調査も容易になっていくのではないかと思います。

考え方のまとめ

フレームワークがいい感じにしてくれている、の裏側を辿る

辿れば分かる。知らなくてもきっと大丈夫。

集まってきた情報を元に調査

ググる。ChatGPTに聞く。

継承した独自クラスを実装

オブジェクト指向をちゃんと意識する。(私はここが抜けていました)

カスタムしたい部分のみ、継承元のコードを参考に実装

コードを読む。調べる。書く。

おわりに

だいたいこの流れなんだなーと分かったので、応用が効く範囲はそこそこあると思います。
(実際、同じようにしてパスワードリセット用メール送信の実装をカスタムしました。)
私が知らなかったのは、 【vendorディレクトリの存在】【フレームワークの構造+それらがオブジェクト指向で成り立っている】 ということです。
「モデルを定義する」ということをMVCフレームワークでのプログラミングにおいて行いますが、フレームワークが実装しているModelクラスを 継承することでモデル定義したクラスを用いてDB操作ができるとはっきり理解することができました。
今回の実装を経て、今まで単独で学んできたことの点と点がつながったように思います。この考え方だけでなんでもできるというわけではないですが、仕組みを理解・応用することを今後も意識していこうと思います。

GitHubで編集を提案
Fusic 技術ブログ

Discussion