
- контракты;
- сервис-провайдеры;
- сервис-контейнер;
- фасады.
Контракты.
Контракты в Laravel – это интерфейсы. На их основе сгруппированы классы, которые реализуют эти интерфейсы и в которых определены методы указанные в интерфейсах (плюс другие нужные методы). Свои контракты (интерфейсы) можно хранить в папке app\Helpers\Contracts.Пример создания своего контракта и классов реализующих данный интерфейс.
Создаем контракт SaveStr (файл app\Helpers\Contracts\SaveStr.php)
<?php namespace App\Helpers\Contracts; use Illuminate\Http\Request; use App\User; Interface SaveStr { public static function save(Request $request, User $user); public function checkData($array); }
Классы реализующие интерфейс создаем в папке app\Helpers.
Файл app\Helpers\SaveEloquent.php
<?php namespace app\Helpers; use App\Helpers\Contracts\SaveStr; use Illuminate\Http\Request; use App\User; class SaveEloquent implements SaveStr{ public static function save(Request $request, User $user){ $obj = new self; $date = $obj->checkData($request->only('description', 'text')); $user->posts()->create($date); } public function checkData($array){ //тут проверка данных return $array; } }
Файл app\Helpers\SaveFile.php
<?php namespace app\Helpers; use App\Helpers\Contracts\SaveStr; use Illuminate\Http\Request; use App\User; use Illuminate\Support\Facades\Storage; class SaveFile implements SaveStr{ public static function save(Request $request, User $user){ $obj = new self; $date = $obj->checkData($request->only('description', 'text')); $str = $date['description'] . ' | ' . $date['text']; /* * фасад для работы с файлами * метод prepend() вставляет данные в конец файла */ Storage::prepend('str.txt', $str); } public function checkData($array){ //тут проверка данных return $array; } }Итак, у нас готов контракт SaveStr. Практическое применение будет описано ниже.
Создание сервис-провайдера.
Сервис-провайдеры предназначены для регистрации сервисов (классов) в сервис-контейнере (глобальном объекте App).Сервис-провайдеры позволяют внедрять зависимости (объекты нужных классов) в нужные методы:
public function form(Request $request, SaveStr $save){}при этом, автоматически, создаются объекты указанных зависимостей и дальше в коде мы работаем с одним и тем же объектом при повторном внедрении зависимости в других методах (т.к. обычно используется шаблон проектирования синглтон).
В файле config\app.php в массиве providers перечислены классы сервис-провайдеров, которые регистрируют в сервис-контейнере (глобальном объекте App) определенный сервис сразу или при первом вызове метода register() - зависит от того, является ли провайдер «отложенным».
Все сервис-провайдеры должны наследовать специальный класс ServiceProvider.
В большинстве сервис-провайдеров есть методы register() и boot(). В методе register() служит исключительно для привязки нужных классов в сервис-контейнер.
Для создания файла сервис-провайдера с названием SaveStrServiceProvider выполнить в консоли:
php artisan make:provider SaveStrServiceProviderсоздастся соответствующий файл в папке app\Providers который будет иметь пустые методы boot() и register().
Регистрация сервиса в сервис-контейнере (App).
Для этого используетс метод register(), который выполняется при начальной загрузке приложения для каждого сервис-провайдера.<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use App\Helpers\SaveEloquent; use App\Helpers\SaveFile; class SaveStrServiceProvider extends ServiceProvider { public function boot() { // } public function register() { $this->app->singleton('App\Helpers\Contracts\SaveStr', function(){ return new SaveEloquent(); }); } }или аналогичный вариант привязки в методе register():
App::singleton(SaveStr::class, function(){ return new SaveEloquent(); });
Так же можно использовать метод bind() вместо singleton(), если не нужно использовать одноименный паттерн.
Данные методы позволяют выполнить в анонимной функции дополнительный код и преобразования. Если это не требуется то привязку можно осуществить так:
public function register() { App::singleton(SaveStr::class, SaveEloquent::class); }
В методы singleton() и bind() первым параметром передается название контракта (интерфейса) который регистрируем, а в качестве второго параметра – анонимная функция, возвращающая один из классов реализующих данный интерфейс.
Лучше использовать метод singleton() т.к. он соответствует шаблону проектирования «синглтон» - то есть создает объект указанного класса только 1 раз, а при последующих обращениях возвращает один и тот же объект.
Можно получить доступ к сервис-контейнеру из сервис-провайдера:
$this->app
Регистраця сервис-провайдера.
Все сервис-провайдеры нужно зарегистрировать в файле config\app.php в массиве providers. В данном случае добавляем в конец массива:
/* * My Service Providers */ App\Providers\SaveStrServiceProvider::class,
Пример использования.
Допустим нужно использовать данный сервис-провайдер при сохранении данных введенных пользователем в форме. Например метод form() соответствующего контроллера получает, проверяет и сохраняет данные.
В таком случае, нужно в данный метод передать зависимости:
- $request - стандартная зависимость для таких случаев – объект запроса;
- $save – объект класса SaveEloquent, реализующего интерфейс SaveStr.
use App\Helpers\Contracts\SaveStr; use Auth; use App\User; … public function form(Request $request, SaveStr $save){ if($request->isMethod('post')){ $save->save($request, User::find(4)); // или для текущего, аутентифицированного пользователя: // $save->save($request, Auth::user()); } …
В сервис-провайдере у нас прописан класс SaveEloquent для реализации контракта (интерфейса) SaveStr. Поэтому сохранение будет проводиться в БД. Для того, чтобы использовался другой класс, например SaveFile (сохранение в файл) достаточно изменить в методе register() класса SaveStrServiceProvider название привязываемого класса:
public function register() { App::singleton(SaveStr::class, function(){ return new SaveFile(); }); }
Используя сервис провайдер, можно зарегистрировать в сервис-контейнере уже существующий объект:
public function register() { $obj = new SaveEloquent(); $this->app->instance('App\Helpers\Contracts\SaveStr', $obj); }
Обратиться к зарегистрированному сервису из сервис-провайдера:
$this->app['App\Helpers\Contracts\SaveStr'];или
$this->app->make('App\Helpers\Contracts\SaveStr')Метод boot() всех классов сервис-провайдеров вызывается автоматически после регистрации всех сервисов. Таким образом в данный метод можно внедрять любую зависимость.
Можно получить нужный сервис из сервис-контейнера без внедрения зависимости:
App::make('App\Helpers\Contracts\SaveStr')тут указывается строка к которой была привязана анонимная функция из метода register().
Если привязать, например, к строковому ключу 'Save' (при использовании фасадов)
public function register() { App::singleton('Save', function(){ return new SaveEloquent(); }); }то получить тот же самый объект можно так:
App::make('Save')а вот внедрение зависимости работать не будет. Чтобы работало нужно создать фасад.
ФАСАДЫ.
Фасады предоставляют легкий доступ к классам зарегистрированным в сервис-контейнере. В целом, можно обходиться и без них, внедряя зависимость в методы или получая объект нужного сервиса прямо в коде из глобального объекта App. Фасады стоит создавать для часто используемых сервисов, для упрощения доступа к их методам.Пример доступа к методам используется статический интерфейс:
Auth::user()можно сказать, что тут вызывается метод user() фасада Auth. На самом деле, обращение идет к алиасу, который перенаправляет вызов на класс фасада Auth, а сам фасад не имеет данного метода, да и сам метод вовсе не статический… Вот как все запутано :) Немного проясним это ниже.
При работе с фасадами используются алиасы, которые существуют в глобальной области видимости. Подключение:
use Auth;
Доступ к алиасу получаем из любого метода, внедрять зависимость не нужно, ведь фасады это как раз альтернатива внедрению зависимостей.
Фактически результат одинаковый с тем что мы получили при внедрении зависимости. Вместо того чтобы писать:
public function form(Request $request, SaveStr $save){ if($request->isMethod('post')){ $save->save($request, User::find(4)); } …понадобится:
public function form(Request $request){ if($request->isMethod('post')){ SaveStr::save($request, User::find(4)); } …
где SaveStr – алиас для фасада, который будет ссылаться на ячейку в сервис контейнере, к которой привязан нужный класс.
Создание фасада.
Файл app\Helpers\Facades\SaveStr.php:<?php namespace App\Helpers\Facades; use Illuminate\Support\Facades\Facade; class SaveStr extends Facade{ protected static function getFacadeAccessor() { return 'save'; } }
Класс фасада должен наследовать от родительского класса Facade и переопределять метод getFacadeAccessor().
Данный метод должен возвращать ключ (строку), к которому привязывается класс в сервис-провайдере. Ключ может быть абсолютно любой строкой, при работе с фасадом обращаться нужно будет не к нему, а к алиасу.
Указываем данный ключ в сервис-провайдере SaveStrServiceProvider:
public function register() { App::singleton('save', SaveFile::class); }
Создаем алиас в файле config\app.php в массиве aliases:
'Save' => App\Helpers\Facades\SaveStr::class
Тут, в качестве ключа указывается алиас с которым работает пользователь, а в качестве значения класс фасада.
Теперь вызов любого метода класса-сервиса (зарегистрированного в сервис-контейнере) выглядит так:
use Save; … Save::save($request, Auth::user());
Основные задачи фасада:
- возвращает строковый ключ по которому зарегистрирован определенный сервис (класс) – метод getFacadeAccessor(). Строковый ключ, обычно, делается коротким и по-смыслу связанным с действием привязанного класса. Таким образом, фасад упрощают доступ к объекту нужного сервиса.
- позволяет обращаться ко всем методам сервиса как к статическим. Для этого используется метод __callStatic($method, $args) из родительского класса Facade, который перенаправляет вызов методов на класс зарегистрированный в сервис-контейнере с помощью сервис-провайдера.
Посмотреть зарегистрированные в сервис-контейнере (глобальном объекте App) сервис-провайдеры и в целом свойства глобального объекта Application можно используя функцию-помощник app():
dump(app());у которого в свойстве bindings находится массив, где ключ массива – это алиас или контракт, а в значении можно посмотреть в т.ч. привязанный сервис-провайдер.
Но все же, я не знал что при использовании, как у вас в примере "автоматически, создаются объекты указанных зависимостей и дальше в коде мы работаем с одним и тем же объектом" - Singleton! Думал что каждый раз создается новый объект.