契約
イントロダクション
Laravelの契約とはインターフェイスのことで、フレームワークにより提供されているコアサービスを定義したものです。例えばIlluminate\Contracts\Queue\Queue
契約はジョブをキューするために必要なメソッドを定義しており、一方Illuminate\Contracts\Mail\Mailer
契約はメール送信に必要なメソッドを定義しています。
それぞれの契約はフレームワークにより提供されている実装と対応しています。たとえばLaravelは多様なドライバと共に、キューの実装を提供しており、メーラーの実装は、SwiftMailerです。
Laravelの全契約はGitHubのリポジトリーで参照できます。これは全契約を素早く参照する方法であり、同時にパッケージ開発者は個別に独立したパッケージを利用する際、参考にできるでしょう。
契約 Vs. Facades
Laravelのファサードとヘルパ関数は、タイプヒントやサービスコンテナを契約の解決に使用する必要なく、Laravelの機能を活用できる、シンプルな手法を提供しています。ほとんどの場合、各ファサードと同等の契約が用意されています。
クラスのコンストラクタで、タイプヒントを指定する必要がないファサードと異なり、契約はクラスで必要な依存を明確に定義付けることができます。ある開発者はこの方法で、明確に依存を定義することを好みますが、一方で他の開発者はファサードの利便性を楽しんでいます。
Tip!! 大抵のアプリケーションでは、好みがファサードでも、契約でも問題ないでしょう。しかし、パッケージを作成する場合は、パッケージ開発のテストのしやすさという点で、契約を使うことをしっかりと考えるべきでしょう。
いつ契約を使うか
他でも説明しているように、契約とファサードのどちらを使うかは、個人や開発チームの好みに行き着きます。契約とファサードのどちらでも、堅牢でよくテストされたLaravelアプリケーションを作成できます。クラスの責務に焦点を当てていれば、契約とファサード間の実践上の違いはとても小さいことに気がつくでしょう。
However, you may still have several questions regarding contracts. For example, why use interfaces at all? Isn't using interfaces more complicated? Let's distil the reasons for using interfaces to the following headings: loose coupling and simplicity.
疎結合
最初にキャッシュの実装とがっちり結合したコードをレビューしてみましょう。次のコードをご覧ください。
<?php
namespace App\Orders;
class Repository
{
/**
* キャッシュインスタンス
*/
protected $cache;
/**
* 新しいリポジトリインスタンスの生成
*
* @param \SomePackage\Cache\Memcached $cache
* @return void
*/
public function __construct(\SomePackage\Cache\Memcached $cache)
{
$this->cache = $cache;
}
/**
* 注文をIDから取得
*
* @param int $id
* @return Order
*/
public function find($id)
{
if ($this->cache->has($id)) {
//
}
}
}
このクラスのコードは、使用しているキャッシュの実装ときつく結合しています。つまりパッケージベンダーの具象キャッシュクラスに依存しているために、結合が強くなっています。パッケージのAPIが変更されたら、同時にこのコードも変更しなくてはなりません。
キャッシュの裏で動作している技術(Memcached)を別のもの(Redis)へ置き換えたくなれば、リポジトリーを修正する必要があるというのは起こり得ます。リポジトリーは誰がデータを提供しているかとか、どのように提供しているかという知識を沢山持っていてはいけません。
このようなアプローチを取る代わりに、ベンダーと関連がないシンプルなインターフェイスへ依存するコードにより向上できます。
<?php
namespace App\Orders;
use Illuminate\Contracts\Cache\Repository as Cache;
class Repository
{
/**
* キャッシュインスタンス
*/
protected $cache;
/**
* 新しいリポジトリインスタンスの生成
*
* @param Cache $cache
* @return void
*/
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}
これでコードは特定のベンダー、しかもLaravelにさえ依存しなくなりました。契約パッケージは実装も依存も含んでいないため、与えられた契約の異なった実装を簡単に記述できます。キャッシュを使用するコードを変更することなく、キャッシュ実装を置き換えることができるようになりました。
単純さ
Laravelのサービスは全てシンプルなインターフェイスの中で適切に定義されているので、サービスが提供する機能も簡単に定義できています。 契約はフレームワークの機能の簡単なドキュメントとして使えます。
それに加え、シンプルなインターフェイスに基づけばあなたのコードは簡単に理解でき、メンテナンスできるようにもなります。大きくて複雑なクラスの中でどのメソッドが使用可能かを探し求めるよりも、シンプルでクリーンなインターフェイスを参照できます。
契約使用法
では、契約の実装はどうやって入手するのでしょうか?とてもシンプルです。
Laravelでは多くのタイプのクラスがサービスコンテナを利用して依存解決されています。コントローラーを始め、イベントリスナ、フィルター、キュージョブ、それにルートクロージャもそうです。契約の実装を手に入れるには、依存を解決するクラスのコンストラクターで「タイプヒント」を指定するだけです。
例として、次のイベントハンドラをご覧ください。
<?php
namespace App\Listeners;
use App\User;
use App\Events\OrderWasPlaced;
use Illuminate\Contracts\Redis\Database;
class CacheOrderInformation
{
/**
* Redisデータベースの実装
*/
protected $redis;
/**
* 新しいイベントハンドラの生成
*
* @param Database $redis
* @return void
*/
public function __construct(Database $redis)
{
$this->redis = $redis;
}
/**
* イベントの処理
*
* @param OrderWasPlaced $event
* @return void
*/
public function handle(OrderWasPlaced $event)
{
//
}
}
イベントリスナの依存解決時に、サービスコンテナはクラスのコンストラクターで指定されているタイプヒントを読み取り、適切な値を注入します。サービスコンテナへ何かを登録する方法を学ぶには、ドキュメントを参照してください。
契約リファレンス
次の一覧表は、全Laravel契約と、同機能のファサードのクイックリファレンスです。