Недавно перешел с фреймворка 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. В конце хочу добавить, что критику принимаю :) Подправим, допилим, может в будущем кому пригодится.
Комментариев нет:
Отправить комментарий