Страницы

Поиск по вопросам

четверг, 28 марта 2019 г.

Laravel: сервис-контейнеры и сервис-провайдеры. Что это и зачем?

Недавно перешел с фреймворка CodeIgniter (в котором была простая схема MVC) на изучение Laravel. Фреймворк очень понравился, но есть некоторые моменты, которые я никак не могу понять, сколько бы не читал документацию. В частности что такое сервис-контейнеры и сервис-провайдеры и зачем они нужны. Прошу помочь разобраться.


Ответ

Ну что-ж, попробую объяснить. С Богом!
Все возможности я не опишу, просто попытаюсь дать представление и возможно (скорее всего) не совсем точно.

Введение:
Вообще эти штуки нужны, в первую очередь, для удобства, как плюс — они реализуют паттерн Dependency Injection. С него и начнём.

class myClassA { private $obj; function __construct() { $this->obj = new myClassB; } }
Красиво?... Нет! Почему? А в друг нам понадобится, чтобы при создании класс myClassB принимал аргументы в свой конструктор? Придётся переписывать. Вы скажете: "перепишу, ничего страшного", но если таких классов myClassA много, то тут уже косяк — надо разруливать, для этого и придумали паттерн, глянем на то, как надо:
function __construct(myClassB $classB) { $this->obj = $classB; }
Т.е. мы в конструктор передали уже существующий класс myClassB, но где мы его создали? ведь new нигде не писали. Магия? Можно представить, что мы в Хогвартсе и зачитать заклинание "PHP Reflection" (но это уже отдельная тема). Так а Laravel что?
Сложно описать, давайте покажем, переведем наш класс myClassA в контроллер:
namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Http\Requests;
use App\MyClass\MyClassB;
class MyController extends Controller { private $obj; public function __construct(MyClassB $classB) { $this->obj = $classB; } public function index() { echo $this->obj->myMethod(); } }
Но нужно теперь и MyClassB создать. Сразу вспомним начало, нам нужно учитывать что класс должен что-то принимать. Создаем папочку app\MyClass и там файлик MyClassB.php с текстом:
action = $action; } public function myMethod() { return "Laravel - ".$this->action; } }
Ну что? Запускаем? Не-не... надо ж как-то передать какой нибудь $action в конструктор. Плюс, вот тут уже справедливо сказать, что Laravel не понимает, что у нас с зависимостями (ведь класс myClassA зависит от myClassB). А мы только тупо создали файл и в нём класс, надо что-то ещё. Вот тут НАКОНЕЦ-ТАКИ мы переходим к Сервис-провайдеру, просим всемогущего Artisan создать нам новый провайдер:
php artisan make:provider MyProvider
Далее, во вторую вкладочку открываем документацию и заполняем его (а сам файлик должен быть тут - app\Providers):
class MyProvider extends ServiceProvider { public function boot() {} public function register() { $this->app->bind(MyClassB::class, function(){ return new MyClassB('MyAction'); }); } }
Из документации видно, что внутри метода register() как раз и должны быть наши связывания, простенькие примеры:
// Если хотим, чтобы каждый вызов, возвращался новый класс $this->app->bind(MyClassB::class, function(){ return new MyClassB('MyAction'); }); // Если хотим, чтобы был создан только один объект и всегда он возвращался $this->app->singleton(MyClassB::class, function(){ return new MyClassB('MyAction'); });
Стоит также одним глазком глянуть в документацию на строчку:
Если вы откроете файл config/app.php, поставляемый с Laravel, то увидите массив providers. В нём перечислены все классы сервис-провайдеров, которые загружаются для вашего приложения.
Наш MyProvider тоже должен быть в этом списке.
Теперь можно запускать и, в принципе, всё должно работать. Теперь мы знаем, что MyProvider - это и есть Сервис-провайдер (внимательные заметили, что расширяет наш класс именно ServiceProvider. Совпадение?). Грубо говоря, это инструкция, которая создаст нам класс, чтобы другие классы могли им пользоваться (и в других местах).
Вроде разобрались с Сервис-провайдерами. Абасаца... А теперь еще Сервис-контейнер
Продолжаем стори... Так а где был создан MyClassB? Так вот, можно сказать, что он был создан в Сервис-контейнере. Srsly?
Можно сказать что Сервис-контейнер — это массив (array(key => value)), где ключ MyClassB::class, а значение это new MyClassB('MyAction'), и запись мы произвели с помощью метода bind. Но, на самом деле, помимо условного массива — это целый механизм, который позволяет делать такую магию в целом, вспоминаем наше заклинание из Хогвартса и зовется сие - Сервисом
Дочитали до конца? Бонусы:
1) Когда мы в app.php прописали провайдер, он будет автоматически создан и занесён в контейнер, а если он не так часто используется, зачем его каждый раз создавать? Для этого воспользуемся термином Отложенный-провайдер, для этого в нашем провайдере (мы-же теперь знаем, что это), нужно прописать protected $defer = true; тогда он будет создан только по запросу.
2) Мы можем и в коде обращаться к нашему контейнеру, например $getClass = $this->app->make('MyClassB'); (либо resolve('MyClassB');) (подробнее в документации)
P.S. В конце хочу добавить, что критику принимаю :) Подправим, допилим, может в будущем кому пригодится.

Комментариев нет:

Отправить комментарий