提到laravel框架,我的脑海中最先浮现的是什么呢?那一定是,依赖注入、控制反转。作为后来居上的框架,laravel能取得如此的成绩,优秀的思想也贡献了很大一部分力量。
什么是Container?
定义:
那么在提到依赖注入之前我们就不得不先说一说一切的根本:容器Container。所谓容器,听名字就知道,是一个仓库,装东西用的,所以,container所有的功能,都围绕一个主题:管理类。
工作流程:
首先,生成一个数组绑定列表,用自定义名称作为主键,然后键值是闭包(输入的可能是闭包或者实体类,但是,在存储的时候,都统一转化成了闭包存储)。
其次,根据绑定列表,生成对应的类的实例,供用户使用,调用的时候,发现如果已经生成,不需要重新生成使用,实际上,Container就是类的一个仓库,以及缓存。
在laravel中Container的实现类为\Illuminate\Container\Container,该类提供了多个操作类的方法。
Container位于…/vendor/laravel/framework/src/Illuminate/Container/Container.php中,接下来我们来看下其常用方法(因版本不同,源码可能略有偏差,如果问题,欢迎留言指正!!!)
别名函数:
/**
* 此方法会给abstract类设置一个alias别名
*/
public function alias($abstract, $alias)
{
$this->aliases[$alias] = $abstract;
$this->abstractAliases[$abstract][] = $alias;
}
/**
*此方法会获取abstract的别名,如果abstract本身为别名则直接返回
**/
public function getAlias($abstract)
{
if (! isset($this->aliases[$abstract])) {
return $abstract;
}
if ($this->aliases[$abstract] === $abstract) {
throw new LogicException("[{$abstract}] is aliased to itself.");
}
return $this->getAlias($this->aliases[$abstract]);
}
/**
*判断$name是否为别名
**/
public function isAlias($name)
{
return isset($this->aliases[$name]);
}
isBuildable函数:
用来判断指定类型是否可构建(实例化)。满足二者之一则可实例化:实际类型是Closure或者实际类型与抽象类型名称相同(其实就是普通的类名称,非抽象类和接口)
protected function isBuildable($concrete, $abstract)
{
return $concrete === $abstract || $concrete instanceof Closure;
}
容器绑定函数:bind、singleton
singleton是单例的,是bind函数share的一个应用。
public function bind($abstract, $concrete = null, $shared = false)
{
// 删除所有陈旧的实例和别名
$this->dropStaleInstances($abstract);
//如果$concrete为null,则认为$abstract为可类名称
if (is_null($concrete)) {
$concrete = $abstract;
}
// 如果concrete不是闭包,则手动创建闭包
if (! $concrete instanceof Closure) {
$concrete = $this->getClosure($abstract, $concrete);
}
//注册绑定。实际就是放到bindings数组中
$this->bindings[$abstract] = compact('concrete', 'shared');
// 如果abstract已解析,则重新注册回调,可以通过毁掉的方式更新已注册的实例
if ($this->resolved($abstract)) {
$this->rebound($abstract);
}
}
实例化函数:make
make函数会自动完成类依赖的注入。
public function make($abstract, array $parameters = [])
{
//获取类别名
$abstract = $this->getAlias($this->normalize($abstract));
//如果是单例则直接返回
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}
//获取给定抽象的具体类型(这里自己其实也有点迷糊,欢迎大佬指正)
$concrete = $this->getConcrete($abstract);
// 实例化对象,递归调用(make类A时自动注入类B其实就是在这里实现的)
if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($concrete, $parameters);
} else {
$object = $this->make($concrete, $parameters);
}
//执行类的扩展。并将他们应用于正在构建的对象
foreach ($this->getExtenders($abstract) as $extender) {
$object = $extender($object, $this);
}
//如果为singleton单例模式则保存实例化的对象到单例instances数组
if ($this->isShared($abstract)) {
$this->instances[$abstract] = $object;
}
//触发类型解析回调
$this->fireResolvingCallbacks($abstract, $object);
//标记实例为已解析
$this->resolved[$abstract] = true;
//返回实例化对象
return $object;
}
实例化函数:build
用来实例化指定类型的对象,等同于直接new 对象,只不过这个操作有容器来完成
public function build($concrete, array $parameters = [])
{
//如果类型为闭包,则直接执行该闭包并返回
if ($concrete instanceof Closure) {
return $concrete($this, $parameters);
}
$reflector = new ReflectionClass($concrete);
// 如果对象类型不可实例化,则抛出异常
if (! $reflector->isInstantiable()) {
if (! empty($this->buildStack)) {
$previous = implode(', ', $this->buildStack);
$message = "Target [$concrete] is not instantiable while building [$previous].";
} else {
$message = "Target [$concrete] is not instantiable.";
}
throw new BindingResolutionException($message);
}
$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.
$parameters = $this->keyParametersByArgument(
$dependencies, $parameters
);
$instances = $this->getDependencies(
$dependencies, $parameters
);
array_pop($this->buildStack);
return $reflector->newInstanceArgs($instances);
}
接下来我们通过一些小例子来加深理解。
abstract class Animal
{
abstract public function run();
}
class Fish extends Animal
{
protected $name;
public function __construct($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
public function run()
{
echo 'Fish run';
}
}
class Pig extends Animal
{
protected $name;
public function __construct($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
public function run()
{
echo 'Pig run';
}
}
class People
{
protected $food;
public function __construct(\Animal $food)
{
$this->food = $food;
}
public function eat()
{
$this->food->run();
}
}
$object2 = new Fish('panda2'); // 传统方式:用new关键字实例化对象
echo $object2->getName(); // panda
$container = new \Illuminate\Container\Container();
$object = $container->build('Fish', ['panda']); // 用容器来实例化对象
// $object = $container->make('Fish', ['panda']); // 用容器来实例化对象,用make也是OK的
echo $object->getName(); // panda
$container->bind('Animal', 'Fish'); // 绑定抽象类 Animal 到 Fish 实现
$object3 = $container->make('Animal', ['goldfish']); // 此时实例化 Animal 则会返回 Pig 对象
echo $object3->run(); // Pig run
$container->flush();
$container->singleton('Animal', 'Fish'); // 绑定抽象类 Animal 到 Fish 实现,单例模式
$object4 = $container->make('Animal', ['goldfish']);
$object5 = $container->make('Animal', ['goldfish']); // 多次实例化返回的是同一个对象
var_dump($object4 === $object5); // true
$container->flush();
$container->bind('Animal', 'Fish'); // 绑定抽象类 Animal 到 Fish 实现
$people = $container->make('people'); // 实例化People,自动注入 $food 参数,无需手动实例化传入
$people->eat(); // Fish run
小结
本章节我们学历了laravel的容器及其常用的相关函数,希望对大家阅读源码有帮助,后续我们将继续对laravel源码进行学习,有兴趣的动动小手点个关注吧,感谢观看!