Контракты
Контракты в Laravel — это набор классов-интерфейсов, определяющий некий функционал ядра фреймворка. Например, контракт Queue определяет методы работы с очередями, Mailer — методы для отправки мейлов.
Каждый контракт имеет свою реализацию (implementation) во фреймворке. Например, есть реализация Queue с различными драйверами очередей и реализация Mailer с использованием SwiftMailer.
Для удобства, контракты находятся в отдельном репозитории на гитхабе — https://github.com/illuminate/contracts.
Зачем нужны контракты?
Вы можете спросить — а для чего нужны контракты? Зачем вообще нужны интерфейсы?
Ответ — слабая связность и упрощение кода.
Слабая связность
Сначала давайте рассмотрим пример кода с сильной связностью.
namespace App\Orders; class Repository < /** * The cache. */ protected $cache; /** * Create a new repository instance. * * @param \Package\Cache\Memcached $cache * @return void */ public function __construct(\SomePackage\Cache\Memcached $cache) < $this->cache = $cache; > /** * Retrieve an Order by ID. * * @param int $id * @return Order */ public function find($id) < if ($this->cache->has($id)) < // > > >
Код этого класса тесно связан с реализацией кэширования \SomePackage\Cache\Memcached . Мы зависим и от способа кэширования ( memcached ), и от API данной библиотеки. Если мы хотим сменить кэширование с memcached на redis , нам придется вносить изменения в код класса Repository .
Чтобы такого не происходило, класс Repository не должен задумываться, кто именно предоставляет данные и как именно осуществляется запись. Давайте изменим наш класс, чтобы отвязаться от конкретной реализации и сделать его более универсальным. Для этого добавим зависимость от интерфейса кэширования.
namespace App\Orders; use Illuminate\Contracts\Cache\Repository as Cache; class Repository < /** * Create a new repository instance. * * @param Cache $cache * @return void */ public function __construct(Cache $cache) < $this->cache = $cache; > >
Этот код не связан ни с одной внешней библиотекой, в том числе с ядром фреймворка! Контракт не содержит никакой конкретной реализации кэширования, только интерфейс, и вы можете написать любую свою реализацию кэширования — используя внешние библиотеки или нет. Кроме того, теперь вы можете в любой момент легко изменить способ кэширования, просто подав другую реализацию в конструктор класса при регистрации его в сервис-контейнере в методе register() вашего сервис-провайдера.
Упрощение кода
Когда все сервисы ядра фреймворка аккуратно определены в простых интерфейсах, очень легко определить, что именно делает тот или иной сервис. Фактически, контракты являются краткой документацией к API Laravel.
Кроме того, когда вы у себя в приложении внедряете в классы зависимости от простых интерфейсов, в вашем коде легче разобраться и его проще поддерживать. Вместо того, чтобы заставлять коллег разбираться, какие методы вашего большого и сложного класса можно использовать извне, вы адресуете их к простому и понятному интерфейсу.
Таблица основных контрактов
Вот таблица соответствий контрактов Laravel 5 фасадам Laravel 4 :
| Контракт | Фасад Laravel 4.x |
|---|---|
| Illuminate\Contracts\Auth\Guard | 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 |
| Illuminate\Contracts\Filesystem\Cloud | |
| Illuminate\Contracts\Filesystem\Factory | File |
| Illuminate\Contracts\Filesystem\Filesystem | File |
| Illuminate\Contracts\Foundation\Application | App |
| Illuminate\Contracts\Hashing\Hasher | Hash |
| Illuminate\Contracts\Logging\Log | Log |
| Illuminate\Contracts\Mail\MailQueue | Mail::queue() |
| Illuminate\Contracts\Mail\Mailer | |
| Illuminate\Contracts\Queue\Factory | Queue::driver() |
| Illuminate\Contracts\Queue\Queue | Queue |
| Illuminate\Contracts\Redis\Database | Redis |
| Illuminate\Contracts\Routing\Registrar | Route |
| Illuminate\Contracts\Routing\ResponseFactory | Response |
| Illuminate\Contracts\Routing\UrlGenerator | URL |
| Illuminate\Contracts\Support\Arrayable | |
| Illuminate\Contracts\Support\Jsonable | |
| Illuminate\Contracts\Support\Renderable | |
| Illuminate\Contracts\Validation\Factory | Validator::make() |
| Illuminate\Contracts\Validation\Validator | |
| Illuminate\Contracts\View\Factory | View::make() |
| Illuminate\Contracts\View\View |
Использование контрактов
Как получить в своем классе реализацию заданного контракта? Очень просто. Достаточно в аргументах конструктора явно указать в качестве типа аргумента название соответствующего контракта — и при его создании там окажется реализация этого интерфейса. Это происходит за счет того, что почти все классы Laravel (контроллеры, слушатели событий и очередей, роуты, фильтры роутов) регистрируются в сервис-контейнере и в процессе создания экземпляра класса из контейнера в него встраиваются все определенные таким образом зависимости. Фактически все происходит автоматически, вам не приходится задумываться об этом механизме.
Например, взглянем на слушателя событий:
namespace App\Events; use App\User; use Illuminate\Contracts\Queue\Queue; class NewUserRegistered < /** * The queue implementation. */ protected $queue; /** * Create a new event listener instance. * * @param Queue $queue * @return void */ public function __construct(Queue $queue) < $this->queue = $queue; > /** * Handle the event. * * @param User $user * @return void */ public function fire(User $user) < // Queue an e-mail to the user. > >
Если вы хотите больше узнать о сервис-контейнере, прочтите соответствующий раздел документации.
Русскоязычное комьюнити
- Группа в VK
- Телеграм LaravelRUS
- Телеграм Laravel для новичков
- Телеграм LaravelPro
Обучающие ресурсы
- Laracasts
- Codecourse
- Курс Дмитрия Елисеева
- Adam Wathan
Блоги разработчиков
- Laravel News
- Freek Van der Herten
- Brent Roose
- Marcel Pociot
Контракты
«Контракты» Laravel – это набор интерфейсов, которые определяют основные службы фреймворка. Например, контракт Illuminate\Contracts\Queue\Queue определяет методы, необходимые для постановки заданий в очередь, а контракт Illuminate\Contracts\Mail\Mailer – для отправки электронной почты.
Каждый контракт имеет соответствующую реализацию, предусмотренную структурой. Например, Laravel предлагает реализацию очереди с множеством драйверов и реализацию почтовой программы, которая работает на SwiftMailer.
Все контракты Laravel находятся в их собственном репозитории GitHub. Это обеспечивает быстрый доступ к списку всех доступных контрактов, а также единый, отдельный пакет, который используется разработчиками пакетов, взаимодействующих со службами Laravel.
Контракты против Фасадов
Фасады и глобальные помощники Laravel обеспечивают простой способ использования служб Laravel без объявления типов зависимости и извлечения их реализаций из контейнера служб. В большинстве случаев каждый фасад имеет эквивалентный контракт.
В отличие от фасадов, которые не требуют, чтобы они находились в конструкторе вашего класса, контракты позволяют вам определять явные зависимости для ваших классов. Некоторые разработчики предпочитают явно определять свои зависимости таким образом и поэтому предпочитают использовать контракты, в то время как другие разработчики пользуются удобством фасадов. В общем, большинство приложений могут без проблем использовать фасады во время разработки.
Когда использовать контракты
Решение об использовании контрактов или фасадов будет зависеть от личного вкуса и вкусов вашей команды разработчиков. И контракты, и фасады могут использоваться для создания надежных, хорошо тестируемых приложений Laravel. Контракты и фасады не исключают друг друга. Некоторые части ваших приложений могут использовать фасады, а другие могут зависеть от контрактов. До тех пор, пока вы сосредоточены на обязанностях класса, вы не заметите практических различий между использованием контрактов и фасадов.
В общем, большинство приложений могут без проблем использовать фасады во время разработки. Если вы создаете пакет, который интегрируется с несколькими фреймворками PHP, то вы можете указать пакет illuminate/contracts в файле composer.json вашего пакета для определения вашей интеграции со службами Laravel без необходимости требований конкретных реализаций Laravel.
Как использовать контракты
Итак, как получить реализацию контракта? На самом деле это довольно просто.
Многие типы классов в Laravel извлекаются из контейнера служб, включая контроллеры, слушателей событий, посредники, очереди заданий и даже замыкания маршрутов. Итак, чтобы получить реализацию контракта, вы можете просто «объявить тип» интерфейса в конструкторе извлекаемого класса.
Например, взгляните на этого слушателя события:
namespace App\Listeners; use App\Events\OrderWasPlaced; use App\Models\User; use Illuminate\Contracts\Redis\Factory; class CacheOrderInformation < /** * Реализация фабрики Redis. * * @var \Illuminate\Contracts\Redis\Factory */ protected $redis; /** * Создать новый экземпляр обработчика события. * * @param \Illuminate\Contracts\Redis\Factory $redis * @return void */ public function __construct(Factory $redis) < $this->redis = $redis; > /** * Обработать событие. * * @param \App\Events\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\Mail\MailQueue | Mail::queue() |
| Illuminate\Contracts\Mail\Mailable | |
| Illuminate\Contracts\Mail\Mailer | |
| 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() |
Русскоязычное комьюнити
- Группа в VK
- Телеграм LaravelRUS
- Телеграм Laravel для новичков
- Телеграм LaravelPro
Обучающие ресурсы
- Laracasts
- Codecourse
- Курс Дмитрия Елисеева
- Adam Wathan
Блоги разработчиков
- Laravel News
- Freek Van der Herten
- Brent Roose
- Marcel Pociot
Contracts
Laravel’s «contracts» are a set of interfaces that define the core services provided by the framework. For example, an Illuminate\Contracts\Queue\Queue contract defines the methods needed for queueing jobs, while the Illuminate\Contracts\Mail\Mailer contract defines the methods needed for sending e-mail.
Each contract has a corresponding implementation provided by the framework. For example, Laravel provides a queue implementation with a variety of drivers, and a mailer implementation that is powered by Symfony Mailer.
All of the Laravel contracts live in their own GitHub repository. This provides a quick reference point for all available contracts, as well as a single, decoupled package that may be utilized when building packages that interact with Laravel services.
Contracts Vs. Facades
Laravel’s facades and helper functions provide a simple way of utilizing Laravel’s services without needing to type-hint and resolve contracts out of the service container. In most cases, each facade has an equivalent contract.
Unlike facades, which do not require you to require them in your class’ constructor, contracts allow you to define explicit dependencies for your classes. Some developers prefer to explicitly define their dependencies in this way and therefore prefer to use contracts, while other developers enjoy the convenience of facades. In general, most applications can use facades without issue during development.
When To Use Contracts
The decision to use contracts or facades will come down to personal taste and the tastes of your development team. Both contracts and facades can be used to create robust, well-tested Laravel applications. Contracts and facades are not mutually exclusive. Some parts of your applications may use facades while others depend on contracts. As long as you are keeping your class’ responsibilities focused, you will notice very few practical differences between using contracts and facades.
In general, most applications can use facades without issue during development. If you are building a package that integrates with multiple PHP frameworks you may wish to use the illuminate/contracts package to define your integration with Laravel’s services without the need to require Laravel’s concrete implementations in your package’s composer.json file.
How To Use Contracts
So, how do you get an implementation of a contract? It’s actually quite simple.
Many types of classes in Laravel are resolved through the service container, including controllers, event listeners, middleware, queued jobs, and even route closures. So, to get an implementation of a contract, you can just «type-hint» the interface in the constructor of the class being resolved.
For example, take a look at this event listener:
namespace App\Listeners; use App\Events\OrderWasPlaced;use App\Models\User;use Illuminate\Contracts\Redis\Factory; class CacheOrderInformation /** * Create a new event handler instance. */ public function __construct( protected Factory $redis, ) <> /** * Handle the event. */ public function handle(OrderWasPlaced $event): void // . >>
When the event listener is resolved, the service container will read the type-hints on the constructor of the class, and inject the appropriate value. To learn more about registering things in the service container, check out its documentation.
Contract Reference
This table provides a quick reference to all of the Laravel contracts and their equivalent facades:
| Contract | References Facade |
|---|---|
| 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\Mail\MailQueue | Mail::queue() |
| Illuminate\Contracts\Mail\Mailable | |
| Illuminate\Contracts\Mail\Mailer | |
| 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 | 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() |
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel attempts to take the pain out of development by easing common tasks used in most web projects.
Продвинутый Laravel: Контракты и Реализации
Контракты и реализации — это мощные инструменты в Laravel, позволяющие определять стандартный интерфейс и писать код, который можно адаптировать к различным реализациям.
Контракты — это сложная тема программирования, общая для многих языков программирования. С технической точки зрения контракты называются интерфейсами , но для целей этой статьи мы будем использовать термин контракты , для упрощения задачи.
Контракт похож на деловое соглашение в том смысле, что это соглашение между сторонами. Контракты обычно имеют условия, которые являются условиями соглашения, которым обе стороны соглашаются следовать. Когда мы берём это и переводим в код, у нас получается что-то вроде этого:
namespace App\Contracts;
interface Dvr
public function play();
public function pause();
>
В этом контракте мы соглашаемся вести дела с DVR и условиями (методами) контракта, которые мы должны соблюдать, чтобы play() и pause() DVR. Как вы можете себе представить, есть несколько компаний предоставляющих услуги DVR. В Соединённых Штатах двумя крупнейшими DVR поставщиками являются Honeywell и Haydon.
Теперь давайте создадим службу API для Honeywell и Haydon.
app/Services/HoneywellApi.php:
namespace App\Services;
class HoneywellApi
public function pressPlay()
return 'Play Honeywell DVR';
>
public function pressPause()
return 'Pause Honeywell DVR';
>
>
app/Services/HaydonApi.php:
namespace App\Services;
class HaydonApi
public function play()
return 'Play Haydon DVR';
>
public function pause()
return 'Pause Haydon DVR';
>
>
Вы. вероятно, заметили, что имена методов, используемые HoneywellApi , отличаются от имён методов HaydonApi , но это совершенно нормально. Почему? Потому что мы не можем сделать всех поставщиков API и SDK одинаковыми. Теперь мы создадим реализацию для предоставления каждого API, соблюдая при этом контракт.
app/Implementations/Honeywell.php:
namespace App\Implementations;
use App\Contracts\Dvr;
use App\Services\HoneywellApi;
class Honeywell implements Dvr
public function __construct(protected HoneywellApi $api) >
public function play()
return $this->api->pressPlay();
>
public function pause()
return $this->api->pressPause();
>
>
app/Implementations/Haydon.php:
namespace App\Implementations;
use App\Contracts\Dvr;
use App\Services\HaydonApi;
class Haydon implements Dvr
public function __construct(protected HaydonApi $api)>
public function play()
return $this->api->play();
>
public function pause()
return $this->api->pause();
>
>
Для использования одного из этих провайдеров, нужно создать контроллер вместе с маршрутом, указывающим на контроллер. Как вы скоро увидите, мы внедрим interface в конструктор. Это важная часть, поскольку она позволяет нам переключаться между поставщиками API, если что-нибудь понадобится. (Позже мы воспользуемся сервис-контейнером Laravel, чтобы собрать всё вместе)
app/Http/Controllers/DvrController.php:
namespace App\Http\Controllers;
use App\Contracts\Dvr;
class DvrController extends Controller
public function __construct(protected Dvr $dvr)>
public function play()
return $this->dvr->play();
>
public function pause()
return $this->dvr->pause();
>
>
Контроллер заботиться только о воспроизведении или приостановке DVR, его не волнует (и не должно заботить) то, кто будет предоставлять базовую услугу.
Двигаясь дальше, мы создадим маршрут для каждого метода контроллера в файле маршрутов api.php .
routes/api.php:
use Illuminate\Support\Facades\Route;
Route::get(‘dvr/play’, [\App\Http\Controllers\DvrController::class, ‘play’]);
Route::get(‘dvr/pause’,[\App\Http\Controllers\DvrController::class, ‘pause’]);
Когда мы запускаем браузер и переходим к нашему маршруту воспроизведения ( http://example.dev/api/dvr/play ), мы сталкиваемся со следующим:
Illuminate\Contracts\Container\BindingResolutionException
Target [App\Contracts\Dvr] is not instantiable while building [App\Http\Controllers\DvrController].
Происходит то, что Laravel видит контракт Dvr, поэтому пытается преобразовать его в реализацию, но не может её найти, что приводит к ошибке.
Давайте немного повеселимся. Мы войдём в наш AppServiceprovider и скажем Laravel, что когда приложение ищет реализацию Dvr, мы вернём ему реализацию Honeywell.
app/Providers/AppServiceProvider.php:
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
/**
* Register any application services.
*/
public function register(): void
$this->app->bind(
\App\Contracts\Dvr::class,
\App\Implementations\Honeywell::class
);
>
/**
* Bootstrap any application services.
*/
public function boot(): void
//
>
>
Теперь мы вернёмся на страницу, которую только что посещали, и обновим.
Play Honeywell DVR
Довольно круто, да? Теперь давайте посетим маршрут паузы ( http://example.dev/api/dvr/pause ), который возвращает:
Pause Honeywell DVR
Теперь предположим, что ваш менеджер приходит и заявляет, что мы собираемся перейти с Honeywell на Haydon, вам нужно просто обновить AppServiceProvider до:
app/Providers/AppServiceProvider.php:
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
/**
* Register any application services.
*/
public function register(): void
$this->app->bind(
\App\Contracts\Dvr::class,
\App\Implementations\Haydon::class
);
>
/**
* Bootstrap any application services.
*/
public function boot(): void
//
>
>
Посетим страницу маршрута воспроизведения ( http://example.dev/api/dvr/play ), который возвращает:
Play Haydon DVR
Теперь маршрут паузы ( http://example.dev/api/dvr/pause ) возвращает:
Pause Haydon DVR
Теперь давайте продвинемся ещё дальше. Представьте себе, что менеджер возвращается и говорит: Итак, мы говорили об этом, на самом деле мы хотим использовать как Honeywell, так и Haydon. Вы можете это сделать? А вы говорите: Я вас прикрою, босс!
Учитывая, что теперь мы будем предоставлять несколько поставщиков, давайте применим DRY к нашим контроллерам.
app/Http/Controllers/HaydonController.php:
namespace App\Http\Controllers;
class HaydonController extends DvrController >
app/Http/Controllers/HoneywellController.php:
namespace App\Http\Controllers;
class HoneywellController extends DvrController >
Как видите, они оба расширяют изначально созданный нами DvrController .
Давайте обновим файл маршрутов api.php , чтобы они соответствовали им:
routes/api.php:
use Illuminate\Support\Facades\Route;
Route::get('dvr/play', [\App\Http\Controllers\DvrController::class, 'play']);
Route::get('dvr/pause', [\App\Http\Controllers\DvrController::class, 'pause']);
Route::get('dvr/play/honeywell', [\App\Http\Controllers\HoneywellController::class, 'play']);
Route::get('dvr/pause/honewell', [\App\Http\Controllers\HoneywellController::class, 'pause']);
Route::get('dvr/play/haydon', [\App\Http\Controllers\HaydonController::class, 'play']);
Route::get('dvr/pause/haydon', [\App\Http\Controllers\HaydonController::class, 'pause']);
Когда мы посещаем конечную точку воспроизведения Honeywell ( http://laravelcontractsandimplementations.test/api/dvr/play/honeywell ), сталкиваемся с проблемой:
Play Haydon DVR
Сейчас, Laravel знает, что когда запрашиваем Dvr , мы возвращаем реализацию Haydon .
Итак, как нам это решить? Нужно предоставить AppServiceProvider дополнительный контекст:
app/Providers/AppServiceProvider.php:
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
/**
* Register any application services.
*/
public function register(): void
$this->app->bind(
\App\Contracts\Dvr::class,
\App\Implementations\Haydon::class
);
$this->app
->when(\App\Http\Controllers\HoneywellController::class)
->needs(\App\Contracts\Dvr::class)
->give(\App\Implementations\Honeywell::class);
$this->app
->when(\App\Http\Controllers\HaydonController::class)
->needs(\App\Contracts\Dvr::class)
->give(\App\Implementations\Haydon::class);
>
/**
* Bootstrap any application services.
*/
public function boot(): void
//
>
>
Теперь, когда мы посещаем конечный точки для Honeywell или Haydon, получаем правильные данные.
В заключение, контракты и реализации — это мощные инструменты в Laravel, позволяющие определять стандартный интерфейс и писать код, который можно адаптировать к различным реализациям. Используя контракты, вы можете создавать более модульный, масштабируемый и удобный в сопровождении код, который при необходимости можно легко обновлять или заменять другими реализациями. Я надеюсь, что эта статья была информативной и полезной в вашем путешествии по Laravel. И я призываю вас продолжать изучать и экспериментировать с этим увлекательным фреймворком!