イントロダクション

Laravelでは多くの点で、フレームワークのコアコンポーネントの動作をカスタマイズでき、完全に置き換えることさえ可能です。例えば、ハッシュ機能はHasherInterface契約で定義されており、あなたのアプリケーションの要件に添って実装することができます。また、Requestオブジェクトを拡張することもでき、自分の便利な「ヘルパー」メソッドを付け加えることもできます。完全に新しい認証やキャッシュ、セッションドライバーを追加することもできるのです!

一般的にLaravelは2つの方法で拡張できます。IoCコンテナで新しい実装を結合するか、「ファクトリー」デザインパターンの実装であるManagerクラスの拡張を登録するかです。この章では、フレームワークの様々な拡張方法を学び、必要なコードを試していきましょう。

Note: Laravelのコンポーネントは通常2つのうちの一方、IoC結合かManagerクラスのどちらかで拡張していることを覚えておいてください。Managerクラスは「ファクトリー」デザインパターンの実装として動作し、キャッシュやセッションのようなドライバーベースの機能をインスタンス化することに責任があります。

マネージャーとファクトリー

ドライバーベースのコンポーネントの作成を管理する、複数のManagerクラスをLaravelは持っています。それらは、キャッシュ、セッション、認証、キューコンポーネントです。Managerクラスはアプリケーションの設定に基づき、特定のドライバーの実装を作成することに責任を持っています。例えば、CacheManagerクラスはAPC、Memcached、ファイル、その他のキャッシュの実装を生成することができます。

各マネージャーは、新しいドライバーのリゾルバーを簡単にマネージャへ登録できるように、extendメソッドを備えています。以降では、各カスタムドライバーのサポートを注入する例と一緒に、こうしたマネージャーを説明していきます。

Note: どうか時間を取り、CacheManagerSessionManagerのような、最初からLaravelに用意されている、様々なManagerクラスを調べてみてください。こうしたクラスを徹底的に読めば、Laravelの内部動作を完全に理解することができます。全Managerクラスは、Illuminate\Support\Managerベースクラスを拡張しており、各Managerに共通で便利な機能を提供しています。

どこで拡張するか

このドキュメントは数多くのLaravelのコンポーネントをどのように拡張するかを説明するものです。しかし、拡張のコードをどこで記述するか、皆さん迷っていると思います。他の多くの初期設定コードと同様に、拡張コードをstartファイルに設置することができます。キャッシュと認証の拡張はこのアプローチが多分良いでしょう。セッションのような他の拡張コードは、リクエストのライフサイクルの初めの方で記述する必要があるため、サービスプロバイダーのregisterメソッドの中に置きましょう。

キャッシュ(Cache)

Laravelのキャッシュ機能を拡張するには、CacheManagerextendメソッドを使用します。このメソッドはManagerへカスタムドライバーのリゾルバーを結合するために使用され、全Managerクラスで使用できます。例えば、"mongo"という名前のキャッシュドライバーを登録するには、次のようになります。

Cache::extend('mongo', function($app)
{
    // Illuminate\Cache\Repositoryインスタンスを返す…
});

extendメソッドの最初の引数は、ドライバーの名前です。この名前は、app/config/cache.php設定ファイルの中のdriverオプションと対応しています。2つ目の引数はクロージャーで、Illuminate\Cache\Repositoryインスタンスをリターンしなければなりません。クロージャーは、Illuminate\Foundation\Applicationのインスタンスであり、IoCコンテナでもある、$appインスタンスへ渡されます。

カスタムキャッシュドライバーを作成するために、最初にIlluminate\Cache\StoreInterface契約を実装する必要があります。ですから、MongoDBキャッシュの実装は、次のようになるでしょう。

class MongoStore implements Illuminate\Cache\StoreInterface {

    public function get($key) {}
    public function put($key, $value, $minutes) {}
    public function increment($key, $value = 1) {}
    public function decrement($key, $value = 1) {}
    public function forever($key, $value) {}
    public function forget($key) {}
    public function flush() {}

}

それぞれのメソッドをMongoDB接続を使用して実装する必要があります。実装を完了したら、カスタムドライバーの登録も完成させましょう。

use Illuminate\Cache\Repository;

Cache::extend('mongo', function($app)
{
    return new Repository(new MongoStore);
});

上の例で見るように、カスタムキャッシュドライバーを作成する場合、 Illuminate\Cache\Repository をベースとして使用できます。通常、自分でリポジトリークラスを作成する必要はありません。

カスタムキャッシュドライバーコードをどこへ設置しようか迷っているなら、Packagistで使用できるようにすることを考えてください!もしくは、アプリケーションのメインフォルダーに、Extensions名前空間を作成することもできます。例えば、アプリケーション名がSnappyであるなら、キャッシュ拡張をapp/Snappy/Extensions/MongoStore.phpとして設置します。しかし、Laravelは厳格なアプリケーション構造を持っていないので、好みに合わせ、自由に構造を決めて良いことを覚えておきましょう。

Note: ちょっとしたコードをどこに設置するか、未だ迷っているのでしたら、いつもサービスプロバイダーを考慮しましょう。既に学んだ通り、サービスプロバイダーをフレームワークの拡張を組織立てるために使うことは、コードを組織立てる方法として優れています。

セッション(Session)

Laravelにカスタムセッションドライバーを拡張するのは、キャッシュシステムを拡張するのと同様、簡単です。カスタムコードを登録するため、セッションでもextendメソッドを使用します。

Session::extend('mongo', function($app)
{
    // ReturnSessionHandlerInterfaceの実装を返す
});

セッションをどこで拡張するか

キャッシュや認証のような拡張とは異なり、セッションの拡張は別のやり方を行います。セッションはリクエストのライフサイクルのとても早い段階から開始するため、startファイルの中で拡張しても遅すぎます。その代わりに、サービスプロバイダーが必要になります。サービスプロバイダーのregisterメソッドの中にセッションの拡張コードを設置して下さい。そしてそのプロバイダーをproviders設定配列中で、Illuminate\Session\SessionServiceProviderより後ろで設定して下さい。

セッションの拡張をコーディングする

カスタムセッションドライバーは、SessionHandlerInterfaceを実装する必要があることに注意してください。このインターフェイスは、PHP 5.4のコアに含まれています。もしあなたが、PHP 5.3を使用しているなら、5.4とのコンパチビリティのために、Laravelが代わって定義します。このインターフェイスは、実装する必要のあるシンプルないくつかのメソッドを含んでいます。MongoDB実装のスタブは、以下のようなコードになります。

class MongoHandler implements SessionHandlerInterface {

    public function open($savePath, $sessionName) {}
    public function close() {}
    public function read($sessionId) {}
    public function write($sessionId, $data) {}
    public function destroy($sessionId) {}
    public function gc($lifetime) {}

}

これらのメソッドは、キャシュのStoreInterfaceのように、簡単に理解できないため、それぞれのメソッドを簡単に解説しましょう。

  • openメソッドは、通常ファイルベースのセッション保存システムで使用されます。Laravelには最初からfileセッションドライバーが含まれているため、このメソッドにはほとんど何も記述する必要はないでしょう。空っぽのスタブのままにしておけます。PHPがこのメソッドを私達に実装しろと要求しているのは、実際、インターフェイスの設計がまずいからに過ぎません。(後ほど説明します。)
  • closeメソッドは、openメソッドと同様に、通常無視できます。ほとんどのドライバーでは、必要ありません。
  • readメソッドは、指定された$sessionIdに関係付けられた、文字列のセッションデーターをリターンする必要があります。ドライバーからセッションデーターを取得したり、保存したりする時に、シリアライズや他のエンコードを行う必要はありません。Laravelがシリアライズを行います。
  • writeメソッドは$data文字列を$sessionIdと関連付けて、MongoDBやDynamo、その他の持続的なストレージシステムに保存する必要があります。
  • destroyメソッドは、$sessionIdに関連付けられたデーターを持続的なストレージから削除します。
  • gcメソッドは、指定されたUNIXタイムスタンプの$lifetimeより古いセッションデーターを全部破棄しなくてはなりません。MemcachedやRedisのように、自分で古いデーターを破棄するシステムでは、このメソッドは空のままにします。

SessionHandlerInterfaceを実装したら、セッションマネージャーに登録する準備ができました。

Session::extend('mongo', function($app)
{
    return new MongoHandler;
});

一度セッションドライバーを登録すれば、mongoドライバーをapp/config/session.php設定で使用できます。

Note: カスタムセッションハンドラーを書いたら、Packagistで共有することを思い出してください。

認証

キャッシュやセッション機能と同じ方法で、認証も拡張できます。既にお馴染みの、extendメソッドを今回も使用します。

Auth::extend('riak', function($app)
{
    // Return implementation of Illuminate\Auth\UserProviderInterface
});

UserProviderInterfaceの実装は、UserInterfaceの実装をMySQLやRiakなどの持続するストレージシステムより取得することだけに責任を持っています。どのようにユーザー情報が保存されていようと、どの様なタイプのクラスがユーザーを表していようと、この2つのインターフェイスのおかげで、Laravelの認証メカニズムは機能し続けることができます。

UserProviderInterfaceに注目しましょう。

interface UserProviderInterface {

    public function retrieveById($identifier);
    public function retrieveByToken($identifier, $token);
    public function updateRememberToken(UserInterface $user, $token);
    public function retrieveByCredentials(array $credentials);
    public function validateCredentials(UserInterface $user, array $credentials);

}

retrieveById関数は、典型的にはMySQLデータベースの自動増加IDのような、ユーザーを表す数字キーを受け付けます。IDに一致するUserInterfaceの実装が取得され、メソッドによりリターンされます。

retrieveByToken関数は、一意の$identifierと、remember_tokenフィールドに保存されている"Remember me"トークンにより、ユーザーを取得します。前のメソッドと同様に、UserInterfaceの実装をリターンしなくてはなりません。

updateRememberTokenメソッドは、$userremember_tokenフィールドを新しい$tokenで更新します。新しいトークンは"Remember me"によるログインが成功した時に生成される真新しいトークンでも良いですし、もしくはユーザーがログアウトした時はnullでもかまいません。

retrieveByCredentialsメソッドは、アプリケーションにログインするために、Auth::attemptメソッドに渡された、ログイン情報の配列を受け取ります。メソッドはユーザー情報に一致するユーザーが、裏にある持続的なストレージに存在しているか「クエリー」する必要があります。典型的にメソッドは、"where"条件で$credentials['username']のクエリーを実行することでしょう。このメソッドでパスワードのバリデーションや認証を試みてはいけません。

validateCredentialsメソッドは指定された$userとユーザーを認証するための$credentialsを比較する必要があります。例えば、このメソッドでは $user->getAuthPassword()文字列と、Hash::makeした$credentials['password']を比較する必要があるでしょう。

これでUserProviderInterfaceのメソッドをそれぞれ学習し終えました。今度は、UserInterfaceに注目していきましょう。プロバイダーはこのインターフェイスの実装をretrieveByIdretrieveByCredentialsから、リターンしなくてはならないことを覚えておいてください。

interface UserInterface {

    public function getAuthIdentifier();
    public function getAuthPassword();

}

このインターフェイスはシンプルです。getAuthIdentifierメソッドは、ユーザーの「主キー」を返さなくてはなりません。MySQLがバックエンドなら、ここでも自動増加主キーとなるでしょう。getAuthPasswordは、ユーザーのハッシュ済みのパスワードを返す必要があります。このインターフェイスにより、どんなORMやストレージの抽象クラスを使用してるのかに係わらず、認証システムをユーザークラスに対し動作させることを可能にしています。デフォルトでLaravelはこのインターフェイスを実装している、app/modelsディレクトリーの、Userクラスを含んでいます。ですから、実装のサンプルとして、このクラスを調べてみてください。

UserProviderInterfaceの実装を済ませたら、ついに私達の拡張をAuthファサードに登録する準備ができました。

Auth::extend('riak', function($app)
{
    return new RiakUserProvider($app['riak.connection']);
});

ドライバーをextendメソッドで登録し終えたら、app/config/auth.phpでその新しいドライバーに切り替えましょう。

IoCベースの拡張

Laravelフレームワークに含まれている、ほとんどすべてのサービスプロバイダーは、IoCコンテナにオブジェクトを結合しています。app/config/app.php設定ファイルで、アプリケーションのサービスプロバイダーのリストを見つけられます。時間があれば、プロバイダーそれぞれのソースコードを拾い読みしてください。そうすれば、それぞれのプロバイダーがフレームワークに何を付け加えているのか、同時にIoCコンテナに数々のサービスを結合するため、どんなキーが使用されているかを、もっと知ることができます。

例えば、HashServiceProviderは、IoCコンテナでhashキーと結合されており、Illuminate\Hashing\BcryptHasherインスタンスへ解決されます。あなたは自分のアプリケーションの中でIoC結合をオーバーライドすることにより、簡単にクラスを拡張したり、オーバーライドしたりできます。例えば:

class SnappyHashProvider extends Illuminate\Hashing\HashServiceProvider {

    public function boot()
    {
        App::bindShared('hash', function()
        {
            return new Snappy\Hashing\ScryptHasher;
        });

        parent::boot();
    }

}

このクラスはベースクラスのデフォルトServiceProviderではなく、 HashServiceProvider を拡張していることに注意してください。サービスプロバイダーを拡張したら、app/config/app.php設定ファイルの中の、HashServiceProviderをあなたが拡張したプロバーダー名へ交換してください。

ここまで、コンテナで結合されたコアクラスを拡張する、一般的な方法を見てきました。基本的に、コンテナ上の全てのコアクラスは、こうした方法で結合されており、オーバーライド可能です。繰りかえしますが、フレームワークに含まれているサービスプロバイダーを全般的に読んでみてください。様々なクラスに親しめますし、どのキーで結合されているか分かります。これは、Laravelがどのように一つにまとめられているのかを詳しく理解できる、素晴らしい方法です。

リクエストの拡張

Requestクラスはフレームワークのとても基礎的な部分であり、リクエストサイクルのとても早い段階でインスタンス化されるため、今までの例とは多少異なる動作をしています。

最初に、通常通りクラスを拡張します。

<?php namespace QuickBill\Extensions;

class Request extends \Illuminate\Http\Request {

    // カスタマイズした、便利なメソッドをここへ…

}

クラスを拡張したら、bootstrap/start.phpファイルを開きます。このファイルは、アプリケーションのリクエスト毎に、とても早い段階で読み込まれるファイルの一つです。Laravelの$appインスタンスを生成している、最初のアクションの実行に注目してください。

$app = new \Illuminate\Foundation\Application;

新しいインスタンスが生成されると、新しいIlluminate\Http\Requestインスタンスが生成され、IoCコンテナにrequestキーとして結合されます。ですから、「デフォルト」リクエストタイプとして使用されるカスタムクラスを指定する方法が必要です。そう、ありがたいことに、アプリケーションインスタンスのrequestClassメソッドが、これを行います!では、bootstrap/start.phpファイルの一番最初に、以下の行を付け加えてください。

use Illuminate\Foundation\Application;

Application::requestClass('QuickBill\Extensions\Request');

カスタムリクエストクラスを指定すると、LaravelはRequestインスタンスを生成する時、いつもこのクラスを使用します。便利なことに、いつでもカスタムリクエストクラスが利用できます。例えば、ユニットテストの中でも使用可能です!