Контракты

Введение

Контракты в Laravel — это набор интерфейсов, которые описывают основной функционал, предоставляемый фреймворком. Например, контракт Illuminate\Contracts\Queue\Queue определяет методы, необходимые для организации очередей, в то время как контракт Illuminate\Contracts\Mail\Mailer определяет методы, необходимые для отправки электронной почты.

Каждый контракт имеет свою реализацию во фреймворке. Например, Laravel предоставляет реализацию Queue с различными драйверами и реализацию Mailer, использующую SwiftMailer.

Все контракты Laravel живут в своих собственных репозиториях GitHub. Это ссылка на все доступные контракты, а также на один отдельный пакет, который может быть использован разработчиками пакетов.

Контракты или фасады?

Фасады Laravel и хелперы дают простой способ использования сервисов Laravel без необходимости типизирования и извлечения контрактов из сервис-контейнера. В большинстве случаев у каждого фасада есть эквивалентный контракт.

В отличие от фасадов, которые не требуют того, чтобы вы запрашивали их в конструкторе вашего класса, контракты позволяют вам определить конкретные зависимости для ваших классов. Некоторые разработчики предпочитают именно так явно определять свои зависимости, поэтому предпочитают использовать контракты, а другие разработчики наслаждаются удобством фасадов.

Для большинства приложений неважно, что вы выберете — фасады или контракты. Но если вы создаёте пакет, то вам надо использовать контракты, так как в этом случае их проще тестировать.

Когда использовать контракты

Это обсуждается повсюду, и большинство дискуссий сводятся к тому, что использование контрактов или фасадов — это дело вкуса или предпочтений вашей команды разработчиков. И те, и другие можно использовать для создания надёжных, проверенных Laravel-приложений. Пока вы сохраняете границы ответственности вашего класса узкими, вы сможете заметить всего несколько практических различий между использованием контрактов и фасадов.

Однако, у вас по-прежнему могут остаться некоторые вопросы о контрактах. Например, зачем вообще нужны интерфейсы? Разве использовать их не слишком сложно? Определим причины использования интерфейсов как следующие: это слабая связность и упрощение кода.

Слабая связность

Для начала рассмотрим код с сильной связностью с реализацией кэша:

<?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;
    }

    /**
     * Получение заказа Order по ID.
     *
     * @param  int  $id
     * @return Order
     */
    public function find($id)
    {
        if ($this->cache->has($id))    {
            //
        }
    }
}

В этом классе код сильно связан с реализацией кэша, потому что мы зависим от конкретного класса Cache данного пакета. Если 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, а также эквивалентные им фасады:

Контракт Соответствующий фасад
Illuminate\Contracts\Auth\Access\Authorizable  
Illuminate\Contracts\Auth\Access\Gate Gate
Illuminate\Contracts\Auth\Authenticatable  
Illuminate\Contracts\Auth\CanResetPassword  
Illuminate\Contracts\Auth\Factory Auth
Illuminate\Contracts\Auth\Guard Auth::guard()
Illuminate\Contracts\Auth\PasswordBroker Password::broker()
Illuminate\Contracts\Auth\PasswordBrokerFactory Password
Illuminate\Contracts\Auth\StatefulGuard  
Illuminate\Contracts\Auth\SupportsBasicAuth  
Illuminate\Contracts\Auth\UserProvider  
Illuminate\Contracts\Bus\Dispatcher Bus
Illuminate\Contracts\Bus\QueueingDispatcher Bus::dispatchToQueue()
Illuminate\Contracts\Broadcasting\Factory Broadcast
Illuminate\Contracts\Broadcasting\Broadcaster Broadcast::connection()
Illuminate\Contracts\Broadcasting\ShouldBroadcast  
Illuminate\Contracts\Broadcasting\ShouldBroadcastNow  
Illuminate\Contracts\Cache\Factory Cache
Illuminate\Contracts\Cache\Lock  
Illuminate\Contracts\Cache\LockProvider  
Illuminate\Contracts\Cache\Repository Cache::driver()
Illuminate\Contracts\Cache\Store  
Illuminate\Contracts\Config\Repository Config
Illuminate\Contracts\Console\Application  
Illuminate\Contracts\Console\Kernel Artisan
Illuminate\Contracts\Container\Container App
Illuminate\Contracts\Cookie\Factory Cookie
Illuminate\Contracts\Cookie\QueueingFactory Cookie::queue()
Illuminate\Contracts\Database\ModelIdentifier  
Illuminate\Contracts\Debug\ExceptionHandler  
Illuminate\Contracts\Encryption\Encrypter Crypt
Illuminate\Contracts\Events\Dispatcher Event
Illuminate\Contracts\Filesystem\Cloud Storage::cloud()
Illuminate\Contracts\Filesystem\Factory Storage
Illuminate\Contracts\Filesystem\Filesystem Storage::disk()
Illuminate\Contracts\Foundation\Application App
Illuminate\Contracts\Hashing\Hasher Hash
Illuminate\Contracts\Http\Kernel  
Illuminate\Contracts\Logging\Log Log
Illuminate\Contracts\Mail\MailQueue Mail::queue()
Illuminate\Contracts\Mail\Mailable  
Illuminate\Contracts\Mail\Mailer Mail
Illuminate\Contracts\Notifications\Dispatcher Notification
Illuminate\Contracts\Notifications\Factory Notification
Illuminate\Contracts\Pagination\LengthAwarePaginator  
Illuminate\Contracts\Pagination\Paginator  
Illuminate\Contracts\Pipeline\Hub  
Illuminate\Contracts\Pipeline\Pipeline  
Illuminate\Contracts\Queue\EntityResolver  
Illuminate\Contracts\Queue\Factory Queue
Illuminate\Contracts\Queue\Job  
Illuminate\Contracts\Queue\Monitor Queue
Illuminate\Contracts\Queue\Queue Queue::connection()
Illuminate\Contracts\Queue\QueueableCollection  
Illuminate\Contracts\Queue\QueueableEntity  
Illuminate\Contracts\Queue\ShouldQueue  
Illuminate\Contracts\Redis\Factory Redis
Illuminate\Contracts\Routing\BindingRegistrar Route
Illuminate\Contracts\Routing\Registrar Route
Illuminate\Contracts\Routing\ResponseFactory Response
Illuminate\Contracts\Routing\UrlGenerator URL
Illuminate\Contracts\Routing\UrlRoutable  
Illuminate\Contracts\Session\Session Session::driver()
Illuminate\Contracts\Support\Arrayable  
Illuminate\Contracts\Support\Htmlable  
Illuminate\Contracts\Support\Jsonable  
Illuminate\Contracts\Support\MessageBag  
Illuminate\Contracts\Support\MessageProvider  
Illuminate\Contracts\Support\Renderable  
Illuminate\Contracts\Support\Responsable  
Illuminate\Contracts\Translation\Loader  
Illuminate\Contracts\Translation\Translator Lang
Illuminate\Contracts\Validation\Factory Validator
Illuminate\Contracts\Validation\ImplicitRule  
Illuminate\Contracts\Validation\Rule  
Illuminate\Contracts\Validation\ValidatesWhenResolved  
Illuminate\Contracts\Validation\Validator Validator::make()
Illuminate\Contracts\View\Engine  
Illuminate\Contracts\View\Factory View
Illuminate\Contracts\View\View View::make()