Edited at
LaravelDay 3

Laravel5 Testing, use Contracts

More than 3 years have passed since last update.


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でもクラスの入れ替えは自由にできましたが、

その敷居と実装方法がさらに広がったという印象です

DBファサードをdoctrine/dbalにしてしまう例

ではこの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();
}

}

長いので一部抜粋

用意されているアプリケーションプロバイダーで今作ったダミークラスに変更します


app/Providers/AppServiceProvider.php

    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\ApplicationregisterCoreContainerAliasesに、

分かる様に記述されましたので、いつでもどこでも確認できます!

明日は @localdisk さんの[Laravel Socialite の話(多分)]です!!