См. исходный код Laravel, чтобы понять контейнер

задняя часть PHP Laravel Symfony

Поскольку приведенное выше «Посмотрите на исходный код Laravel, чтобы понять загрузку ServiceProvider», мы знаем, что Приложение (или Контейнер) действует как контейнер Laravel и в основном включает в себя все основные функции Laravel в этом контейнере.

Давайте посмотрим, что же такое это Приложение/Контейнер сегодня?

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

Inversion of Control

Прежде чем мы узнаем, что такое инверсия управления, нам следует лучше понять принцип:

Принцип инверсии зависимости (DIP) защищает:

  • Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны полагаться на абстракцию
  • Абстракция не должна зависеть от деталей, детали должны зависеть от абстракции
  • Программа для интерфейса, а не для реализации

При программировании мы разрабатываем код по модульному принципу, и зависимости между ними неизбежны.Например модуль А зависит от модуля Б, то согласно DIP модуль А должен зависеть от интерфейса модуля Б, а не от реализации модуль Б.

Возьмем пример.

Нам нужна функция Logger для вывода системного журнала в файл. Мы можем написать так:

class LogToFile {
    public function execute($message) {
        info('log the message to a file :'.$message);
    }
}

Звоните напрямую, где это необходимо:

class UseLogger {
    protected $logger;

    public function __construct(LogToFile $logger) {
        $this->logger = $logger;
    }

    public function show() {
        $user = 'yemeishu';
        $this->logger->execute($user);
    }
}

Напишите тестовый пример:

$useLogger = new UseLogger(new LogToFile());

$useLogger->show();

Если нам нужно вывести журнал в DingTalk в это время, мы переписываем класс LogToDD:

class LogToDD {
    public function execute($message) {
        info('log the message to 钉钉 :'.$message);
    }
}

В настоящее время нам также необходимо изменить код на стороне использования (UseLogger), чтобы ввести класс LogToDD:

class UseLogger {
    protected $logger;

    public function __construct(LogToDD $logger) {
        $this->logger = $logger;
    }

    public function show() {
        $user = 'yemeishu';
        $this->logger->execute($user);
    }
}

На самом деле, в этот момент вы можете «вынюхать» плохой код:

Если у меня много пользователей, значит, я должен везде импортировать изменения.

По принципу DIP мы должны развиваться в сторону интерфейса. Заставьте потребителя зависеть от интерфейса, а не от реализации. Итак, создаем интерфейс:

interface Logger {
    public function execute($message);
}

тогда пустьLogToFileа такжеLogToDDв видеLoggerРеализация:

class LogToFile implements Logger {
    public function execute($message) {
        info('log the message to a file :'.$message);
    }
}

class LogToDD implements Logger {
    public function execute($message) {
        info('log the message to 钉钉 :'.$message);
    }
}

Таким образом, когда мы используем терминал, мы напрямую вводимLoggerИнтерфейс, пусть код разделяет конкретную реализацию.

class UseLogger {
    protected $logger;

    public function __construct(Logger $logger) {
        $this->logger = $logger;
    }

    public function show() {
        $user = 'yemeishu';
        $this->logger->execute($user);
    }
}

Таким образом, можно быть уверенным, что независимо от того, используете ли вы файл для сохранения или отправки его в DingTalk, вам не нужно менять код.Вы должны выбрать его в соответствии с потребностями бизнеса, когда вам действительно нужно позвони еще раз. В качестве теста:

$useLogger1 = new UseLogger(new LogToFile());
$useLogger1->show();

$useLogger2 = new UseLogger(new LogToDD());
$useLogger2->show();

результат:

Но здесь есть проблема: наконец, при создании и использовании его все равно нужно «жестко запрограммировать».newНаш класс реализации (LogToDD или LogToFile).

Есть ли способ сделать еще один шаг и поставить окончательныйnew LogToDD()Тоже инверсия управления?

Реализация привязки

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

Давайте напишем простой класс для реализации функции привязки и парсинга:

class SimpleContainer {

    // 用于存储所有绑定 key-value
    protected static $container = [];

    public static function bind($name, Callable $resolver) {
        static::$container[$name] = $resolver;
    }

    public static function make($name) {
        if(isset(static::$container[$name])){
            $resolver = static::$container[$name] ;
            return $resolver();
        }
        throw new Exception("Binding does not exist in container");
    }
}

Мы можем протестировать:

SimpleContainer::bind(Logger::class, function () {
    return new LogToFile();
});

$useLogger3 = new UseLogger(SimpleContainer::make(Logger::class));

$useLogger3->show();

Пока есть одна привязкаLoggerа такжеLogToFileотношение, вы можете напрямую разрешить ссылку, где бы вам ни потребовалось ее вызвать.

То есть с помощью этого метода во всех местах кодирования вводится «интерфейс», а не класс реализации. Тщательно внедряйте принципы DPI.

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

Laravel Container

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

$container = new Container();
$container->bind(Logger::class, LogToFile::class);

$useLogger4 = new UseLogger($container->make(Logger::class));
$useLogger4->show();

добиться такого же эффекта

[2018-05-19 15:36:30] testing.INFO: log the message to a file :yemeishu

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

В сочетании с предыдущей статьей «Просмотр исходного кода Laravel, чтобы понять загрузку ServiceProvider» и объяснением вышеуказанных принципов. Нам не привыкать к использованию контейнеров.

Далее вы можете взглянуть на исходный код контейнера.

Анализ исходного кода контейнера

Как видно из вышеизложенного, у Container есть две основные функции, одна — связывание, а другая — синтаксический анализ.

связывать

Давайте посмотрим, каковы основные типы привязки:

  1. связать синглтон
  2. экземпляр привязки
  3. Привязать интерфейс к реализации
  4. привязать исходные данные
  5. контекстная привязка
  6. привязка тегов тегов

Ниже мы разберем эти типы:

1. Привязать синглтон

public function singleton($abstract, $concrete = null)
    {
        $this->bind($abstract, $concrete, true);
    }

В настоящее время в основном используйте параметр $share = true, чтобы пометить привязку как синглтон.

2. Экземпляр привязки

public function instance($abstract, $instance)
{
    $this->removeAbstractAlias($abstract);

    $isBound = $this->bound($abstract);

    unset($this->aliases[$abstract]);

    // We'll check to determine if this type has been bound before, and if it has
    // we will fire the rebound callbacks registered with the container and it
    // can be updated with consuming classes that have gotten resolved here.
    $this->instances[$abstract] = $instance;

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

    return $instance;
}

Связывающие экземпляры, в основном[$abstract, $instance]хранить в массиве$instancesсередина.

3. привязка тегов

/**
 * Assign a set of tags to a given binding.
 *
 * @param  array|string  $abstracts
 * @param  array|mixed   ...$tags
 * @return void
 */
public function tag($abstracts, $tags)
{
    $tags = is_array($tags) ? $tags : array_slice(func_get_args(), 1);

    foreach ($tags as $tag) {
        if (! isset($this->tags[$tag])) {
            $this->tags[$tag] = [];
        }

        foreach ((array) $abstracts as $abstract) {
            $this->tags[$tag][] = $abstract;
        }
    }
}

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

4. Привязка

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);

    // 如果传入的实现为空,则绑定 $concrete 自己
    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.
    // 目的是将 $concrete 转成闭包函数
    if (! $concrete instanceof Closure) {
        $concrete = $this->getClosure($abstract, $concrete);
    }

    // 存储到 $bindings 数组中,如果 $shared = true, 则表示绑定单例
    $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);
    }
}

Разобрать

/**
 * Resolve the given type from the container.
 *
 * @param  string  $abstract
 * @param  array  $parameters
 * @return mixed
 */
public function make($abstract, array $parameters = [])
{
    return $this->resolve($abstract, $parameters);
}

/**
 * Resolve the given type from the container.
 *
 * @param  string  $abstract
 * @param  array  $parameters
 * @return mixed
 */
protected function resolve($abstract, $parameters = [])
{
    $abstract = $this->getAlias($abstract);

    $needsContextualBuild = ! empty($parameters) || ! is_null(
        $this->getContextualConcrete($abstract)
    );

    // If an instance of the type is currently being managed as a singleton we'll
    // just return an existing instance instead of instantiating new instances
    // so the developer can keep using the same objects instance every time.
    if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
        return $this->instances[$abstract];
    }

    $this->with[] = $parameters;

    $concrete = $this->getConcrete($abstract);

    // We're ready to instantiate an instance of the concrete type registered for
    // the binding. This will instantiate the types, as well as resolve any of
    // its "nested" dependencies recursively until all have gotten resolved.
    if ($this->isBuildable($concrete, $abstract)) {
        $object = $this->build($concrete);
    } else {
        $object = $this->make($concrete);
    }

    // If we defined any extenders for this type, we'll need to spin through them
    // and apply them to the object being built. This allows for the extension
    // of services, such as changing configuration or decorating the object.
    foreach ($this->getExtenders($abstract) as $extender) {
        $object = $extender($object, $this);
    }

    // If the requested type is registered as a singleton we'll want to cache off
    // the instances in "memory" so we can return it later without creating an
    // entirely new instance of an object on each subsequent request for it.
    if ($this->isShared($abstract) && ! $needsContextualBuild) {
        $this->instances[$abstract] = $object;
    }

    $this->fireResolvingCallbacks($abstract, $object);

    // Before returning, we will also set the resolved flag to "true" and pop off
    // the parameter overrides for this build. After those two things are done
    // we will be ready to return back the fully constructed class instance.
    $this->resolved[$abstract] = true;

    array_pop($this->with);

    return $object;
}

Пошагово разберем «аналитическую» функцию:

$needsContextualBuild = ! empty($parameters) || ! is_null(
    $this->getContextualConcrete($abstract)
);

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

// If an instance of the type is currently being managed as a singleton we'll
// just return an existing instance instead of instantiating new instances
// so the developer can keep using the same objects instance every time.
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
    return $this->instances[$abstract];
}

Если это связанный синглтон и ему не нужны вышеуказанные зависимости параметров. мы можем вернуться прямо$this->instances[$abstract].

$concrete = $this->getConcrete($abstract);

...

/**
 * Get the concrete type for a given abstract.
 *
 * @param  string  $abstract
 * @return mixed   $concrete
 */
protected function getConcrete($abstract)
{
    if (! is_null($concrete = $this->getContextualConcrete($abstract))) {
        return $concrete;
    }

    // If we don't have a registered resolver or concrete for the type, we'll just
    // assume each type is a concrete name and will attempt to resolve it as is
    // since the container should be able to resolve concretes automatically.
    if (isset($this->bindings[$abstract])) {
        return $this->bindings[$abstract]['concrete'];
    }

    return $abstract;
}

Этот шаг в основном предназначен для того, чтобы узнать из контекста привязки, можно ли найти класс привязки; если нет, то из контекста привязки.$bindings[]Найдите связанный класс реализации; если он не будет найден в конце, он вернется напрямую$abstractсам.

// We're ready to instantiate an instance of the concrete type registered for
// the binding. This will instantiate the types, as well as resolve any of
// its "nested" dependencies recursively until all have gotten resolved.
if ($this->isBuildable($concrete, $abstract)) {
    $object = $this->build($concrete);
} else {
    $object = $this->make($concrete);
}

...

/**
 * Determine if the given concrete is buildable.
 *
 * @param  mixed   $concrete
 * @param  string  $abstract
 * @return bool
 */
protected function isBuildable($concrete, $abstract)
{
    return $concrete === $abstract || $concrete instanceof Closure;
}

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

$this->build($concrete)

/**
 * Instantiate a concrete instance of the given type.
 *
 * @param  string  $concrete
 * @return mixed
 *
 * @throws \Illuminate\Contracts\Container\BindingResolutionException
 */
public function build($concrete)
{
    // If the concrete type is actually a Closure, we will just execute it and
    // hand back the results of the functions, which allows functions to be
    // used as resolvers for more fine-tuned resolution of these objects.
    // 如果传入的是闭包,则直接执行闭包函数,返回结果
    if ($concrete instanceof Closure) {
        return $concrete($this, $this->getLastParameterOverride());
    }

    // 利用反射机制,解析该类。
    $reflector = new ReflectionClass($concrete);

    // If the type is not instantiable, the developer is attempting to resolve
    // an abstract type such as an Interface of Abstract Class and there is
    // no binding registered for the abstractions so we need to bail out.
    if (! $reflector->isInstantiable()) {
        return $this->notInstantiable($concrete);
    }

    $this->buildStack[] = $concrete;

    // 获取构造函数
    $constructor = $reflector->getConstructor();

    // If there are no constructors, that means there are no dependencies then
    // we can just resolve the instances of the objects right away, without
    // resolving any other types or dependencies out of these containers.
    // 如果没有构造函数,则表明没有传入参数,也就意味着不需要做对应的上下文依赖解析。
    if (is_null($constructor)) {
        // 将 build 过程的内容 pop,然后直接构造对象输出。
        array_pop($this->buildStack);

        return new $concrete;
    }

    // 获取构造函数的参数
    $dependencies = $constructor->getParameters();

    // Once we have all the constructor's parameters we can create each of the
    // dependency instances and then use the reflection instances to make a
    // new instance of this class, injecting the created dependencies in.
    // 解析出所有上下文依赖对象,带入函数,构造对象输出
    $instances = $this->resolveDependencies(
        $dependencies
    );

    array_pop($this->buildStack);

    return $reflector->newInstanceArgs($instances);
}

Этот метод распадается на две ветви: если$concrete instanceof Closure, затем напрямую вызовите функцию закрытия и верните результат:$concrete(); другая ветвь состоит в том, что входящий$concrete === $abstract === 类名, через отражение, разрешает и создает новый класс.

См. примечания выше для конкретных объяснений. Использование класса ReflectionClass, конкретная ссылка:PHP.go вытягивает Ravel.com/class Горячее безумие...

Код смотрит вниз:

// If we defined any extenders for this type, we'll need to spin through them
// and apply them to the object being built. This allows for the extension
// of services, such as changing configuration or decorating the object.
foreach ($this->getExtenders($abstract) as $extender) {
    $object = $extender($object, $this);
}

// If the requested type is registered as a singleton we'll want to cache off
// the instances in "memory" so we can return it later without creating an
// entirely new instance of an object on each subsequent request for it.
if ($this->isShared($abstract) && ! $needsContextualBuild) {
    $this->instances[$abstract] = $object;
}

$this->fireResolvingCallbacks($abstract, $object);

// Before returning, we will also set the resolved flag to "true" and pop off
// the parameter overrides for this build. After those two things are done
// we will be ready to return back the fully constructed class instance.
$this->resolved[$abstract] = true;

array_pop($this->with);

return $object;

Их легче понять. В основном судит о наличии расширения, а затем о соответствующей функции расширения; если это связывающий синглтон, то результат разбора сохраняется в$this->instancesМассивы; наконец, проанализируйте «последствия».

Наконец, давайте посмотрим на парсинг тега tag:

/**
 * Resolve all of the bindings for a given tag.
 *
 * @param  string  $tag
 * @return array
 */
public function tagged($tag)
{
    $results = [];

    if (isset($this->tags[$tag])) {
        foreach ($this->tags[$tag] as $abstract) {
            $results[] = $this->make($abstract);
        }
    }

    return $results;
}

Лучше увидеть это сейчас, если значение тега входящего тега существуетtagsв массиве перебрать все$abstract, проанализируйте их один за другим и сохраните результат в виде выходного массива.

Суммировать

Хотя мы понимаем основное содержание Container, все еще есть много деталей, достойных нашего дальнейшего изучения, таких как: связанные с $alias, связанные с событиями, связанные с расширениями.

Наконец, я рекомендую всем взглянуть на этот Контейнер:silexphp/Pimple

A small PHP 5.3 dependency injection container pimple.symfony.com

Продолжение следует