フリーランスのためのネットビジネス専門学校 ネットで独立開業を目指す人を応援
フリーランスのためのネットビジネス専門学校 ネットで独立開業を目指す人を応援

Larabelでマイグレーションを使用する際にintegerのサイズの指定方法に注意

本項のテーマ & 前置き

Laravel5.7のコードを読んでいきます。
5.6 → 5.7でいくつかの機能が追加されました。そのうちの一つがEmail Verificationです。
早い話がユーザー登録時のメール確認ですね。以下の点をコード上で確認してみたいと思います。

  • ユーザー登録→確認メール送信
  • メール記載のURLをクリック→登録完了

Let’s コードリーディング!!

確認メールを送るまで

まずは登録時の処理がどう変わっているのか見ていきましょう。
登録処理はIlluminate/Foundation/AuthRegistersUsersトレイトで実装されています。

vendor/laravel/framework/src/Illuminate/Foundation/Auth/RegistersUsers.php
/**
 * Handle a registration request for the application.
 *
 * @param  IlluminateHttpRequest  $request
 * @return IlluminateHttpResponse
 */
public function register(Request $request)
{
    $this->validator($request->all())->validate();

    event(new Registered($user = $this->create($request->all())));

    $this->guard()->login($user);

    return $this->registered($request, $user)
                    ?: redirect($this->redirectPath());
}

特に変わった様子はありません。ここで確認メールを送ってるかと思ったのですが違うみたいです。
Registeredイベントを使ってメール送信をしているのかもしれません。イベントサービスプロパイダーを覗いてみましょう。

app/Providers/EventServiceProvider.php
protected $listen = [
    Registered::class => [
        SendEmailVerificationNotification::class,
    ],
];

RegisteredイベントにSendEmailVerificationNotificationハンドラーを登録していました。では、SendEmailVerificationNotificationクラスを見てみます。

vendor/laravel/framework/src/Illuminate/Auth/Listeners/SendEmailVerificationNotification.php
public function handle(Registered $event)
{
    if ($event->user instanceof MustVerifyEmail && ! $event->user->hasVerifiedEmail()) {
        $event->user->sendEmailVerificationNotification();
    }
}

$event->userのuserはAuthRegistersUsersトレイトでRegisteredイベントのコンストラクタの引数として渡していたUserクラスのインスタンスです。つまり、UserクラスのsendEmailVerificationNotificationメソッドを呼び出しています。Userクラスを読み進めて見ましょう。

AppUserクラスは親クラスはIlluminateFoundationAuthUserクラスです。こちらを見てみます。

vendor/laravel/framework/src/Illuminate/Foundation/Auth/User.php
class User extends Model implements
    AuthenticatableContract,
    AuthorizableContract,
    CanResetPasswordContract
{
    use Authenticatable, Authorizable, CanResetPassword, MustVerifyEmail;
}

MustVerifyEmailトレイトをuseしてますね。SendEmailVerificationNotificationクラスでもUserクラスのインスタンスがMustVerifyEmailインターフェースかどうかチェックしていました。MustVerifyEmailトレイトの方を見て見ましょう。

※ 実はここまででおかしな点があります。少し脱線するので後ほど解説したいと思います。

vendor/laravel/framework/src/Illuminate/Auth/MustVerifyEmail.php
trait MustVerifyEmail
{
    /**
     * Determine if the user has verified their email address.
     *
     * @return bool
     */
    public function hasVerifiedEmail()
    {
        return ! is_null($this->email_verified_at);
    }

    /**
     * Mark the given user's email as verified.
     *
     * @return bool
     */
    public function markEmailAsVerified()
    {
        return $this->forceFill([
            'email_verified_at' => $this->freshTimestamp(),
        ])->save();
    }

    /**
     * Send the email verification notification.
     *
     * @return void
     */
    public function sendEmailVerificationNotification()
    {
        $this->notify(new NotificationsVerifyEmail);
    }
}

sendEmailVerificationNotificationメソッドでnotifyしてます。そのままNotifications/VerifyEmailクラスを確認して見ましょう。

vendor/laravel/framework/src/Illuminate/Auth/Notifications/VerifyEmail.php
public function toMail($notifiable)
{
    if (static::$toMailCallback) {
        return call_user_func(static::$toMailCallback, $notifiable);
    }

    return (new MailMessage)
        ->subject(Lang::getFromJson('Verify Email Address'))
        ->line(Lang::getFromJson('Please click the button below to verify your email address.'))
        ->action(
            Lang::getFromJson('Verify Email Address'),
            $this->verificationUrl($notifiable)
        )
        ->line(Lang::getFromJson('If you did not create an account, no further action is required.'));
}

protected function verificationUrl($notifiable)
{
    return URL::temporarySignedRoute(
        'verification.verify', Carbon::now()->addMinutes(60), ['id' => $notifiable->getKey()]
    );
}

おめでとう!あなたは目的地に到達しました!このtoMailメソッドで確認メールを送っています。そしてverificationUrlメソッドで確認用のURLを作成しています。URL::temporarySignedRouteを使用していますので、60分で無効になる署名URLになっているようです。(Laravelの署名URLについてはこのへんを参考にしてください。)

おかしな点

さて、キリがいいので、ここで注釈で言っていたおかしな点についで話をしてみます。

Illuminate/Foundation/Auth/Userクラスの基本的な実装方針としては、「interfaceをimplementしておいて、traitで実装を書く」形になってます。このクラスは3つのインターフェイスをimplementしていますが、各々のtraitとの対応関係は以下のようになります。

interface trait
IlluminateContractsAuthAuthenticatable(AuthenticatableContract) IlluminateAuthAuthenticatable
IlluminateContractsAuthAccessAuthorizable(AuthorizableContract) IlluminateFoundationAuthAccessAuthorizable
IlluminateContractsAuthCanResetPassword(CanResetPasswordContract) IlluminateAuthPasswordsCanResetPassword

本来であれば、Illuminate/Foundation/Auth/UserクラスはIlluminateContractsAuthMustVerifyEmailインターフェイスをimplementした上でIlluminateAuthMustVerifyEmailトレイトをuseするのが正しい流れなのですが、なぜかUserクラスはMustVerifyEmailインターフェイスをimplementしていません。

ダックタイピングのおかげ(?)でこのコードはなんの問題もなく動くと思いますが、明示的にimplementsしたほうが当然わかりやすいので個人的にこの実装は好きじゃありません。(感想)

メール記載のURLクリックから確認まで

メールに記載されているURLはverification.verifyというルート名でした。どうせVerificationControllerクラスのverifyメソッドなので見てみましょう(適当)

app/Http/Controllers/Auth/VerificationController.php
use IlluminateFoundationAuthVerifiesEmails;

class VerificationController extends Controller
{
    use VerifiesEmails;

    public function __construct()
    {
        $this->middleware('auth');
        $this->middleware('signed')->only('verify');
        $this->middleware('throttle:6,1')->only('verify', 'resend');
    }
}

例によってtraitで実装してるみたいです。IlluminateFoundationAuthVerifiesEmailsトレイトに進みましょう。

vendor/laravel/framework/src/Illuminate/Foundation/Auth/VerifiesEmails.php
public function verify(Request $request)
{
    if ($request->route('id') == $request->user()->getKey() &&
        $request->user()->markEmailAsVerified()) {
        event(new Verified($request->user()));
    }

    return redirect($this->redirectPath());
}

UserクラスのmarkEmailAsVerifiedメソッドでいかにも何かしてそうです。このmarkEmailAsVerifiedメソッドはUserクラスが継承するIlluminateFoundationAuthUserクラスでuseしているMustVerifyEmailトレイトに実装されていましたね。すでに一度引用していますが、以下再掲します。

vendor/laravel/framework/src/Illuminate/Auth/MustVerifyEmail.php
trait MustVerifyEmail
{
    public function markEmailAsVerified()
    {
        return $this->forceFill([
            'email_verified_at' => $this->freshTimestamp(),
        ])->save();
    }
}

email_verified_atカラムを更新しています。このemail_verified_atカラムは
(公式のリリースノート)[laravel.com/docs/5.7/releases#laravel-5.7] にも書いてあるように、今回の更新でu追加されました。一応マイグレーションファイルを確認してみましょう。

database/migrations/2014_10_12_000000_create_users_table.php
Schema::create('users', function (Blueprint $table) {
    $table->increments('id');
    $table->string('name');
    $table->string('email')->unique();
    $table->timestamp('email_verified_at')->nullable(); /// ← こいつが追加
    $table->string('password');
    $table->rememberToken();
    $table->timestamps();
});

さぁ、これでメール確認が完了しました。ですが、メール確認したかどうかのチェックをしている箇所も確認しておいたほうがいいでしょう。

メール確認が済んでいるかどうかのチェックはどこ?

ユーザーの認証チェックはミドルウェアでやってました。なのでメール確認済みかどうかもミドルウェアでやってるに違いありません(根拠なし)。なのでKernelクラスを確認して該当するミドルウェアがないかみて見ましょう。

app/Http/Kernel.php
protected $routeMiddleware = [
    'auth' => AppHttpMiddlewareAuthenticate::class,
    'auth.basic' => IlluminateAuthMiddlewareAuthenticateWithBasicAuth::class,
    'bindings' => IlluminateRoutingMiddlewareSubstituteBindings::class,
    'cache.headers' => IlluminateHttpMiddlewareSetCacheHeaders::class,
    'can' => IlluminateAuthMiddlewareAuthorize::class,
    'guest' => AppHttpMiddlewareRedirectIfAuthenticated::class,
    'signed' => IlluminateRoutingMiddlewareValidateSignature::class,
    'throttle' => IlluminateRoutingMiddlewareThrottleRequests::class,
    'verified' => IlluminateAuthMiddlewareEnsureEmailIsVerified::class, /// ← きっとこいつ
];

あった(一勝)。IlluminateAuthMiddlewareEnsureEmailIsVerifiedクラスに進みます。

vendor/laravel/framework/src/Illuminate/Auth/Middleware/EnsureEmailIsVerified.php
class EnsureEmailIsVerified
{
    public function handle($request, Closure $next)
    {
        if (! $request->user() ||
            ($request->user() instanceof MustVerifyEmail &&
            ! $request->user()->hasVerifiedEmail())) {
            return $request->expectsJson()
                    ? abort(403, 'Your email address is not verified.')
                    : Redirect::route('verification.notice');
        }

        return $next($request);
    }
}

UserクラスのhasVerifiedEmailメソッドでチェックしてそうです。このhasVerifiedEmailメソッドはUserクラスが継承するIlluminateFoundationAuthUserクラスでuseしているMustVerifyEmailトレイトに実装されていましたね。(コピペ)

vendor/laravel/framework/src/Illuminate/Auth/MustVerifyEmail.php
trait MustVerifyEmail
{
    public function hasVerifiedEmail()
    {
        return ! is_null($this->email_verified_at);
    }

email_verified_atをチェックしています。これで5.7の標準認証もバッチリだな?

終わりに

間違いあったら指摘おなしゃーす。

コメント

記事に戻る

コメントを残す