Понимание Laravel Dependency Injection с нуля

PHP

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

Ларавочный контейнер (контейнер)

Сервисный контейнер в Laravel на самом деле представляет собой реестр контейнеров и приложений для внедрения зависимостей.

Laravel Container— мощный инструмент для управления зависимостями и хранения объектов, которые можно использовать для различных целей, объекты можно хранить и использовать в Фасадах.

Laravel обычно использует внедрение зависимостей. даже доступRequestМы также можем использовать инъекцию, например.

public function __construct(Request $request)

При попытке внедрить объекты в класс Container используетReflection APIПроверьте метод конструктора и извлеките содержимое зависимостей.

Что такое API отражения

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

Рефлексия обычно определяется как способность программы в основном ссылаться на изучение самой себя и изменение своей логики по мере ее выполнения.

Можно посмотреть на официальном сайтеPHP.netконкретное описание.

Начиная с PHP5 PHP поставляется с полным API-интерфейсом отражения, который добавляет возможность обратного проектирования классов, интерфейсов, функций, методов и расширений. Кроме того, API отражения предоставляет методы для получения комментариев к документам для функций, классов и методов.
Launch популярен в PHP. На самом деле, есть несколько ситуаций, когда вы можете использовать его, не зная об этом. Некоторые из встроенных функций PHP косвенно используют API отражения — одна из нихcall_user_funcфункция.

мы можем часто использоватьget_classа такжеget_class_methodпонимать чужой код.

Давайте кратко рассмотрим, как использоватьReflection APIдля обработки функции.

ReflectionFunction

можно использоватьReflectionFunctionПолучить информацию о функции.

<?php

function functionWithParameters($param1){

}

$reflectionFunction = new ReflectionFunction('functionWithParameters');

$name = $reflectionFunction->getName(); // functionWithParameters

$parameters = $reflectionFunction->getParameters();

/*
Array
    (
        [0] => ReflectionParameter Object
            (
                [name] => param1
            )
    )
 */

ReflectionClass

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

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

На самом деле это довольно просто, все, что вы хотите знать, можно найти вReflectionClassПосмотреть выше.

<?php

class OurDependencyClass{}

class OurTestClass
{

  public function __construct(OurDependencyClass $anotherClass)
  {
  }
}

$reflectedClass = new ReflectionClass(new OurTestClass());

// or

$reflectedClass = new ReflectionClass('OurTestClass');

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

Мы можем позвонитьgetConstructorметод проверки конструктора. он возвращаетReflectionMethod, который содержит почти все, что нам нужно.

<?php

$reflection = new ReflectionClass('OurTestClass');

$constructor = $reflection->getConstructor();

Нам нужно проверить параметры, как объяснялось ранее.ReflectionFuction.

предупреждать:Если в классе нет метода-конструктора, то$constructorбудет назначен какnull. Так что вы должны проверить это тоже.

<?php

// now we can retrieve out parameters

$parameters = $constructor->getParameters();

/*

array(1) {
  [0]=>
  object(ReflectionParameter)#3 (1) {
    ["name"]=>
    string(10) "otherClass"
  }
}

 output will be like this

*/

он возвращаетReflectionParameterмассивы и получить информацию о параметрах функции или метода.

Теперь мы рассмотрим все эти параметры и определим проблему, которую нам нужно решить.

<?php

foreach ($parameters as $parameter)
{
  $class = $parameter->getClass();

  if(null === $class){
       // this parameter doesn't have a class name
       // we can't resolve it so we will skip for now
  }

  $name = $class->name; // we'll use it later
}

Нам нужно знать имя класса для разрешения зависимостей, теперь давайте остановимся на минутку и разберемся с процессом: .

Классический PHP-код

Вот как примерно будет работать код без контейнера:

  • Applicationзависимый классFoo, поэтому нам нужно сделать:
  • использоватьApplicationперед созданиемFoo
  • существуетApplicationвызыватьFoo
  • FooполагатьсяBar(например,service), так:
  • В использованииFooпрежде, создайтеBar
  • существуетFooвызыватьBar
  • BarполагатьсяBim(например, может бытьservice, также может бытьrepository, …), так:
  • В использованииBarсоздать передBim
  • Bar does something

Как ты себя чувствуешь?

Использовать внедрение зависимостей

Вот как примерно будет работать код, использующий Container:

  • ApplicationполагатьсяFoo, FooполагатьсяBar, BarполагатьсяBim, так:
  • Applicationнайдено непосредственноBim, поэтому создайте напрямуюBim
  • СоздайтеBimкогда возникает необходимостьBar, такApplicationСоздайтеBarи вернуться кBim
  • СоздайтеBarкогда возникает необходимостьFoo, такApplicationСоздайтеFooи вернуться кBar
  • ApplicationпередачаFoo
  • FooпередачаBar
  • BarпередачаBim
  • Bim does something

ЭтоИнверсия контролярежим. Управление зависимостями между вызываемым и вызываемым объектом меняется на противоположное.

Ниже мы моделируем эту ситуацию в коде.

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

<?php

class Container
{
  /**
   *
   *  @param mixed $class
   * 
   */
  public function make($class)
  {
    // pass $class into ReflectionClass
    // note that: ReflectionClass may throw an Exception if user puts
    // a class name that doesn't exist.
    $reflection = new ReflectionClass($class);

    $constructor = $reflection->getConstructor();

    // we'll store the parameters that we resolved
    $resolvedParameters = [];

    foreach ($constructor->getParameters() as $parameter){

        $parameterClass = $parameter->getClass();

        if(null === $parameterClass){
           // this parameter is probably is a primitive variable
           // we can't resolve it so we will skip for now
        }

        $parameterName = $parameter->getName();
        $className = $parameterClass->name;

        // this function is becoming resursive now.
        // it'll continue 'till  nothing left.

        $resolvedParameters[$parameterName] = $this->make($className);

        // we need to know which value belongs to which parameter
        // so we'll store as an associative array.
    }
  }
}

Не пытайтесь запустить это! Это определенно потерпит неудачу.

Нам также нужно обратиться к примитивным переменным, также называемым параметрами. Итак, мы просто вставляем нашmakeДобавьте в метод необязательный параметр.

<?php

 /* change the method definition as follows;
 public function make($class, $params = [])
 */

$parameterName = $parameter->getName();

if(null === $parameterClass){
   // if our primitive parameter given by user we'll use it
   // if not, we'll just throw an Exception
   if(isset($params[$parameterName])){
       // this is just a very simple example
       // in real world you have to check whether this parameter passed by
       // reference or not
       $resolvedParameters[$parameterName]= $params[$parameterName];
    }else{
        throw new Exception(
               sprintf('Container could not solve %s parameter', $parameterName)
             );
    }
}

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

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

<?php

// this will create and return a new instance of given class.
return $reflection->newInstanceArgs($resolvedParameters);

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

<?php

class Container
{

    /**
     *
     * @param mixed $class
     * @param array $params
     *
     */
    public function make($class, array $params = [])
    {

        // pass $class into ReflectionClass
        // note that: ReflectionClass may throw an Exception if user puts
        // a class name that doesn't exist.
        $reflection = new ReflectionClass($class);

        //  if object does not have an constructor method, $constructor will be assigned null.
        // you better have check this too
        $constructor = $reflection->getConstructor();

        // we'll store the parameters that we resolved
        $resolvedParameters = [];

        foreach ($constructor->getParameters() as $parameter) {

            $parameterClass = $parameter->getClass();
            $className = $parameterClass->name;

            $parameterName = $parameter->getName();

            if (null === $parameterClass) {

                // if our primitive parameter given by user we'll use it
                // if not, we'll just throw an Exception
                if (isset($params[$parameterName])) {
                    // this is just a very simple example
                    // in real world you have to check whether this parameter passed by
                    // reference or not
                    $resolvedParameters[$parameterName] = $params[$parameterName];
                } else {
                    throw new Exception(
                        sprintf('Container could not solve %s parameter', $parameterName)
                    );
                }

            } else {

                // this function is becoming recursive now.
                // it'll continue 'till  nothing left.

                $resolvedParameters[$parameterName] = $this->make($className);

                // we need to know which value belongs to which parameter
                // so we'll store as an associative array.

            }
        }

        return $reflection->newInstanceArgs($resolvedParameters);
    }
}

То, что мы узнали до сих пор,Reflection APIи как мы можем использовать его для отражения функций, параметров и классов.

Вернуться к Ларавелю

Вернемся к Ларавелю. Посмотрите, как Laravel справляется с этим прогрессом. Давайте разберемся.

<?php

// When you call App::make or app()->make it refers to Container::make and it's just a duplication of  Container::resolve

class Container implements ArrayAccess, ContainerContract
{
    /**
     * Resolve the given type from the container.
     *
     * @param  string  $abstract
     * @param  array  $parameters
     * @return mixed
     */
    protected function resolve($abstract, $parameters = [])
    {

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

        return $object;

    }

}

Исходная функция длиннее и сложнее. Я уменьшил большую часть сложности.

Laravel проверяет объект и определяет, можно ли легко создать его экземпляр или сначала необходимо разрешить «вложенные» зависимости.

как мы?

<?php

    /**
     * Instantiate a concrete instance of the given type.
     *
     * @param  string  $concrete
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function build($concrete)
    {

        $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)) {
            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);
    }

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

<?php

    /**
     * Resolve all of the dependencies from the ReflectionParameters.
     *
     * @param  array  $dependencies
     * @return array
     */
    protected function resolveDependencies(array $dependencies)
    {
        $results = [];

        foreach ($dependencies as $dependency) {
            // If this dependency has a override for this particular build we will use
            // that instead as the value. Otherwise, we will continue with this run
            // of resolutions and let reflection attempt to determine the result.
            if ($this->hasParameterOverride($dependency)) {
                $results[] = $this->getParameterOverride($dependency);

                continue;
            }

            // If the class is null, it means the dependency is a string or some other
            // primitive type which we can not resolve since it is not a class and
            // we will just bomb out with an error since we have no-where to go.
            $results[] = is_null($class = $dependency->getClass())
                            ? $this->resolvePrimitive($dependency)
                            : $this->resolveClass($dependency);
        }

        return $results;
    }

OK. Если вы не понимаете, я объясню;

Laravel проверяет, является ли зависимость примитивным классом или классом, и обрабатывает ее на основе этого.

<?php

    /**
     * Resolve a class based dependency from the container.
     *
     * @param  \ReflectionParameter  $parameter
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function resolveClass(ReflectionParameter $parameter)
    {
        try {
            return $this->make($parameter->getClass()->name);
        }

        // If we can not resolve the class instance, we will check to see if the value
        // is optional, and if it is we will return the optional parameter value as
        // the value of the dependency, similarly to how we do this with scalars.
        catch (BindingResolutionException $e) {
            if ($parameter->isOptional()) {
                return $parameter->getDefaultValue();
            }

            throw $e;
        }
    }

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

Спасибо за прочтение ^_^

Добро пожаловать, чтобы оставить сообщение для обсуждения.