В процессе использования 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;
}
}
Пусть будет так. После прочтения описанного выше процесса вы должны лучше понять, как работают контейнеры и внедрение зависимостей.
Спасибо за прочтение ^_^
Добро пожаловать, чтобы оставить сообщение для обсуждения.