Рубрики
Uncategorized

Как понять сервисный контейнер и инъекцию в Laravel и ThinkPHP 5?

Автор оригинала: David Wong.

Из документа

Многие люди видели официальные документы в самом начале, будь то Laravel или ThinkPHP. Даже просто перескакивай и не смотри. В любом случае, я не использую ничего такого высокого класса. Если идея нормальна за короткий промежуток времени, особенно для пользователей, которые привыкли мыслить в формате 3, концепция относительного внедрения является более продвинутой. Если вы долго об этом не думаете, это зависит от вашего собственного планирования карьеры. Далее, давайте посмотрим на это вместе и внимательно рассмотрим продукты.

Начните с Laravel

Из документации Laravel вы можете видеть, что bindсинглтон , а также экземпляр На эти три распространенных метода будут даны ответы один за другим.

практическое применение

Предположим, у нас есть такой сценарий. Когда наши пользователи регистрируются, нам необходимо отправить код подтверждения SMS на мобильный телефон пользователя. Затем, когда пользователь получит проверочный код, необходимо проверить правильность проверочного кода при отправке регистрационной формы.

Это требование выглядит очень простым в реализации, не так ли?

Когда мы получим документ о разработке платформы коротких сообщений, нам нужно будет написать только два метода. отправить и проверить Они используются для отправки проверочных кодов и проверочных кодов соответственно. Здесь мы будем писать псевдокоды без использования контейнеров.

  • Я тоже. php
dis
        $cacheManager = cache();
        // Set 5 minutes to expire
        $cacheManager->set('sms:' . $phone, $code, 5 * 60);
        return true;
    }

    public function check($phone, $code)
    {
        $cacheManager = cache();
        return $cacheManager->get('sms:' . $phone) === $code;
    }
}

Это просто, не так ли?

Затем экземпляр новых носителей в контроллере вызывается напрямую. отправить и проверить Вы можете отправить и проверить коды проверки отдельно.

Однако, если в ходе операции вдруг обнаружится, что предыдущая платформа SMS ненадежна, отправка SMS нестабильна, пользователи часто не могут ее получить.

В это время нам нужно изменить интерфейс, распространенный способ заключается в том, что мы пишем другой объект, а затем, возможно, этот код написан кем-то другим для вашего получения, вы считаете, что имя метода отправки или проверки недостаточно стандартизировано, затем вы меняете его и, кстати, меняете исходную сторону регистрации, а затем код внезапно выскакивает на строку и запускается.

Через некоторое время оператор почувствовал, что короткое сообщение этой платформы было слишком дорогим. Кроме того, он нашел дешевую и стабильную. Затем вы снова повторили все вышесказанное. На этот раз вы чувствуете, что метод совершенен и не нуждается в изменении.

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

Конечно, это всего лишь небольшой пример, мы можем столкнуться с более сложными изменениями в процессе разработки, или операция хочет, чтобы вы вернулись к предыдущей версии? ЭММ.

Здесь, если есть друзья, которые знали простые заводские модели, они могут подумать, что я могу использовать простой заводской режим для решения этой проблемы.

function factory($name)
{
    $modules = [
        'sms' => new MeiSms(),
    ];
    if (!isset($modules[$name])) {
        Throw new Exception ('object does not exist. ).
    }
    return $modules[$name];
}

Звоните напрямую, где это необходимо завод ("sms") Таким образом, мы можем получить объект для отправки коротких сообщений. Когда спрос изменится, я смогу напрямую преобразовать фабрику. Это не намного проще.

Но здесь вы столкнетесь с некоторыми проблемами. Объекты, созданные фабрикой, не имеют подсказок по типу, и у нас нет возможности ограничить, какие методы класс должен реализовывать на фабрике (конечно, вы можете усложнить фабрику плюс проверку интерфейса), но в конце концов вы обнаружите, что мы собираемся делать все дальше и дальше, в первую очередь, и все сложнее и сложнее, верно? Более того, фабрики не так просты в использовании.

Сервисный контейнер

Здесь мы должны оглянуться на наш Сервисный контейнер Во-первых, давайте посмотрим на описание внедрения зависимостей в документации контроллера, что многие люди начинают понимать. Инъекция зависимостей Место.

  • Инъекция Конструктора

Сервисный контейнер Laravel анализирует все контроллеры. Поэтому вы можете использовать подсказки типа для указания возможных зависимостей в конструкторе контроллера. Объявления зависимостей автоматически анализируются и вводятся в экземпляр контроллера

  • Способ инъекции

При внедрении конструктора обработки вы также можете ввести зависимости запроса типа в метод контроллера. Наиболее распространенным вариантом использования инъекции метода является инъекция в метод контроллера Осветить\Http\Запрос Пример

Каждый раз, когда мы создаем метод контроллера, мы активно заполняем первый параметр, то есть первый параметр. Запрос $запрос Вы когда-нибудь обращали внимание на параметр запроса? Разве это удивительно?

Почему бы мне ничего не делать, я могу это использовать, и это не ограничивается встроенными объектами Laravel, мы можем писать свои собственные объекты и легко использовать подсказки типа при использовании разработки IDE. Эти задачи Контейнер услуг Сделайте это за нас.

Когда контейнер анализирует метод, когда метод существует, он использует отражение для анализа параметров и типов параметров, необходимых в методе. Абстрактная функция отражения:: getParameters, а затем выясните в контейнере, есть ли у нас этот тип привязки, если нет, продолжайте использовать контейнер для создания этого класса (поскольку метод построения этого класса может также зависеть от других классов), пока не будет создан экземпляр зависимого класса, и сохраните экземпляр в контейнере.

Ты чувствуешь, что ты здесь? Служебный контейнер Яиц нет? Это просто для того, чтобы помочь нам рекурсивно создавать экземпляры классов. Значит, ты слишком молод. Теперь, когда это упомянуто выше, нам нужно его использовать. Сервисный контейнер Для решения Простая фабрика Я обману вас, если будет трудно решить проблему. Ха-ха

Теперь я собираюсь начать с первого. привязать

Способ привязки

Во-первых, давайте рассмотрим реализацию метода bind в Laravel.

    /**
     * Register a binding with the container.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @param  bool  $shared
     * @return void
     */
    public function bind($abstract, $concrete = null, $shared = false)
    {
        // If no concrete type was given, we will simply set the concrete type to the
        // abstract type. After that, the concrete type to be registered as shared
        // without being forced to state their classes in both of the parameters.
        $this->dropStaleInstances($abstract);

        if (is_null($concrete)) {
            $concrete = $abstract;
        }

        // If the factory is not a Closure, it means it is just a class name which is
        // bound into this container to the abstract type and we will just wrap it
        // up inside its own Closure to give us more convenience when extending.
        if (! $concrete instanceof Closure) {
            $concrete = $this->getClosure($abstract, $concrete);
        }

        $this->bindings[$abstract] = compact('concrete', 'shared');

        // If the abstract type was already resolved in this container we'll fire the
        // rebound listener so that any objects which have already gotten resolved
        // can have their copy of the object updated via the listener callbacks.
        if ($this->resolved($abstract)) {
            $this->rebound($abstract);
        }
    }
    /**
     * Drop all of the stale instances and aliases.
     *
     * @param  string  $abstract
     * @return void
     */
    protected function dropStaleInstances($abstract)
    {
        unset($this->instances[$abstract], $this->aliases[$abstract]);
    }

    /**
     * Get the Closure to be used when building a type.
     *
     * @param  string  $abstract
     * @param  string  $concrete
     * @return \Closure
     */
    protected function getClosure($abstract, $concrete)
    {
        return function ($container, $parameters = []) use ($abstract, $concrete) {
            if ($abstract == $concrete) {
                return $container->build($concrete);
            }

            return $container->make($concrete, $parameters);
        };
    }

Это было названо в самом начале. $this->отбросьте устаревшие экземпляры($abstract); Прослеживая исходный код, мы видим, что он напрямую удалил существующий экземпляр и псевдоним, соответствующий первому параметру.

Затем продолжайте, когда $конкретный Ни одного. Закрытие Когда дело доходит до анонимности, он сделает некоторую обертку и сделает это анонимным способом. привязки Это свойство имеет ключ _____________ $аннотация Значение представляет собой массив, в котором конкретный Это обернутый метод, а затем он вызывает контейнер make …. При запуске в это место

if ($this->resolved($abstract)) {
    $this->rebound($abstract);
}

Первым шагом является определение того, был ли этот анализ проанализирован, и обновление копий, хранящихся в контейнере.

Вот оно. привязка То, что мы делаем, что проще описать, – это предоставляем псевдоним для класса, экземпляр класса и анонимный метод, который будет привязан к контейнеру.

Когда мы используем resolve , Когда вы только что передали псевдоним, вы можете проанализировать и получить экземпляр, который мы ранее связали.

Приходите и попробуйте?

Давайте сначала откроем его. bootstrap/app.php Как вы можете видеть с самого начала, мы создали экземпляр приложения и попробовали его в $app Привязать перед возвращением.

  • начальная загрузка/app.php:54
$app->bind('hello', \App\Tools\MeiSms::class);
return $app;
  • маршруты/web.php:23
Route::any('hello', function ()
{
    $resolve = resolve('hello');
    var_dump(get_class($resolve));
});

Откройте браузер и посмотрите

Это хорошо, но вот мы здесь, мы просто делаем то же самое, что и простая фабрика. Затем давайте преобразуем наш класс текстовых сообщений.

Во-первых, мы договариваемся об интерфейсе. Проверка коротких сообщений должна отправлять короткие сообщения и проверять коды проверки коротких сообщений соответственно. отправить($телефон) и проверить($телефон,$код) Метод.

  • Во-первых, мы договариваемся об интерфейсе. Проверка коротких сообщений должна отправлять короткие сообщения и проверять коды проверки коротких сообщений соответственно.

Затем мы реализуем этот интерфейс с предыдущим классом пропусков.

  • Затем мы реализуем этот интерфейс с предыдущим классом пропусков.
set('sms:' . $phone, $code, 5 * 60);
        return true;
    }

    public function check(string $phone, string $code): bool
    {
        $cacheManager = cache();
        return $cacheManager->get('sms:' . $phone) === $code;
    }
}

А теперь давайте еще раз. bootstrap/app.php Зарегистрирован, на этот раз немного отличается от предыдущего.

$app->bind(\App\Contracts\Interfaces\Sms::class, \App\Tools\MeiSms::class);
return $app;

Как вы можете видеть, интерфейс Sms, когда передается наш первый параметр, класс носителей, которые будут привязаны, а затем мы изменим маршрутизацию.

  • Как вы можете видеть, интерфейс Sms, когда передается наш первый параметр, класс носителей, которые будут привязаны, а затем мы изменим маршрутизацию.
Route::any('hello', function (\App\Contracts\Interfaces\Sms $sms)
{
    var_dump(get_class($sms));
});
  • Результат

Ты что, только что одурачил меня скриншотом? Что четко определено выше, так это \Приложение\Контракты\Интерфейсы\Sms Как его распечатать \Приложение\Инструменты\Носители Код не сообщил об ошибке?

Не удивляйтесь, во-первых, \Приложение\Инструменты\Средства Было достигнуто \Приложение\Контракты\Интерфейсы\Sms Интерфейс, поэтому законно ограничить тип интерфейса.

И поскольку этот метод вызывается контейнером, контейнер вызывает внутренний. make Метод выполняет серию обработки внедрения зависимостей, когда для получения метода требуется \App\Контракты\Интерфейсы\Sms Когда используется параметр типа, контейнер вводит символ имени класса в привязку, чтобы найти его, потому что мы зарегистрировали его раньше, поэтому это эквивалентно присвоению псевдонима классу, и в конечном итоге \App\Tools\MeiSms Выполняет результаты для нас.

выгода

Поэтому, когда мы возвращаемся к этой теме, нам кажется, что мы решили проблему еще до того, как подумали об этом. Тогда в чем преимущество сравнения предыдущих методов?

  • Лучшая спецификация

Поскольку мы ограничиваем ограничение интерфейса в маршрутизации, нам больше не нужно беспокоиться о вызове “отправить” или “проверить”. Не беспокойтесь об этом, потому что параметр возвращаемого значения метода отправки или проверки не знает, как оценить результат (поскольку мы ограничили, что может быть возвращено только значение bool).

  • Больше никаких изменений в исходном бизнес-коде, меньше ошибок

Да, это правильно. Нам больше не нужно изменять существующий бизнес-код, нам просто нужно реализовать интерфейс недавно добавленного класса и привязать его к контейнеру, и больше ничего не изменилось.

  • Лучшее тестирование

Заполнить вакансию

Конечно, когда вы говорите об этом, вы можете подумать, что я мало что сказал.

Одноэлементный метод

На самом деле, из исходного кода легко видеть, что синглтон вызывает метод привязки, но общий параметр отличается, что означает, что объект синглтона связан.

    /**
     * Register a shared binding in the container.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @return void
     */
    public function singleton($abstract, $concrete = null)
    {
        $this->bind($abstract, $concrete, true);
    }

Метод экземпляра.

Это почти то же самое, что и bind, за исключением того, что bind может связывать анонимный метод или прямое имя класса (которое обрабатывается внутри). Экземпляр, как и его имя, используется для привязки экземпляра к контейнеру.

Конец

В небольших масштабах Сервисный контейнер и Заводской режим Есть много общего, но Сервисный контейнер Это откроет вам доступ к другому блоку знаний PHP. reflex Это мощный API.

На самом деле, я понятия не имею, почему я должен использовать Laravel в качестве примера для написания этой статьи. Потому что, посмотрев на реализацию ThinkPHP, ее легче читать. На самом деле, сначала я собирался поговорить об обоих фреймворках, но я чувствовал то же самое. Я выбрал Ларавель. Хотя это было сложнее, я даже не позаботился о многих моментах, я написал эту статью, не так ли?

Надеюсь, эта статья поможет вам понять. Сервисный контейнер Это полезно. Конечно, я бы рекомендовал вам больше. Laravel Или исходный код ThinkPHP, потому что это может углубить их понимание этого.

  • framework/Container.php в 6.0 · верхнее мышление/фреймворк
  • framework/Container.php в 5.8 · laravel /фреймворк

Справочный материал

  • Инъекция зависимости – свобода, Энциклопедия
  • Инверсия управления (IoC) и внедрение зависимостей (DI) – Краткая книга
  • Сервисный контейнер, “Китайский документ Laravel 5.8”, сообщество Laravel China
  • PHP: Отражение – Руководство