分享 - 如何从零开始理解 Laravel 依赖注入 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
qloog
V2EX    PHP

分享 - 如何从零开始理解 Laravel 依赖注入

  •  
  •   qloog 2018-05-30 14:50:12 +08:00 2836 次点击
    这是一个创建于 2743 天前的主题,其中的信息可能已经有所发展或是发生改变。

    原文地址: 从零开始理解 Laravel 依赖注入

    大家在使用 Laravel 的过程中,可能会感觉到在 Laravel 里很多神奇的东西会发生。依赖注入似乎是一个。但它真的很神奇吗? 下面我们来具体看下。

    Laravel 容器(Container)

    Laravel 中的服务容器其实就是一个依赖注入容器和应用程序的注册表。

    Lravel Container 是用于管理依赖项和存储对象的强大工具,可用于各种目的; 可以存储对象并在 Facades 中使用它们。

    Laravel 通常使用依赖注入。即使访问 Request 我们也可以使用注入,比如。

    public function __construct(Request $request) 

    当尝试向类中注入对象时,Container 使用 Reflection API 检查构造函数方法并检索依赖项的内容。

    Reflection Api 是什么

    首先,反射 API 从深度维度中获取能量(抽象理解就好了),因此在使用反射 API 时必须小心。使用它,但不要滥用它。当检查许多对象时,反射是昂贵的,它有可能扰乱整个宇宙(有点夸张哈)。

    反射通常被定义为程序能力,主要是指检查自身并在执行时修改其逻辑。

    可以从官网查看 PHP.net 具体描述。

    从 PHP5 开始 PHP 带有一个完整的反射 API,增加了对类,接口,函数,方法和扩展进行逆向工程的能力。另外,反射 API 提供了检索函数,类和方法的 doc 注释的方法。 发射在 PHP 中很流行。事实上,有几种情况即使不知道它也可以使用它。一些 PHP 的内置函数间接使用了反射 API--其中一个是call_user_func 函数。

    我们可能经常使用 get_classget_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 代码

    以下是不使用 Container 的代码大致工作的方式:

    • 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

    让我们回到 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 和依赖注入的工作原理有了更好的理解。

    感谢阅读 ^_^

    欢迎留言讨论。

    目前尚无回复
    关于     帮助文档     自助推广系统     博客     API   FAQ     Solana     920 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 39ms UTC 20:42 PVG 04:42 LAX 12:42 JFK 15:42
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86