Fuhui

了解Laravel依赖注入


随笔

突然想了解一下Laravel,然后发现:它没有我想象的那么简单,很多的调用都找不到入口。加载view的逻辑,看了很长时间,还是没有搞明白:一个值传递的参数,怎么好好的就变了呢?下面都是看别的的文章的总结,我还要继续完善,直到搞清楚这个view是怎么实现的。

看了两篇文章,介绍了如何使用xdebug断点调试php及测试性能。作为了解Laravel的必要工具,也介绍进来。

  1. How to Install Xdebug with PHPStorm and Vagrant
  2. Debugging and Profiling PHP with Xdebug

概要

使用use Illuminate\Container\Container;作为参考的例子。

可以浏览原创:Laravel Container (容器) 深入理解 (下)

摘抄Laravel

The Laravel service container is a powerful tool for managing class dependencies and performing dependency injection.

通过config/app.php可以查看LaravelService containerService下的register便是用来创建binding的。通过php artisan make:provider CustomServiceProvider创建自定义的ServiceProvider

There is no need to bind classes into the container if they do not depend on any interfaces. The container does not need to be instructed on how to build these objects, since it can automatically resolve these objects using reflection.

type hint

对于type hint类型的注入。比如MyContainer构造函数注入:public function __construct(AnotherClass $dependency),在构造函数中声明了依赖类。当我们直接使用Container去创建该对象时,Container会尝试为我们自动创建AnotherClass这个类。

比如我们执行$container->make(MyContainer::class);,框架自动帮我们去创建AnotherClass类。也需要看AnotherClass类是否配合。如果AnotherClass的构造函数中指定了传递参数__construct($a, $b),但在make函数中我们并没有传递任何参数,此时系统并不能帮我们自动生成。而是会报错:

make主要来创建该实例类,等同于代码中的new

Illuminate\Contracts\Container\BindingResolutionException : 
Unresolvable dependency resolving [Parameter #0 [ <required> $a ]] in class Tests\Unit\AnotherClass

Binding Interfaces to Implementations

代码开发中,上层模型不应该依赖底层,两者应该依赖抽象。抽象不应该依赖具体,具体却应该依赖抽象。

例子也请参照这篇文章:Dependency Injection。简要介绍:FileSessionStorageMySqlSessionStorage实现了SessionStorage接口。之后在SimpleAuth构造函数中注入依赖public function __construct(SessionStorage $session )

当我们调用make去实例化SimpleAuth对象时,因为仅通过接口,无法明确实例化一个实现该接口的类。系统报错:

Illuminate\Contracts\Container\BindingResolutionException : 
Target [Tests\Unit\SessionStorage] is not instantiable while building [Tests\Unit\SimpleAuth].

解决的办法是Register a binding。明确告诉系统,这个interface去具体实现那个类:$container->bind( SessionStorage::class, MysqlSessionStorage::class);

结合这个例子,绑定之后意味着:通过这个interfacemake再也无法创建FileSessionStorage类了。只能更新binding才能切换了。

Closure Binding

方法:bind($abstract, $concrete = null, $shared = false)

方法主要是Register a binding,保存了abstractconcreteshared的对应关系。

内部其实对concrete做了处理:if (! $concrete instanceof Closure){,最终还是会将该concrete转换为一个闭包函数,在框架初始化abstract时调用该闭包函数。

这样设计属于lazily mode,只有当对象对调用时,才会创建对象。具体可以查看Factories。闭包函数的唯一参数是:Container $container。当实例化对象时,this会被传入:return $concrete($this, $this->getLastParameterOverride());

Resolving Callbacks

Container创建完对象之后,执行的回调函数。

方法:resolving($abstract, Closure $callback = null)

Container将回调函数保存到resolvingCallbacks变量中:$this->resolvingCallbacks[$abstract][] = $callback;,可以创建多个callback回调。

当对象创建完成之后,执行fireResolvingCallbacks($abstract, $object)来调用函数。

Extending a Class

用来对一个对象进行扩展,可以封装一个类然后返回一个不同的对象 (装饰模式)。代码中$object = $extender($object, $this);负责处理这个逻辑。

方法:extend($abstract, Closure $closure)

当实例已经存在,直接调用回调函数处理。否则,将Closure存储到成员变量中:$this->extenders[$abstract][] = $closure;

resolve创建对象成功之后,依次调用extenders中的回调进行处理。所以,回调定义的顺序就显得重要了。而且回调函数最后也必须返回一个正确的对象。

singleton

Container使用instances来存储单例对象,且通过isShared方法来判断是否为单例对象

方法:singleton($abstract, $concrete = null)

bind方法完全相同,只是明确的将shared参数设置为true。之后,在$this->bindings[$abstract] = compact('concrete', 'shared');shared标识保存在binding数组中。

当调用make创建对象之前,首先查看isset($this->instances[$abstract])是否存在。

方法:instance($abstract, $instance)

将已经存在的实例,设置为单例模式。其实就是将该实例保存到instances中:$this->instances[$abstract] = $instance;

Arbitrary Binding Names

方法:bind($abstract, $concrete = null, $shared = false)

对于abstract可以指定任意的字符串,而不是类/接口名称。但这种情况下不能使用类型提示,并且只能用make() 来获取实例。

方法:alias($abstract, $alias)

给一个类型指定一个别名。该方法操作两个变量aliasesabstractAliases。具体如下:$this->aliases[$alias] = $abstract; $this->abstractAliases[$abstract][] = $alias;

可以看出:别名唯一对应一个类型,类型可以对应多个别名。

Contextual Binding

参照:传送门