1. Home
  2. PHP
  3. Laravel
  4. LaravelのContracts(契約)とは。を探す旅に出る。

LaravelのContracts(契約)とは。を探す旅に出る。

  • 公開日
  • 更新日
  • カテゴリ:Laravel
  • タグ:PHP,Laravel
LaravelのContracts(契約)とは。を探す旅に出る。

まずはLaravelにおける「契約」についての説明を拾う。

Laravelの契約とはインターフェイスのことで、フレームワークにより提供されているコアサービスを定義したものです。

Laravelのファサードとヘルパ関数は、タイプヒントやサービスコンテナを契約の解決に使用する必要なく、Laravelの機能を活用できる、シンプルな手法を提供しています。 ほとんどの場合、各ファサードと同等の契約が用意されています。

クラスのコンストラクタで、タイプヒントを指定する必要がないファサードと異なり、契約はクラスで必要な依存を明確に定義付けることができます。

引用元:readouble.com - Laravel 6.x 契約

  • タイプヒントとか必要な時(注入時とか)に明示的に指定できる
  • 契約で宣言してあげることで実装側に依存しなくなるので疎結合になる

公式ドキュメントにあるキャッシュの例では、Memcached をキャッシュに使っているパッケージを使用していて、タイプヒンティングで実装クラスを指定している事で結合度が強くなっている状態から、MemcachedによるCacheクラスを定義する時に実装したインターフェースを指定してあげることで疎結合になる。
(ここではどの実装クラスかを知らなくて良くなる=パッケージ変えてもここを変更する必要がなくなる)
という事を言っている。

ここからちょっと実践してみる。段階的に。

メンバープロフィールページのURLを作成するクラスがあったとして、まずは何も考慮せずに定義する。

profileUrlMaker.php
<?php
declare(strict_types=1);

namespace App\Generator;

use App\User;

class profileUrlMaker
{
    public function getUrl(User $user): string
    {
        $path = sprintf('/member/%d/profile', $user->id);
        $url = config('app.env') === 'production' ? secure_url($path) : url($path);
        return $url;
    }
}

使用する側はこんな感じ

SampleController.php
// 以下のユーザがいるとして
$user = new User();
$user->id = 1;

$myPageUrlMaker = new profileUrlMaker();

echo $myPageUrlMaker->getUrl($user);
// => http://localhost/member/1/profile

で、profileUrlMaker.php に関して、これでやりたい事は実現出来ているけれど、configヘルパや urlヘルパがべったりなので、ここらへんもっときれいにしようということで、これらを外から注入(DI)しましょう、という事で以下。

profileUrlMaker.php
<?php
declare(strict_types=1);

namespace App\Generator;

use App\User;
use Illuminate\Routing\UrlGenerator;
use Illuminate\Config\Repository;

class profileUrlMaker
{
    protected $url;
    protected $env;

    public function __construct(UrlGenerator $url, Repository $config)
    {
        $this->url = $url;
        $this->env = $config->get('app.env');
    }

    public function getUrl(User $user): string
    {
        $path = sprintf('/member/%d/profile', $user->id);
        $url = $this->env === 'production' ? $this->url->secure($path) : $this->url->to($path);
        return $url;
    }
}

使用する側では依存の自動解決の為にサービスコンテナからインスタンスを取るように変更。

SampleController.php
// サービスコンテナからインスタンスを取得
$myPageUrlMaker = app(profileUrlMaker::class);

echo $myPageUrlMaker->getUrl($user);
// => http\://localhost/member/1/profile

外から注入するようにしたのでいい感じになったね。と言いたいところだけど、まさにこの状態が Memcachedの例と同じ状態になっていて、それは型宣言(ここではuse文の方)を見るとわかる。

use Illuminate\Routing\UrlGenerator;
use Illuminate\Config\Repository;

これらはそれぞれ configや urlの実装クラスなので、結合度が高いままになってしまっている。

なのでここを、Contracts(契約)で宣言するようにする。

profileUrlMaker.php(use文以外は変更なし)
// use Illuminate\Routing\UrlGenerator;
// ↓ 変更
use Illuminate\Contracts\Routing\UrlGenerator;

// use Illuminate\Config\Repository;
// ↓ 変更
use Illuminate\Contracts\Config\Repository;

これで、フレームワークにより提供されているコアサービスを定義したインターフェース(=契約)で依存を明確に定義付けることができた。

LaravelのContracts(契約)とは

  • Laravelのファサードやヘルパ関数などのコアサービスで実装されているインターフェースの事。
  • 型宣言には実装側ではなく契約を使用する事で疎結合な関係になり保守性が向上する。

参考

例えばファサードとか、ここを知らないとフツーにファサードの実装側で宣言してしまうだろうから、この機会に知れてよかった。

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級