原文地址: 从零开始理解 Laravel 依赖注入
大家在使用 Laravel 的过程中,可能会感觉到在 Laravel 里很多神奇的东西会发生。依赖注入似乎是一个。但它真的很神奇吗? 下面我们来具体看下。
Laravel 中的服务容器其实就是一个依赖注入容器和应用程序的注册表。
Laravel Container 是用于管理依赖项和存储对象的强大工具,可用于各种目的; 可以存储对象并在 Facades 中使用它们。
Laravel 通常使用依赖注入。即使访问 Request 我们也可以使用注入,比如。
public function __construct(Request $request)
当尝试向类中注入对象时,Container 使用 Reflection API 检查构造函数方法并检索依赖项的内容。
首先,反射 API 从深度维度中获取能量(抽象理解就好了),因此在使用反射 API 时必须小心。使用它,但不要滥用它。当检查许多对象时,反射是昂贵的,它有可能扰乱整个宇宙(有点夸张哈)。
反射通常被定义为程序能力,主要是指检查自身并在执行时修改其逻辑。
可以从官网查看 PHP.net 具体描述。
从 PHP5 开始 PHP 带有一个完整的反射 API,增加了对类,接口,函数,方法和扩展进行逆向工程的能力。另外,反射 API 提供了检索函数,类和方法的 doc 注释的方法。 发射在 PHP 中很流行。事实上,有几种情况即使不知道它也可以使用它。一些 PHP 的内置函数间接使用了反射 API--其中一个是
call_user_func函数。
我们可能经常使用 get_class 和 get_class_method 来理解别人的代码。
下面我们来快速看看如何使用 Reflection API 来处理函数。
可以使用 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 上查看。
<?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
}
我们必须知道类名来解决依赖关系,现在让我们停下来一分钟,弄清楚这个过程:。
以下是不使用 Container 的代码大致工作的方式:
Application 依赖类 Foo, 所以我们需要这么做:Application 前创建 FooApplication 中调用 FooFoo 依赖 Bar (比如一个 service), 所以:Foo 前,先创建 BarFoo 中调用 BarBar 依赖 Bim (比如可能是一个 service, 也可能是 repository, …), 所以:Bar 前先要创建 BimBar does something** 感觉如何?**
以下是使用 Container 的代码大致工作的方式:
Application 依赖 Foo, Foo 依赖 Bar, Bar 依赖 Bim, 所以:Application 直接发现的是 Bim, 所以直接创建 BimBim 时发现需要 Bar, 所以 Application 创建 Bar 并返回给 BimBar 时发现需要 Foo, 所以 Application 创建 Foo 并返回给 BarApplication 调用 FooFoo 调用 BarBar 调用 BimBim 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。看看 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;
}
好的。如果你不明白我会解释;
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;
}
}
就这样吧。看完上面的过程你应该对 Container 和依赖注入的工作原理有了更好的理解。
感谢阅读 ^_^
欢迎留言讨论。