Contracts?
Laravelを熟知する為に役立つといっても過言ではない、
ContractsというものがLaravel5から追加されました
これは、Laravelを構成しているコアクラスで利用されているものを
インターフェースとして定義したものです
つまり、これらのインターフェースを実装しているクラスのほとんどは
ユーザーが自由に入れ替える事が4よりも更に簡単になります
いままでもインターフェースでしたが、ソースを追わなくても分かる様になりました
それぞれのクラス、ファサードとContractsの関連は下記のような感じになります
Contracts | Facades |
---|---|
Illuminate\Contracts\Auth\Authenticator | Auth |
Illuminate\Contracts\Auth\PasswordBroker | Password |
Illuminate\Contracts\Cache\Repository | Cache |
Illuminate\Contracts\Cache\Factory | Cache::driver() |
Illuminate\Contracts\Config\Repository | Config |
Illuminate\Contracts\Container\Container | App |
Illuminate\Contracts\Cookie\Factory | Cookie |
Illuminate\Contracts\Cookie\QueueingFactory | Cookie::queue() |
Illuminate\Contracts\Encryption\Encrypter | Crypt |
Illuminate\Contracts\Events\Dispatcher | Event |
一部抜粋 | |
公式Contract Reference | |
日本語訳Contract Reference |
実装例
Authサービスプロバイダーの例
$this->app->singleton('auth.driver', function($app) {
return $app['auth']->driver();
}
);
Authファサードは、コンテナに'auth'という名前で登録されたクラスの
サービスロケーターとして機能している訳ですが、
'auth.dirver'という名前で $app['auth'] のdriver()メソッドで返却される
オブジェクトがバインドされていますが、
ここで返却されるオブジェクトは
Illuminate\Contracts\Auth\Guard
インターフェースを実装したクラスです
これまで利用していた\Auth::driver()
以降の、
例えば
attemptやloginといったメソッドがこのContractsで定義されているものとなります
ほか認証クラスを構成しているクラス群も
これまでのインターフェースからContaracts
に定義されているものへと変更されました
これを利用して実装されているクラスは難なく独自にものや、
それぞれの用途にあったものへ変更する事が出来る様になったというわけです
もちろんこれまでのLaravelでもクラスの入れ替えは自由にできましたが、
その敷居と実装方法がさらに広がったという印象です
ではこのContaractsとFacadesとの関連性をなんとなく理解したところで、
使いたい所で利用してみましょう
Authをコントローラーでメソッドインジェクションで利用してみます
class SampleController extends Controller
{
/**
* @param \Illuminate\Contracts\Auth\Guard $guard
*/
public function __construct(Guard $guard)
{
var_dump(get_class($guard));
}
}
フレームワークのデフォルトですでにIlluminate\Auth\Guard
が結びついてるはずです
これまでLaravelの特徴であったファサードを全面に押し出さず、
メソッドインジェクションも実装された事によりすっきりした印象です
もちろんヘルパー等を入れなくても補完は効く様になり、
内部の実装自体もよりシンプルになるので、見通しもさらによくなるはず
ファサードの方が楽なのに、なんでこれがいいの?
主にこれはパッケージの開発者や、コアを作り替えたりする中、上級者向けな気がしてますが、
初学者やそこまでガッツリ改造しなくても十分、という方は
これまで通りファサードを利用した実装を選んだ方がスムーズだと思います
パッケージ開発で、且つフレームワーク本体を拡張する様なものや、
認証やセッションドライバーなどを拡張、追加する様なパッケージを公開などする場合には、
これらのインターフェースを利用して実装する事で、
今後のバージョンアップや、
実装が変更された場合にも必要最小限の変更などに納める事ができます(たぶん)
よくわかんねぇよ!!!
ダミーの認証を作ってみます
ドライバーはデフォルトのままですが、ドライバーを無視したクラスに変えます
まずはuser()はIlluminate\Contracts\Auth\Authenticatable
を実装しなければならないので
適当に実装します
phpstormだと楽できて良いです
namespace App\Authenticate;
use Illuminate\Contracts\Auth\Authenticatable;
class Dummy implements Authenticatable
{
/**
* Get the unique identifier for the user.
* @return mixed
*/
public function getAuthIdentifier()
{
return 1;
}
/**
* Get the password for the user.
* @return string
*/
public function getAuthPassword()
{
return '1';
}
/**
* Get the token value for the "remember me" session.
* @return string
*/
public function getRememberToken()
{
return 'dummy';
}
/**
* Set the token value for the "remember me" session.
* @param string $value
* @return void
*/
public function setRememberToken($value)
{
// none
}
/**
* Get the column name for the "remember me" token.
* @return string
*/
public function getRememberTokenName()
{
return 'dummy';
}
}
次にIlluminate\Contracts\Auth\Guard
namespace App\Authenticate;
use Illuminate\Contracts\Auth\Guard;
class NoGuard implements Guard
{
/**
* Get the currently authenticated user.
*
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function user()
{
return new Dummy();
}
}
長いので一部抜粋
用意されているアプリケーションプロバイダーで今作ったダミークラスに変更します
public function register()
{
$this->app->bind('Illuminate\Contracts\Auth\Guard', 'App\Authenticate\NoGuard');
}
ドライバーすら作らずにさくっと任意のものに変更できます
簡単!それでいてフレームワークには影響を与えません
肯定的な意見も否定的な意見も取り入れてより柔軟性を増した作りへと変わりました
先ほどのメソッドインジェクションで使われるクラスが変更されているはずです
応用してContextual Binding
開発でAuthを使い分けたいとか色々困る事もあると思いますが、
Contratcs
を使ってこんな事に応用する事も出来ます
普通のインターフェースでもなんでもいいんですが、一応
class SampleController extends Controller
{
/**
* @param \Illuminate\Contracts\Auth\Guard $guard
*/
public function __construct(Guard $guard)
{
var_dump(get_class($guard));
}
/**
* @param \Illuminate\Contracts\Auth\Guard $guard
*/
public function sample(Guard $guard)
{
var_dump(get_class($guard));
}
}
サービスプロバイダーに下記のものを追記してみます
public function register()
{
$this->app->bind('Illuminate\Contracts\Auth\Guard', 'App\Authenticate\NoGuard');
// これ
$this->app->when('App\Http\Controllers\SampleController')
->needs('Illuminate\Contracts\Auth\Guard')
->give('Illuminate\Auth\Guard');
}
bindで解決されるものをクラスによって変更できる機能が追加されました
この場合は通常変更するものはApp\Authenticate\NoGuard
として、
App\Http\Controllers\SampleController
のコンストラクタでは
Illuminate\Auth\Guard
をくださいよ、と指示します
今まで複数の認証はパッケージに頼っていた方も多いと思いますが、
(実は昔からAuth::driver()->user()
として用途によって変更して使えたんです)
手軽になりました
サンプルとして認証クラスとContracts
を例にしましたが、
コンポーネントのほとんどがこういった仕組みで構成する様になりましたので、
好みでどんどん作り替えられます
テスト
その他に加えて大きく利便性があがるものはテストのし易さです!
もともとLaravelはテストし易い方だと思いますが、
これまでファサードを利用したくクラスは、ほとんどの方がMockery
を利用しているはずです
もちろんMockeryも強力でかなり楽なのでLaravel以外でも多用してますが、
テストをするのも少し面倒なときもありました
\Auth::shouldReceive('attempt')->once()->andReturn();
Mockeryが統合されていますので、これまで通りにテストを書く事が出来ます
ただし、自由に書ける様になるにはある程度Mockeryを勉強しなければなりません
ほとんどの方がテストコードを楽に書きたい!とおもうはずです
Contaracts
を思い出してみましょう
テストを書くのがググッと簡単にそして自由になっています
use App\Http\Controllers\SampleController;
class ControllerTest extends TestCase
{
/** @var \App\Http\Controllers\SampleController */
protected $controller;
public function setUp()
{
$this->controller = new SampleController();
}
public function testSample()
{
$this->assertInstanceOf(
'Illuminate\View\View', $this->controller->sample(new DummyGuard()
);
}
}
スタブのクラスを用意するだけで、
フレームワークを意識させないくらいの自然なテストが書けます
今まで通りsetUpの中でbindで差し替える事も出来ますが、
メソッドインジェクションが実装されたため、
App::make()
を使う機会がサービスプロバイダー、
もしくはLaravel以外のライブラリを使う時くらいでしょうか
ググッと敷居が低くなりました
それと同時にパッケージ開発者はこの仕組みをしっかりと理解して
テストコードを書かなければなりません!
最後に
これらの詳細な関連性は
\Illuminate\Foundation\Application
のregisterCoreContainerAliases
に、
分かる様に記述されましたので、いつでもどこでも確認できます!
明日は @localdisk さんの[Laravel Socialite の話(多分)]です!!