Home > アーカイブ > 2014-02

2014-02

Laravel でカスタムドライバを使って Remember Me(オートログイン)を実装する

この記事の所要時間: 743

Laravel には remeber me 機能があるのですが、これは暗号化した Cookie の情報だけで認証を行うので、やや心許ない実装です。

rememberme

そこで、Laravel フレームワークを拡張して、独自の認証処理ドライバを実装してみました。

ログイントークンテーブル

認証用ログイントークンを保存するテーブルを作成します。

まず、artisanコマンドでマイグレーションクラスを作成します。

$ php artisan migrate:make make_login_tokens

生成されたマイグレーションクラスにテーブル定義を書いていきます。

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class MakeLoginTokens extends Migration {
	/**
	 * Run the migrations.
	 *
	 * @return void
	 */
	public function up()
	{
        Schema::create('login_tokens', function($table) {
            $table->increments('id');
            $table->text('token')->unique();
            $table->integer('user_id')
                ->references('id')->on('users')
                ->onDelete('cascade')->onUpdate('cascade');
            $table->timestamps();
        });
	}

	/**
	 * Reverse the migrations.
	 *
	 * @return void
	 */
	public function down()
	{
        Schema::drop('login_tokens');
	}
}

マイグレーションを実行して、login_tokensテーブルを生成します。

$ php artisan migrate

認証カスタムドライバの作成

ログイン認証処理は、Authクラスが担っています。ただこれは只のファサードでしかなく、実体はIlluminate\Auth\AuthManagerです。Illuminate\Auth\AuthManagerクラスは、認証処理をドライバに委譲しているので、このドライバを差し替えることで、独自の認証処理を行うことができます。

フレームワークにはIlluminate\Auth\Guardというドライバが用意されているので、これを継承してカスタムドライバを実装します。

今回、実装(オーバーライド)したのはgetUserByRecaller()メソッドとqueueRecallerCookie()メソッドです。

getUserByRecaller()メソッドは、オートログイン時に呼ばれるので、ブラウザから送信された Cookie の値を使って自動ログインを行うようにしています。

queueRecallerCookie ()メソッドは、ログイン時に呼ばれるので、ログイントークンを生成してDBに登録し、Cookie に埋め込むようにしました。

<?php
namespace Acme;

use Carbon\Carbon;
use Config;
use DB;
use Illuminate\Auth\Guard;
use LoginToken;

/**
 * Class AuthLoginGuard
 * @package Acme
 */
class AutoLoginGuard extends Guard
{
    /**
     * @param mixed $id
     * @return mixed
     */
    protected function getUserByRecaller($id)
    {
        $limit = Carbon::now()->subDays(Config::get('auth.login_token_limit_day'));

        // gc
        DB::table(with(new LoginToken)->getTable())->where('updated_at', '<=', $limit)->delete();

        $token = LoginToken::where('token', $id)->where('updated_at', '>', $limit)->first();
        if (empty($token)) {
            return null;
        }

        $user = parent::getUserByRecaller($token->user_id);
        if (empty($user)) {
            return null;
        }

        $token->touch();
        $this->updateSession($user->id);

        return $user;
    }

    /**
     * @param string $id
     */
    protected function queueRecallerCookie($id)
    {
        $bytes = Config::get('auth.login_token_length') / 2;
        $token = bin2hex(openssl_random_pseudo_bytes($bytes));

        $loginToken = new LoginToken();
        $loginToken->token = $token;
        $loginToken->user_id = $id;
        $loginToken->save();

        parent::queueRecallerCookie($token);
    }
}

カスタムドライバを有効にする

作成したカスタムドライバをフレームワークで有効にします。

Auth::extend()

Auth::extend()で、Acme\AutologinGuardクラスをインスタンス化するクロージャを定義します。ドライバ名にはautologinを指定しています。

Auth::extend('autologin', function($app) {
    $provider = new EloquentUserProvider($app['hash'], $app['config']['auth.model']);
    return new AutoLoginGuard($provider, $app['session.store']);
});

app/config/auth.php

app/config/auth.php に、このautologinドライバを認証ドライバとして利用するように設定します。

'driver' => 'autologin',

さらに、Acme\AutologinGuardクラスに必要なログイントークンに関する設定も追加します。

  'login_token_limit_day' => 30, // ログイントークン有効期間
  'login_token_length' => 60, // ログイントークンの長さ

まとめ

独自のカスタムドライバを作成して、Authクラスの認証処理を差し替えてみました。

このように Laravel では、フレームワークの処理を置き換えることができるような機構が用意されているので、要件に合わない部分に関しては、好きに変更することができます。

<

p>こうした柔軟性の高さも Laravel の面白いところですね。

  • コメント (Close): 0
  • トラックバック (Close): 0

Laravel IoC コンテナの使い方

この記事の所要時間: 1149

laravel

最近は Laravel + AngularJS で Web アプリケーションを開発する毎日です。

少し時間が取れたので Laravel フレームワークのソースを読んでいます。その中から Laravel の肝でもある IoC コンテナの使い方をまとめてみました。

Laravel の IoC コンテナ

Laravel で使われている IoC コンテナは、Illiminate\Foundation\Applicationクラスです。これはIlliminate\Container\Containerクラスを継承したもので、コンテナとしての基本機能はIlliminate\Container\Containerクラスが担っています。

Laravel アプリケーションで、この IoC コンテナを利用する際は、AppクラスというIlliminate\Foundation\Applicationのファサードクラスが用意されているので、こちらを利用することが多いです。

// Foo インスタンスを取得
$foo = App::make('Foo');

AppクラスのgetFacadeAccessor()メソッドを使うと、Illiminate\Foundation\Applicationクラスのインスタンスを取得することができるので、直接メソッドを実行できます。

// Foo インスタンスを取得
$foo = App::getFacadeAccessor()->make('Foo');

下準備

ここでは、IoC コンテナの動きを見ていくので、Laravel フレームワークを起動せずにシンプルなコードを書きます。

ファサードクラスは定義されていないので、Illiminate\Foundation\Applicationクラスのインスタンスを直接操作します。(Appクラスを使う場合は、メソッド呼び出しをクラスメソッドとして読み替えて下さい。)

<?php
require __DIR__.'/vendor/autoload.php';

class Foo
{
    /**
     * @var string
     */
    protected $message;

    /**
     * @var string $message
     */
    public function __construct($message = '')
    {
        $this->message = $message;
    }

    /**
     * @return string
     */
    public function getMessage()
    {
        return $this->message;
    }
}


$app = new \Illuminate\Foundation\Application();

コンテナからインスタンス取得

IoC コンテナからインスタンスを取得するには、make()メソッドを使います。引数には、コンテナにバインドした際の名前を指定します。コンテナに指定した名前がバインドされていない場合、クラスとしてインスタンスを生成します。下記では、’Foo’という名前のバインドは行っていませんが、Fooクラスのインスタンスを返します。make()メソッドの第2引数に連想配列を渡すと、生成するインスタンスのコンストラクタに引数として渡されます。

$foo = $app->make('Foo'); // message = ''
$foo = $app->make('Foo', ['Hello']); // message = 'Hello'

IoC コンテナは、AraryAccessインターフェースを実装しているので、配列としてアクセスすると、内部でmake()メソッドを実行して、該当インスタンスを返します。

$foo = $app['Foo']; // message = ''

コンテナにファクトリをバインド

コンテナにインスタンスを生成するファクトリをバインドしてみます。バインドを行うメソッドはいくつかあり、それぞれ用途に応じて使い分けます。

bind()

インスタンスを生成するファクトリをバインドします。make()メソッド実行毎にファクトリが実行され、インスタンスが生成されます。

$app->bind('foo_bind', 'Foo');
$foo = $app->make('foo_bind');
$app->bind('foo_bind', function($app) {
    return new Foo('foo_bind');
});
$foo = $app->make('foo_bind');

bindShared()

bind()と同じですが、make()実行時に生成されたインスタンスをコンテナが保持します。再度make()を実行した際は同じインスタンスを返します。下記コードでは、$foo1$foo2は同じインスタンスです。

$app->bindShared('foo_bind_shared', function($app) {
    return new Foo('foo_bind_shared');
});
$foo1 = $app->make('foo_bind_shared');
$foo2 = $app->make('foo_bind_shared');

bindIf()

コンテナに同じ名前のバインドが無ければ、バインドします。すでにあれば何も行いません。

$app->bindIf('foo_bind', function($app) {
    return new Foo('foo_bind_if');
});
$foo = $app->make('foo_bind'); // 変わっていない

instance()

生成したインスタンスをバインドします。make()メソッド実行時は、常にこのインスタンスを返します。

$app->instance('foo_instance', new Foo('instance'));
$foo1 = $app->make('foo_instance'); // message == 'instance'
$foo2 = $app->make('foo_instance'); // message == 'instance'

singleton()

bindShared()と同じくmake()実行時に生成したインスタンスをコンテナが保持するので、以後同じインスタンスを返します。

$app->singleton('foo_singleton', 'Foo');
$foo1 = $app->make('foo_singleton'); // message == 'foo_singleton'
$foo2 = $app->make('foo_singleton'); // message == 'foo_singleton'
assert($foo1 === $foo2);
$app->singleton('foo_singleton_closure', function($app) {
    return new Foo('foo_singleton_closure');
});
$foo1 = $app->make('foo_singleton_clusure'); // message == 'foo_singleton_closure'
$foo2 = $app->make('foo_singleton_closure'); // message == 'foo_singleton_closure'
assert($foo1 === $foo2);

alias()

コンテナにバインドした名前に別名を付けます。

$app->share('bar', 'foo');
$bar = $app->make('bar'); // message == 'foo'

extend()

コンテナにバインドされたファクトリを拡張します。クロージャの第1引数に元にバインドされたファクトリが返すインスタンスが渡されるので、追加処理を記述して、そのインスタンスを返すようにします。

$app->extend('foo_bind', function($foo, $app) {
    $foo->extend = 'ex';
    return $foo;
});
$foo = $app->make('foo_bind'); // message == 'foo_bind', extend = 'ex'

連想配列として代入

make()メソッドと同じく連想配列として代入することでバインドすることができます。この場合、bind()メソッドの実行と同じ扱いになります。

$app['foo_set'] = function() {
  return Foo('foo_set');
};
$foo = $app->make('foo_set');

具象クラスをインジェクトするDI

では IoC コンテナを使った DI を行ってみます。サンプルのソースは以下です。

このソースでは、HelloクラスとBarクラスがあり、Barクラスではコンストラクタで、Helloクラスのインスタンスを必要としています。

IoC コンテナのmake()メソッドを使って Bar クラスのインスタンスを取得しようとすると、コンテナがリフレクションを使って、コンストラクタの引数を判別して、Helloクラスのインスタンを生成し、Barクラスのコンストラクタに渡してくれます。

結果として、Helloクラスのインスタンスを保持したBarクラスのインスタンスを取得することができます。

このように具象クラスであればコンテナへのバインドを行わなくても、よしなにインスタンスをインジェクトしてくれます。

インターフェースをインジェクトするDI

次は、インスタンスを生成するファクトリをコンテナにバインドしてDIを行います。

サンプルソースを変更しました。Greetableインターフェースを定義して、Helloクラスはこのインターフェースをimplementしています。Barクラスのコンストラクタでは、Helloクラスではなく、Greetableインターフェースのインスタンスを引数として取るように変更しました。

Barクラスを$app->make('bar')で取得しようとするとIlluminate\Container\BindingResolutionExceptionという例外が発生します。これはコンストラクタで必要としているGreetableはインターフェースのため、インスタンス化ができないためです。

これを解決するにはいくつかの方法があります。

Greetableという名前で取得できるインスタンスをコンテナにバインドしておきます。ここではHelloクラスを指定しています。これにより、BarクラスのコンストラクタにはHelloクラスのインスタンスがセットされるようになります。

$app->bind('Greetable', 'Hello');
$app->make('Bar')->say();

Greetableという名前にHelloクラスのインスタンスを生成するファクトリを指定することもできます。

$app->bind('Greetable', function($app) {
    return new Hello();
});
$app->make('Bar')->say();

もちろん、Barという名前でBarクラスのファクトリをバインドする方法もokです。

$app->bind('Bar', function($app) {
    return new Bar($app->make('Hello'));
});
$app->make('Bar')->say();

インジェクトするクラスを変える

IoC コンテナを使って、DI できるようにしておくと、インジェクトするクラスを簡単に変更することができます。

Helloクラスの代わりにGreetableを実装したByeクラスをインジェクトするには、下記のようにコンテナのバインドを変更するだけです。Barクラスの実装は変更する必要がありません。

class Bye implements Greetable
{
    /**
     *
     */
    public function greet()
    {
        echo 'Bye';
    }
}

$app->bind('Greetable', 'Bye');
$app->make('Bar')->say(); // Bye

おわりに

Laravel に実装されている IoC コンテナの使い方を見てみました。

IoC コンテナは、Laravelを構成する重要な機能なので、使い方を理解しておくとフレームワークへの理解も深まります。

コンテナを連想配列ライクに利用すると Pimple と似ているので、Pimple を使ったことがある人には馴染みやすいかもしれませんね。

(Laravel 作者Taylor Otwell氏の著書(Laravel: From Apprentice To Artisanでは「Laravel の IoC コンテナは、のPimple IoC コンテナとそのまま置き 換えることができます。」と書かれていたりします:D)

  • コメント (Close): 0
  • トラックバック (Close): 0

Home > アーカイブ > 2014-02

検索
フィード
メタ情報

Return to page top