イントロダクション
Laravelでは多くの点で、フレームワークのコアコンポーネントの動作をカスタマイズでき、完全に置き換えることさえ可能です。例えば、ハッシュ機能はHasherInterface
契約で定義されており、あなたのアプリケーションの要件に添って実装することができます。また、Request
オブジェクトを拡張することもでき、自分の便利な「ヘルパ」メソッドを付け加えることもできます。完全に新しい認証やキャッシュ、セッションドライバーを追加することもできるのです!
一般的にLaravelは2つの方法で拡張できます。IoCコンテナで新しい実装を結合するか、「ファクトリー」デザインパターンの実装であるManager
クラスの拡張を登録するかです。この章では、フレームワークの様々な拡張方法を学び、必要なコードを試していきましょう。
Note: Laravelのコンポーネントは通常2つのうちの一方、IoC結合か
Manager
クラスのどちらかで拡張していることを覚えておいてください。Managerクラスは「ファクトリー」デザインパターンの実装として動作し、キャッシュやセッションのようなドライバーベースの機能をインスタンス化することに責任があります。
マネージャーとファクトリー
ドライバーベースのコンポーネントの作成を管理する、複数のManager
クラスをLaravelは持っています。それらは、キャッシュ、セッション、認証、キューコンポーネントです。Managerクラスはアプリケーションの設定に基づき、特定のドライバーの実装を作成することに責任を持っています。例えば、CacheManager
クラスはAPC、Memcached、ファイル、その他のキャッシュの実装を生成することができます。
各マネージャーは、新しいドライバーのリゾルバーを簡単にマネージャへ登録できるように、extend
メソッドを備えています。以降では、各カスタムドライバーのサポートを注入する例と一緒に、こうしたマネージャーを説明していきます。
Note: どうか時間を取り、
CacheManager
やSessionManager
のような、最初からLaravelに用意されている、様々なManager
クラスを調べてみてください。こうしたクラスを徹底的に読めば、Laravelの内部動作を完全に理解することができます。全Managerクラスは、Illuminate\Support\Manager
ベースクラスを拡張しており、各Managerに共通で便利な機能を提供しています。
どこで拡張するか
このドキュメントは数多くのLaravelのコンポーネントをどのように拡張するかを説明するものです。しかし、拡張のコードをどこで記述するか、皆さん迷っていると思います。他の多くの初期設定コードと同様に、拡張コードをstart
ファイルに設置することができます。キャッシュと認証の拡張はこのアプローチが多分良いでしょう。セッションのような他の拡張コードは、リクエストのライフサイクルの初めの方で記述する必要があるため、サービスプロバイダーのregister
メソッドの中に置きましょう。
キャッシュ(Cache)
Laravelのキャッシュ機能を拡張するには、CacheManager
のextend
メソッドを使用します。このメソッドは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
メソッドは、$user
のremember_token
フィールドを新しい$token
で更新します。新しいトークンは"Remember
me"によるログインが成功した時に生成される真新しいトークンでも良いですし、もしくはユーザーがログアウトした時はnullでもかまいません。
retrieveByCredentials
メソッドは、アプリケーションにログインするために、Auth::attempt
メソッドに渡された、ログイン情報の配列を受け取ります。メソッドはユーザー情報に一致するユーザーが、裏にある持続的なストレージに存在しているか「クエリー」する必要があります。典型的にメソッドは、"where"条件で$credentials['username']
のクエリーを実行することでしょう。このメソッドでパスワードのバリデーションや認証を試みてはいけません。
validateCredentials
メソッドは指定された$user
とユーザーを認証するための$credentials
を比較する必要があります。例えば、このメソッドでは
$user->getAuthPassword()
文字列と、Hash::make
した$credentials['password']
を比較する必要があるでしょう。
これでUserProviderInterface
のメソッドをそれぞれ学習し終えました。今度は、UserInterface
に注目していきましょう。プロバイダーはこのインターフェイスの実装をretrieveById
とretrieveByCredentials
から、リターンしなくてはならないことを覚えておいてください。
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
インスタンスを生成する時、いつもこのクラスを使用します。便利なことに、いつでもカスタムリクエストクラスが利用できます。例えば、ユニットテストの中でも使用可能です!