実践知識

トレイトを理解してログイン認証のソースを追っていく

自慢ではないのですが、僕はトレイトを知らなかったのでLaravelの認証のソースが読めませんでした。

Laravelを使う人は僕みたいな感じではなく、ちゃんとPHPの基本は抑えています。

なので、普通はこんなことにならないでしょう。

しかし、今回は自身の復習の意味もこめて、トレイトの勉強とLaravelの認証のソースを追ってみます。

 

トレイトについて

トレイトは基本的にクラスと同じ振る舞いをします。

関数も書けますし、変数を定義することもできます。

クラスとの違いは、トレイトは継承することなくメンバ(クラス内の変数とメソッド)を他のクラスで使用することができる点です。

PHPではクラスを多重継承することができません。

しかしトレイトは一つのクラスから複数のトレイトを利用することができます。

そのため、トレイトを使用すれば多重継承と同様なことができます。

 

構文

trait トレイト名1{
処理
}

trait トレイト名2{
処理
}

class クラス名{
use トレイト名1, トレイト名2
}

トレイトは「trait トレイト名」で宣言します。

トレイトを利用するクラスはuseキーワードでトレイト名を指定します。

一つのクラスから複数のトレイトを使用する場合は、トレイト名をカンマ区切りで指定します。

 

例題

trait TestA{
function getTextA(){
echo 'トレイトTestAのgetTextAメソッド コール成功!<br>';
}
}

trait TestB{
function getTextB(){
echo 'トレイトTestBのgetTextBメソッド コール成功!';
}
}

Class TestC{
use TestA,TestB;
}

$test=new TestC();
$test->getTextA();
$test->getTextB();

 
実行結果

 

insteadof 演算子

メソッド名が被ってしまった場合、insteadof演算子を使います。

 

同名のメソッドをつけた場合

trait TestA {
function get_text() {
echo 'トレイトTestA:get_textメソッド コール成功!';
}
}

trait TestB {
function get_text() {
echo 'トレイトTestB:get_textメソッド コール成功!';
}
}

Class TestC {
use TestA,TestB; // FatalError !
}

$test=new TestC();
$test->get_text();

 
実行結果

 

解決策

insteadof 演算子を使います。

trait TestA{
function get_text(){
echo 'トレイトTestA:get_textメソッド コール成功!';
}
}

trait TestB{
function get_text(){
echo 'トレイトTestB:get_textメソッド コール成功!';
}
}

Class TestC{
use TestA,TestB{
//TesBのget_text()じゃなくてTestAのget_text()を使います宣言
TestA::get_text insteadof TestB;
}
}

$test=new TestC();
$test->get_text();

 
実行結果

insteadof 演算子は以下のような感じで覚えるといいかもしれません。

 

ログイン認証のソースを追っていく

目的

ログイン画面で入力した値がどこで処理されているか確認

※php artisan make:auth で自動生成されるソースを追っていきます。

 

流れ

1)ルーティング
2)コントローラ(AuthController.php)
3)トレイト(AuthenticatesAndRegistersUsers.php.php)
4)トレイト(AuthenticatesUsers.php)

 

1)ルーティング

実はルーティングを見ても認証画面のコントローラがどこかわかりません。

Route::auth();

そこで以下のコマンドを実行をしてルートの流れを確認します。

php artisan route:list

上記のコマンドでログイン処理がApp\Http\Controllers\Auth\AuthController.phpで記述されていることがわかりました。

 

2)コントローラ(AuthController.php)

App\Http\Controllers\Auth\AuthController.php にあるloginメソッドを確認します。

しかしソースを見ると中身がスッカラカンであることがわかります。

ここで先ほど学んだトレイトの知識が生かされます。

namespace App\Http\Controllers\Auth;

use App\User;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;

class AuthController extends Controller
{
  use AuthenticatesAndRegistersUsers, ThrottlesLogins;

  protected $redirectTo = '/';

  public function __construct()
  {
  $this->middleware('guest', ['except' => 'logout']);
  }
:

認証処理は AuthenticatesAndRegistersUsers(トレイト) に書かれています。

AuthenticatesAndRegistersUsers は use でエイリアスが作成されています。

エイリアスを見ると

use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;

とあるのでこのトレイトを確認します。

vendor/laravel/framework/src/Illuminate/Foundation/Auth/AuthenticatesAndRegistersUsers.php

を開きます。

 

3)トレイト(AuthenticatesAndRegistersUsers.php)

AuthenticatesAndRegistersUsers.phpを開くとまたトレイトです。

namespace Illuminate\Foundation\Auth;

trait AuthenticatesAndRegistersUsers
{
  use AuthenticatesUsers, RegistersUsers {
  AuthenticatesUsers::redirectPath insteadof RegistersUsers;
  AuthenticatesUsers::getGuard insteadof RegistersUsers;
  }
}

ここではトレイトの名前の衝突で学んだ "insteadof" 演算子が使われています。

実体ファイルは AuthenticatesUsers になります。

これも名前空間のエイリアスが設定されています。

パスは "Illuminate\Foundation\Auth" と記述されています。

現在開いているファイル AuthenticatesAndRegistersUsers.php と同じ階層にあるファイルです。

 

4)トレイト(AuthenticatesUsers.php)

ここまで追って、ようやくloginメソッドの本体が見つかります。

  public function login(Request $request)
  {
  //バリデーション
  $this->validateLogin($request);

  //スロットル取得
  $throttles = $this->isUsingThrottlesLoginsTrait();

  //もし、規定の回数を超えていたら
  if ($throttles && $lockedOut = $this->hasTooManyLoginAttempts($request)) {
  $this->fireLockoutEvent($request);

  return $this->sendLockoutResponse($request);
  }

  //認証情報取得
  $credentials = $this->getCredentials($request);

  // 認証
  if (Auth::guard($this->getGuard())->attempt($credentials, $request->has('remember'))) {
  return $this->handleUserWasAuthenticated($request, $throttles);
  }

  //失敗だったらスロットルをカウントアップ
  if ($throttles && ! $lockedOut) {
  $this->incrementLoginAttempts($request);
  }

  return $this->sendFailedLoginResponse($request);
  }

ログインの定義は20行目になります。

// 認証
  if (Auth::guard($this->getGuard())->attempt($credentials, $request->has('remember'))) {
  return $this->handleUserWasAuthenticated($request, $throttles);
  }

attempltで認証を実行しています。

成功なら handleUserWasAuthnticated をコールします。

その他にも、スロットル処理が行われています。

ログインの失敗を数え、一定の数を超えると、しばらくログインできないという処理が加わっています(標準では60秒間に5回エラー)。

このようにトレイトの振る舞いをきちんと理解しておかないと、Laravelの認証のソースが全く読めません。

Laravelのログイン認証については以上です。

本庄マサノリ

仕事でLaravelを使っています。気づいたことや新しい発見など情報を発信していきます。

 

-実践知識