You are here
Home > Posts tagged "トレイト"

ユーザー認証(11)Laravel 5.2 ログインの記録

「ユーザー認証」のポストは、もうすでに11回目になりました。Taylorくんのプログラムは、宝石がいっぱい詰まっているから、ソースコードを見ているといろいろ発見あります。

例えば、私はユーザー認証成功後にログインの記録が欲しいです。つまり、ユーザーがどのIPからどのブラウザあるいはどのOSでアクセスしたかをDBに記録したいのです。ソースコードを見ているとそのことをあたかも考慮しているメカニズムが存在することに気づきます。今回はそれをどう利用するかを紹介します。

まず、ユーザー認証(9)Laravel 5.2 コンポーネント自動作成で紹介したコマンドで作成されるファイルの1つ、AuthController.phpを編集することになります。しかし、以下を見てわかるようにトレイトをバンバン使っているので、そのファイルの中身はほとんど空です。

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']);
    }

    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|max:255',
            'email' => 'required|email|max:255|unique:users',
            'password' => 'required|confirmed|min:6',
        ]);
    }

    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
        ]);
    }
}

さて、どうすればよいでしょう?

上の11行目のトレイトのファイルを遡って辿ります。

AuthenticatesAndRegistersUsers

AuthenticatesUsers

このファイルで、postLoginは、ログイン情報を送信したときに呼ばれる関数、その定義の中では、loginをコールしています。

...
    public function postLogin(Request $request)
    {
        return $this->login($request);
    }
...
    public function login(Request $request)
    {
        $this->validate($request, [
            $this->loginUsername() => 'required', 'password' => 'required',
        ]);
 
        $throttles = $this->isUsingThrottlesLoginsTrait();

        if ($throttles && $this->hasTooManyLoginAttempts($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) {
            $this->incrementLoginAttempts($request);
        }
        return $this->sendFailedLoginResponse($request);
    }
...
    protected function handleUserWasAuthenticated(Request $request, $throttles)
    {
        if ($throttles) {
            $this->clearLoginAttempts($request);
        }
        if (method_exists($this, 'authenticated')) {
            return $this->authenticated($request, Auth::guard($this->getGuard())->user());
        }
        return redirect()->intended($this->redirectPath());
    }
...

loginの定義ではattempt()(21行目)で認証を実行しています。そしてそれが成功なら、handleUserWasAuthenticatedをコールします。

今度は、36行目見てください。ここのifは、このクラスにauthenticatedというメソッドが定義されているなら、それを実行しますよ!という意味です。

先のAuthController.phpに以下のメソッドを追加します。以下のハイライトされた
部分です。

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;

use App\UserLog;
use Illuminate\Http\Request;

class AuthController extends Controller
{
    use AuthenticatesAndRegistersUsers, ThrottlesLogins;

    protected $redirectTo = '/';

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

    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|max:255',
            'email' => 'required|email|max:255|unique:users',
            'password' => 'required|confirmed|min:6',
        ]);
    }

    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
        ]);
    }

    public function authenticated(Request $request, User $user)
    {
        UserLog::add($request, $user);//ここでログを作成

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

これで、ログインが成功するたびに、このauthenticatedがコールされログインの履歴をDBに作成します。

以下はそこで使用されるモデルUserLogです。ホスト名を得るために、ミューテーターを使用します。


namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;

class UserLog extends Model {

    protected $table = 'user_log';
    protected $primaryKey = null;
    public $incrementing = false;
    public $timestamps = true;

    public function setHostnameAttribute($ip)
    {
        $this->attributes['hostname'] = gethostbyaddr($ip);
    }

    public static function add(Request $request, User $user)
    {
        $userLog = new static;

        $userLog->user_id = $user->id;
        $userLog->ip = $request->getClientIp();
        $userLog->hostname = $userLog->ip; // ミューテーターを使用

        $userLog->save();
    }
}

user_logのDBテーブルを作成することをお忘れずに。

authenticatedには、ログインの履歴を作成するだけでなく、会員の1周年記念のためのお祝いのメッセージや、それにまつわる特典の提供とか、いろいろなコードを含むことが考えられます。

ユーザー認証(2)ユーザーの登録

ユーザーの登録は、重複回避ーDB重複エラーで、一部分紹介しました。ここでは、一部と言わず全部をカバーしてみましょう。しかも、ララベル5.1がサンプルとして提供するプログラムをもとに。

ララベルが提供するユーザー認証のサンプルは、ララベルの作者、テイラーが作成したプログラムでありますが、すべてのパーツが揃っていて、親切な説明がなされているというものではありません。コントローラで使用するブレイドのファイルもないし、ブレイドのファイル名やパス名もドキュメントにはありません。また、プログラムでは、トレイトが多用されているために、まず参照されているファイルを見て、次の参照ファイルを見て、さらに次へ・・という、理解には探偵作業が必要です。ちょっと腰を据えて取り組んでみましょう。

まず、最前線のファイルのリストから、

app/Http/routes.php
app/Http/Controllers/Auth/AuthController.php

そう、これだけなのです。もちろんブレイドファイルも作成する必要ありますが、これだけでユーザーの登録とログイン画面ができてしまいます。それぞれ中身を見てみましょう。

まず、routes.php。これは、画面でアクセスするプログラムの回路図のようなもので、ここでURLとコントローラを結びつけます。

// 会員登録
Route::get('auth/register', 'Auth\AuthController@getRegister');
Route::post('auth/register', 'Auth\AuthController@postRegister');

// ログイン・ログアウト
Route::get('auth/login', 'Auth\AuthController@getLogin');
Route::post('auth/login', 'Auth\AuthController@postLogin');
Route::get('auth/logout', 'Auth\AuthController@getLogout');

//会員登録後、ログイン後に飛ばされるホーム画面
Route::get('/home', function () {
    return view('auth.home');
});

次に、AuthController.php。このひとつで会員登録とログインをやってしまいます。いったいどうやっているのか見てみましょう。(オリジナルの英語のコメントはスペースのために省いています)


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;

    public function __construct()
    {
        // ログアウト以外は、guestのミドルウェアを通す。
    // ここですでに認証されているかどうかを判断。
        $this->middleware('guest', ['except' => 'getLogout']);
    }

  // 入力チェックのルーチン
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|max:255',
            'email' => 'required|email|max:255|unique:users',
            'password' => 'required|confirmed|min:6',
        ]);
    }

  // DBにレコードを作成。
    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
        ]);
    }
}

これを最初に見て思うのは、routes.phpで使用されているコントローラのメソッドはどこにある??

getRegister
postRegister
getLogin
postLogin
getLogout

これらのメソッドのことです。もしかして、Controllerのクラスを継承しているので、Controller.phpで定義されている?
しかし、そちらはもっと短い(以下)!


namespace App\Http\Controllers;

use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;

abstract class Controller extends BaseController
{
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}

それならいったいどこで?と探偵作業の始まりです。

となると、AuthController.phpの13行目のトレイト?

use AuthenticatesAndRegistersUsers

見てみましょう。


namespace Illuminate\Foundation\Auth;
trait AuthenticatesAndRegistersUsers
{
    use AuthenticatesUsers, RegistersUsers {
        AuthenticatesUsers::redirectPath insteadof RegistersUsers;
    }
}

またしてもトレイト!

use AuthenticateUsers, RegisterUsers

RegisterUsers.phpを見てみましょう(ログイン関連は、AuthenticateUsers.phpですがそれは次回に)。


namespace Illuminate\Foundation\Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
trait RegistersUsers
{
    use RedirectsUsers;

    public function getRegister()
    {
        return view('auth.register');
    }

    public function postRegister(Request $request)
    {
        $validator = $this->validator($request->all());
        if ($validator->fails()) {
            $this->throwValidationException(
                $request, $validator
            );
        }
        Auth::login($this->create($request->all()));
        return redirect($this->redirectPath());
    }
}

これですね、探していたのは!

getRegisterでは、auth.registerがブレードの場所ということわかります。つまり、

resources/views/auth/register.blade.php

を作成すれば、会員登録の画面ができあがりです。

postRegisterの関数では、先にAuthController.phpで定義した、validatorcreateがここで参照されていますね。

Auth::login($this->create($request->all()));

ここでは、DBレコードを作成して返される、Userのインスタンスをもとに、ログインも行っていしまいます。つまり、登録したら自動ログインして、ホームにリダイレクトということです。

しかし、いったいどこへリダイレクト?

それも、もちろんRedirectUsersのトレイトなのです。


namespace Illuminate\Foundation\Auth;

trait RedirectsUsers
{
    public function redirectPath()
    {
        if (property_exists($this, 'redirectPath')) {
            return $this->redirectPath;
        }

        return property_exists($this, 'redirectTo') ? $this->redirectTo : '/home';
    }
}

デフォルトは、/homeですね。また、Authocontrollerにおいて、redirectToの上書きも可能ということです。

最後に、登録画面のブレードファイルには、以下のようなHTMLがあればOKです。

<form method="post" action="auth/register">
{!! csrf_field() !!}
  <div>
    ログイン:
    <input type="email" name="email" value="{{ old('email') }}" required autofocus>
  </div>
  <div>
     パスワード:
     <input type="password" name="password" required>
  </div>
  <div>
     パスワードの確認:
     <input type="password" name="password_confirmation" required>
  </div>
  <div>  
     名前:
     <input type="text" name="name" value="{{ old('name') }}" required>
  </div>
  <div>
     <button type="submit">保存</button>
  </div>
</form>

次回は、ログインの方を深く見てみましょう。

Top