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

202
19 августа 2018, 13:10

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

Answer 1

Ну что-ж, попробую объяснить. С Богом!

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

Введение:

Вообще эти штуки нужны, в первую очередь, для удобства, как плюс — они реализуют паттерн 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 с текстом:

<?php
namespace App\MyСlass;
class MyClassB
{
    private $action;
    public function __construct($action) {
        $this->action = $action;
    }
    public function myMethod() {
        return "Laravel - ".$this->action;
    }
}

Ну что? Запускаем? Не-не... надо ж как-то передать какой нибудь $action в конструктор. Плюс, вот тут уже справедливо сказать, что Laravel не понимает, что у нас с зависимостями (ведь класс myClassA зависит от myClassB). А мы только тупо создали файл и в нём класс, надо что-то ещё. Вот тут НАКОНЕЦ-ТАКИ мы переходим к Сервис-провайдеру, просим всемогущего Artisan создать нам новый провайдер:

php artisan make:provider MyProvider

Далее, во вторую вкладочку открываем документацию и заполняем его (а сам файлик должен быть тут - app\Providers):

<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\MyClass\MyClassB;
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. В конце хочу добавить, что критику принимаю :) Подправим, допилим, может в будущем кому пригодится.

READ ALSO
Laravel вызвать контроллер в анонимной функции в роутинге

Laravel вызвать контроллер в анонимной функции в роутинге

В документации полно примеров как вызвать метод контроллера для обработки конкретного urlНапример:

180
LDAP: ldap_rename параметры функции

LDAP: ldap_rename параметры функции

помогите пожалуйста примером у кого есть, какие параметры требует функция php ldap_rename(), не совсем понимаю что она требует, глядя на документацию...

171
Прописать путь к скрипту в другом файле

Прописать путь к скрипту в другом файле

Скрипт 1 находится на: domains/site/script/functionphp Скрипт 2 на: domains/site/include/connect

152
API транзакция blockchain

API транзакция blockchain

нашел в доке такую конструкцию, правильно ли я понимаю, что так можно проверить по хеш данного кошелька, что был осуществлен перевод? И если...

175