Laravel – ログイン処理の流れ

ログインフォーム表示

ルーティング定義

ルーティングファイルにAuth::routes();を記述することでユーザー認証関係のルーティングが自動設定される。その中には、以下のログインフォームへのルーティングが含まれる。

  • メソッド:GET
  • URL:login
  • アクション:Auth\LoginController@showLoginForm
  • ルート名:login

showLoginFormアクション

LoginControllerコントローラーはAuthenticatesUsersトレイトを利用していて、その中でshowLoginFormアクションが定義されている。

AuthenticatesUsersトレイトのshowLoginForm()アクションメソッドは以下の通りで、auth.registerビューをレンダリングしている。

auth.registerビューは初期状態では存在しないのでコマンドで生成するかカスタムで作成する必要がある

フォームからのPOSTとログイン実行

ルーティング定義

ルーティングファイルにAuth::routes();を記述することで、以下のルーティングが定義される。

  • メソッド:POST
  • URI:login
  • アクション:Auth\LoginController@login

loginアクション

showLoginFormアクションと同じく、loginアクションもAuthenticatesUsersトレイトで定義されている。

このアクションの動作は以下の通り。

バリデーション

最初はログインフォームのバリデーション。

  • validateLogin()メソッドは同じAuthenticatesUsersトレイトで定義されている

連続エラー時の抑止処理

次はログインエラーが連続したときのログイン抑止処理。ここで捕捉されなければ後段のログイン認証に移る。

  • ここで呼ばれているhasTooManyLoginAttempts()fireLockoutEvent()sendLockoutResponse()の各メソッドは全てThrottlesLoginsトレイトで定義されている
    • hasTooManyLoginAttempts()はログイントライ回数のがmaxAttempts()(デフォルトでは5回)を越えているかどうかをチェック
    • fireLockoutEvent()Lockoutイベントを発火させる
    • sendLockoutResponse()は一定時間ログインを抑止する

      表示例。秒数は30秒程度で試行ごとに変化した。

      Too many login attempts. Please try again in 37 seconds.

認証チェック

上記で、バリデーションと連続トライ回数以内の確認が通ったなら、attemptLogin()で認証チェックを行う。

  • attemptLogin()は同じAuthenticatesUsersトレイトで定義されている。

    • $this->guard()ファサード関係の複雑な処理を経て、\Illuminate\Auth\SessionGuardクラスのインスタンスとなる
    • $this->credentials()は以下のように定義されていて、ユーザー名とパスワードが認証に使われる
    • SessionGuard::attempt()は以下のように認証・ログインを行う
    • SessionGuard::login()メソッドでは、セッション更新、Rememberトークン処理、ユーザーのセットなどが行われる

ログイン失敗時のカウント

ログイン失敗時には失敗回数をカウントアップする。この結果は連続エラーのチェックで参照される。

 

Laravel – ユーザー登録処理の流れ

登録フォーム表示

ルーティング定義

ルーティングファイルにAuth::routes();を記述することでユーザー認証関係のルーティングが自動設定される。その中には、以下の登録フォーム表示のルーティングも含まれている。

  • メソッド:GET
  • URL:register
  • アクション:Auth\RegisterController@showRegistrationForm
  • ルート名:register

アクション

RegisterControllerコントローラーはRegistersUsersトレイトを利用していて、その中でshowRegistrationFormアクションが定義されている。

RegistersUsersトレイトのshowRegistrationForm()アクションメソッドは以下の通りで、auth.registerビューをレンダリングしている。

auth.registerビューは初期状態では存在しないので、コマンドで生成するかカスタムで作成する必要がある

フォームからのPOSTと登録実行

ルーティング定義

ルーティングファイルへのAuth::routes();の記述によって、以下のルーティングも定義される。

  • メソッド:POST
  • URI:register
  • アクション:Auth\RegisterController@register

アクション

showRegistrationFormアクションと同じく、registerアクションもRegisterControllerでuseされるRegistersUsersトレイトで定義されている。

このアクションの動作は以下の通り。

  • マスアサインメント$request->all()のための$fillableapp\User.phpモデルで定義しておく
  • 入力されたフィールド群を検証するvalidator()メソッドで検証条件を定義
    • validator()RegistersUsersトレイトを利用する親元のRegisterControllersで定義
    • 戻り値のValidatorインスタンスのvalidate()メソッドで検証実行

  • RegistersUsersトレイトを利用する親元のRegisterControllercreate()メソッドを呼んでデータ作成
  • そのUserインスタンスを与えて登録済みのイベント発行

  • $this->registered()は登録確認のようだが、同じトレイトで空で定義されている
  • このため三項演算子のfalse側に書かれているredirect($this->redirectPath())returnの戻り値となる
  • redirectPath()RegistersUsersトレイトでさらにuseされたRedirectsUsersトレイトで定義されていて、
    • redirectTo()メソッドが定義されていれば実行
    • $redirectTo属性が定義されていればそこにリダイレクト
    • いずれでもなければ/homeにリダイレクト
  • 初期状態ではこのトレイトの親元RegisterController$redirectToが定義されているので、この内容(RouteServiceProvider::HOME)にリダイレクトされる

 

Laravel – Userモデル

概要

Laravelプロジェクト作成時にUserモデルのapp/User.phpファイルが自動作成される。この中で、認証関係について調べてみた。

User.phpファイルの内容

app/User.phpの内容は以下のとおり。

認証関係

  • UserクラスはModelクラスを(直接には)継承していない
  • UserクラスはAuthenticatableクラスを継承している
  • AuthenticatableクラスはIlluminate\Foundation\Auth\Userのエイリアスとして定義されている。

Illuminate\Foundation\Auth\Userクラス

UserモデルクラスがAuthenticatbleクラスとして継承しているIlluminate\Foundation\Auth\Userクラスの要点は以下のとおり。

  • Modelクラスを継承している(L14)
  • AuthenticatableContractインターフェイスを適用している(L15)
    • AuthenticatableContractIlluminate\Contracts\Auth\Authenticatableインターフェイスのエイリアス(L15)
  • AuthenticatableContractインターフェイスのメソッドは、Illuminate\Auth\Authenticatableトレイトで実装(L19, L5)

同じAuthenticatableという名前でインターフェイスとトレイトがあるのがややこしい。

Authenticatableインターフェース

Illuminate\Contracts\Auth\Authenticatableインターフェイスは、識別子やパスワード、セッションに関する6つのメソッドの実装を要請する。

Userクラスでこのインターフェイスを適用する際には、エイリアスでAuthenticatbleContractという別名で扱っている。

Authenticatableトレイト

Illuminate\Auth\Authenticatableトレイトは、Authenticatableインターフェイスで要請された6つのメソッドを実装する。

要約

要約すると、プロジェクトで生成されるUserクラスはIlluminate\Foundation\Auth\Userクラスを継承することによって、

  • Illuminate\Database\Eloquent\Modelクラスを継承している
  • Authenticatableインターフェイスの6つのメソッドを実装している
    • これにより、ユーザー認証と継続セッションの機能が実装される。

通知関係

UserモデルクラスはIlluminate\Notifications\Notifiableトレイトを実装している。

属性関係

$fillable

フォームのデータをマスアサインメントで取り込むための設定。

$hidden

モデルを変換する際に、指定した属性を含めないための指定。

$cast

データベースの読み込み時にstringから特定のデータ型にキャストするための指定。

マイグレーションファイル

Userモデルクラスファイルとともに、以下のマイグレーションファイルもLaravelにより生成される。

artisan migrateコマンドで生成されたテーブルの構造は以下のとおり。

ユーザー認証

基本的なユーザー認証の設定はこちらを参照。

また、ユーザー登録処理の内容ログイン処理の内容についてはそれぞれのリンクを参照。

 

Laravel6 – ユーザー認証

 

プロジェクト開始時に作成されるファイル

Userモデル

プロジェクトを作成すると、appディレクトリー下にUser.phpが作成される。

Userモデルの内容についてはこちらを参照。

コントローラー

プロジェクトを作成すると、Controllers/Authディレクトリー下に以下の認証用コントローラー群が自動的に作成される。

ルーティング設定

ユーザー認証のためのルーティング

プロジェクト作成後にUserモデルと必要なコントローラーはLaravelにより作成されるが、ルーティングは設定が必要になる。

ルーティングファイルに以下の1行を記載すると、ユーザー認証用の各種ルーティングがまとめて定義される。

ルーティングリスト

関係するルーティングは以下の通りで、ログイン関係とユーザー登録関係が定義されている。

ビューの作成

自動作成の場合

  • php artisan ui vue --authコマンドでユーザー認証に必要なビューを作成
  • 作成されるビューはresources/views/authディレクトリー下に配置

カスタムビュー作成の場合

authディレクトリーの作成

  • resources/viewsディレクトリー下にauthディレクトリーを作成
  • 作成したauthディレクトリー下に必要なビューを定められたファイル名と内容で作成

登録用フォームビューの作成~register.blade.php

authディレクトリー下に、以下フォームを含む内容でregister.blade.phpを作成。ビュー名はLaravelが生成するコントローラーで想定されているされるため、これに合わせる必要がある。

たとえばユーザー登録フォームのビュー名はRegisterControlleruseされているRegistersUsersトレイトshowRegistrationForm()メソッドにおいて、'auth.register'として呼ばれる。

ブラウザーからURL:ドメイン名/registerでアクセスすると以下のようなフォームが表示される。

カスタマイズしていない状態で登録ボタンを押すと、URL:ドメイン名/homeにリダイレクトされる(後述のように、そのままでは404エラーになる)。

ログインフォームビューの作成~login.blade.php

authディレクトリー下に、以下のフォームを含む内容でlogin.blade.phpを作成。

ブラウザーからURL:ドメイン名/loginでアクセスすると以下のようなフォームが表示される。

カスタマイズしていない状態で登録ボタンを押すと、URL:ドメイン名/homeにリダイレクトされる。

コントローラー設定

ログイン後の画面遷移

たとえばユーザーログイン後のトップページを'posts/index'とする。

デフォルト設定~404エラー

カスタマイズしていない状態では以下の様になっているが、このままログインすると404エラーとなる。

  • ルーティングファイル→特に設定なし
  • コントローラー

このときブラウザーのURL表示はhttp://localhost:3000/homeとなっている。

方法1~コントロールファイルを変更

以下の様に$redirectToの定義を編集すると、意図したページに遷移する。

方法2~ルーティングファイルに追加

コントローラーが以下の様に生成直後の状態で・・・

ルーティングファイルに以下の1行を追加する。

なお、上記をルーティングファイルに追加すれば、コントローラーの定義は以下でもよい。

ルート名は使えない

ルート名が設定されていても、以下の表現はエラーになる(Constant expression contains invalid operations)。

ユーザー登録後の画面遷移

RegisterControllerでもデフォルトで以下の様に定義されている。

LoginControllerと同じようにルーティング設定をすることで、ユーザー登録後の遷移先を設定することができる。

ユーザー認証処理の詳細

ユーザー登録処理の内容ログイン処理の内容についてはそれぞれのリンクを参照。

2038年問題対応

Laravelでモデルの雛形を生成する場合、MySQLだと作成日時と更新日時のtimestamp型の2038年問題が内包される。

database/migrationsディレクトリー下のマイグレーションファイル、...create_users_table.phptimestampsdatetime型に修正しておくべき。

ログアウト処理

ログアウト処理についてはこちら

 

PHP – マジックメソッド – __callStatic()

__callStatic()はマジックメソッドの一つで、実行させようとしたクラスのstaticメソッドが存在しない時に呼ばれる。

以下の例では、MyClass__callStatic()メソッドのみが定義されている。__callStatic()の内容は、引数の$method$argsを表示させるようにしている。

このクラスに存在しないstaticメソッドを、引数なし、引数1個、2個で実行した場合の実行結果。引数は配列として$argsにセットされ、引数がない場合は空の配列、引数が1個の場合は要素数1(要素番号0)の配列となる。

インスタンスに対して存在しないstaticメソッドを呼んで__callStatic()が実行される場合、通常のstaticメソッドと違って'->'はエラーになる。

 

PHP – static::とself::

概要

static::self::はクラスのスタティックメソッドを指すときに使えるが、それぞれ以下のような違いがある。

  • staticはそれが実行されるときのクラスを指す
  • selfはそれが定義されたときのクラスを指す

参照:new staticとnew self

準備

以下のようにParentClassとそれを継承したChildClassを準備する。

  • いずれも同じ名前のstaticメソッドstatic_method()を持つ
  • それぞれのクラスはインスタンスメソッドparent_method()child_method()を持つ
    • 何れの内容も同じで、static::static_method()self::static_method()を実行する

クラスからstaticメソッドを呼ぶ

クラスからstaticメソッドを呼ぶと、当然それぞれに対応したstatic_method()が実行される。

親クラスのメソッドから呼ぶ

親クラスのインスタンスを作って、インスタンスメソッドparent_method()内でstaticselfstatic_method()を呼び出す場合。

親クラスのstaticメソッドしか対象がないので、いずれの場合も親クラスのstatic_method()が実行される。

子クラスのメソッドから呼ぶ

子クラスのインスタンスを作って、インスタンスメソッドchild_method()内でstaticselfstatic_method()を呼び出す場合。

staticselfとも子クラスを指し、子クラスのインスタンスメソッドが実行される。

子クラスで親クラスのメソッドから呼ぶ

子クラスのインスタンスを作って、継承元の親クラスのインスタンスメソッドparent_method()からstaticselfstatic_method()を呼び出す場合。

staticは実行時のクラスを指すため、Childクラスのstatic_method()が実行される。

一方selfは定義時のクラスを指すため、parent_method()が定義されたParentClassstatic_method()が実行される。

Laravel – artisan – ルーティングリストの表示

下記コマンドでルーティングのリストが表示される。

php artisan route:list

ただし、各ルーティングで呼ばれるコントローラー・アクションが定義されていなければならない。

実行例。

 

Laravel – 名前付きルート

記述方法

ルーティングのname()メソッドで、ルートに名前を付けることができる。

Route::メソッド('URL', 'コントローラー@アクション')->name('ルート名');

この名前を使ってルーティングのURLを得るには、route()ヘルパーを使う。

route('ルート名')

URLにルートパラメーターを含む場合、route()でパラメーターの値を設定して、これを含むURLを得ることができる。

route('ルート名', ['パラメーター' => 値])

ルートパラメーターが複数ある場合は連想配列の要素として列挙。いずれのパラメーターも省略することはできない。

route('ルート名', ['パラメーター1' => 値1, 'パラメーター2' => 値2])

ルーティングで以下のように記述する。

コントローラーのnamed_route()アクションでビューを呼び出す。

ビューの中で、route()ヘルパーによってルーティングのURLを表示させる。

結果は以下のように表示される。

名前付きルート
http://localhost:3000/named_route
http://localhost:3000/named_route/100/set
http://localhost:3000/named_route/first/100/second/200

 

メリット

たとえばアンカータグのURLにルートパラメーターを含む場合、文字列で結合するよりも可読性が高くなる。

URLを変更した場合でも、ルーティングファイルのみ書き換えればよい。

このほか、RESTに基づくリソースルーティングの場合は、自動的に名前が付けられる。

 

MySQL – timestampの2038年問題

timestamp型とdatetime型

MySQLの日付・時刻表現にはtimestamp型とdatetime型がある。

MySQLのtimestamp型は内部表現に整数を用いていて、これが32bit精度の場合には、2038-01-19 03:14:07UTCまでしか表現できない。これは日本のタイムゾーンだと2038-01-19 12:14:07JSTに対応する。

一方datetime型は文字列で日時を表現していて、その範囲は1000年~9999年の間とされている。

timestamp型とdatetime型の有効範囲を比較すると以下のとおり。

timestamp datetime
UTC内部表現で保持 タイムゾーン文字列で保持
1970-01-01 00:00:01UTC~2038-01-19 03:14:07UTC 1000-01-01 00:00:00~9999-12-31 23:59:59

timestamp型の2038年問題

確認環境

timestamp型で上限値より大きな値を登録しようとすると、エラーで登録ができない。このことを確認してみる。環境は以下のとおり。

  • Windows(64bit版)上のVagrant+VirtualBOXで構築したCentOS7
  • MySQLのバージョンは8.0.26

確認用のテーブルは以下のとおり。

上限を超える値の登録は不可

まず上限値一杯の日時を登録してみる。UTCに対して日本のタイムゾーンで表現した2038年1月19日12時14分07秒を登録すると、問題なく登録される。

次に上限値に1秒を加えた日時を登録しようとすると、"Incorrect datetime value"となって登録できない。

timestamp型に対する加算の場合は9999年まで

次に登録された上限一杯の値に対してインターバルを加えていき、問題が生じるまでその境界を探していった。

その結果、9999年12月31日23時59分59秒999999…までは登録可能だが、10000年1月1日0時0分0秒に達することはできず、その場合にはNULLとなることがわかった。

32bitの上限を超えた後も値は保持されているが、その次の上限はdatetime型の上限と一致している。以下の何れかと推測される。

  • 内部的には64bit表現だが、入力時には32bitの上限で、演算時にはdatetime型の上限で抑えている
  • 内部的に32bit表現だが、その上限を超えたときにはdatetime型に内部的に切り替えている

まとめ

  • MySQLのtimestamp型は、2038年の上限値を超える値を登録できない
  • timestamp型に対する加算を行った場合、datetime型と同じ上限まで値を保持できる
    • 加算後にdatetime型の上限を超えた場合の値はNULLになる

64bit整数の場合はtimestampも西暦3000億年弱まで扱い可能だが、一部が64bitシステムでも、データベース、言語、フレームワークの全てが対応していないとまずそうなので、日付時刻を扱う際はdatetime型としておくのが安全なようだ。

 

Tips – カラムの幅を一杯に広げる

概要

外枠の中に複数カラムを起き、1つのカラムを最大幅まで伸長するためのCSS。

外枠

外枠を表示領域の80%の幅とし、センタリング。

  • 幅を表示領域の80%に:width: 80%;
  • 左右方向のセンタリング:margin: 0 auto;
 width: 80%; margin: 0 auto;

外枠の中に複数カラム

親要素にdisplay: flex;を指定し、伸長する子要素のみflex: 1;を指定。

2カラム~右拡張

  • 外枠(親要素):display: flex;
  • 左側カラム:幅指定なし→内容に合わせる
  • 右側カラム:flex: 1;→残り幅一杯
margin: 10px;
margin: 10px;flex: 1;
いづれの御時にか、女御、更衣あまた候ひ給ひける中に、いとやむごとなき際にはあらぬが、すぐれて 時めき給ふありけり。

3カラム~中央拡張

  • 外枠(親要素):display: flex;
  • 左側カラム:width: 10%;
  • 中央カラム:flex: 1;→残り幅一杯
  • 右側カラム:width: 10%;
width 10%;
margin: 10px;flex: 1;
いづれの御時にか、女御、更衣あまた候ひ給ひける中に、いとやむごとなき際にはあらぬが、すぐれて 時めき給ふありけり。
width 10%;