对于laravel的服务容器不是很理解看了《Laravel框架关键技术解析》和网上的一些资料后对于服务容器有了一些自己的理解,在这里分享给大家,过程比较曲折,看了比较长的时间,包括回调函数,use用法,反射函数等需要查相关的资料,稍后我也做一些更新。
简化版的IoC容器类,使用bind()函数进行服务绑定,使用make()函数来进行解析,最后在容器内由build()函数创建并返回实例。下面是具体如何使用。
IOC依赖注入过程:
1、make 容器已经存在接口和实例的关系, 获取回调函数;('Go_To_School', 'Bicycle' )和('studentaaaa', 'Student')
2、执行make('studentaaaa'),回调函数存在,执行回调函数;(回调函数开始实例化【在初始bind的时候】对象Student);
3、实例化过程:利用反射先找到Student的构造函数的参数:trafficTool;
再通过反射机制实例化对象时的依赖,找到trafficTool的接口:Go_To_School;找到Go_To_School去找相关的make,对Go_To_School进行实例化;
【重复2的过程:Go_To_School回调函数存在,执行回调函数;(回调函数开始实例化【在初始bind的时候】对象Bicycle);
利用反射先找到Bicycle,因该函数不需要构造函数,所以直接new一个对象来返回】;
4、利用反射机制函数实例化对象:object(Student)[7]private 'trafficTool' => object(Bicycle)[9]
5、容器初始化完成;
代码如下:
<?php
//设计公共接口
interface Go_To_School
{
public function go();
}
//实现交通工具类
class Foot implements Go_To_School
{
public function go()
{
echo 'walt to school';
}
}
class Car implements Go_To_School
{
public function go()
{
echo 'drive to school';
}
}
class Bicycle implements Go_To_School
{
public function go()
{
echo 'ride to school;';
}
}
//设计学生类,实现去学校时要依赖的交通工具实例
class Student
{
private $trafficTool;
public function __construct(Go_To_School $trafficTool)
{
$this->trafficTool = $trafficTool;
}
public function go_to_school(){
$this->trafficTool->go();
}
}
class Container {
//用于装提供实例的回调函数,真正的容器还会装实例等其他内容
//从而实现单例等高级功能
public $bindings = [];
//绑定接口和生成相应实例的回调函数
public function bind($abstract, $concrete=null, $shared=false) {
//如果提供的参数不是回调函数,则产生默认的回调函数 即concrete变为回调函数,不是一个字符串
if(!$concrete instanceof Closure) {
$concrete = $this->getClosure($abstract, $concrete);
}
$this->bindings[$abstract] = compact('concrete', 'shared');
}
//默认生成实例的回调函数
protected function getClosure($abstract, $concrete) {
//使用bing函数的时候不执行该函数;仅仅把$abstract, $concrete 放入$container对象中;
return function($container) use ($abstract, $concrete) {
$method = ($abstract == $concrete) ? 'build' : 'make';
echo "回调函数".$abstract;
var_dump( $concrete);
//利用回调函数来实例化
return $container->$method($concrete);
};
}
//解决接口和要实例化类之间的依赖关系
public function make($abstract) {
$concrete = $this->getConcrete($abstract);
echo "make<br>";
var_dump($concrete);
if($this->isBuildable($concrete, $abstract)) {
echo "build<br>";
$object = $this->build($concrete);
} else {
echo "make2<br>";
$object = $this->make($concrete);
}
echo "make end";
return $object;
}
protected function isBuildable($concrete, $abstract) {
return $concrete === $abstract || $concrete instanceof Closure;
}
//获取绑定的回调函数
protected function getConcrete($abstract) {
var_dump($this->bindings);echo $abstract;
//如果是接口或者别名(Go_To_School,studentaaaa)返回回调函数来执行;如果是实例类,则返回实例类字符串
if(!isset($this->bindings[$abstract])) {
echo "stringabstract";
return $abstract;
}
return $this->bindings[$abstract]['concrete'];
}
//实例化对象
public function build($concrete) {
if($concrete instanceof Closure) {
echo "instanceof";
//执行回调函数
return $concrete($this);
}
echo "build detail<br>";
$reflector = new ReflectionClass($concrete);
//检查类是否可实例化
if(!$reflector->isInstantiable()) {
echo $message = "Target [$concrete] is not instantiable";
}
// 获取类的构造函数
$constructor = $reflector->getConstructor();
if(is_null($constructor)) {
return new $concrete;
}
$dependencies = $constructor->getParameters();
//public 'name' => string 'trafficTool'
$instances = $this->getDependencies($dependencies);
var_dump($reflector->newInstanceArgs($instances));
//object(Student)[7]private 'trafficTool' => object(Bicycle)[9]
return $reflector->newInstanceArgs($instances);
}
//解决通过反射机制实例化对象时的依赖
protected function getDependencies($parameters) {
$dependencies = [];
foreach($parameters as $parameter) {
// public 'name' => string 'Go_To_School'
$dependency = $parameter->getClass();
if(is_null($dependency)) {
$dependencies[] = NULL;
} else {
$dependencies[] = $this->resolveClass($parameter);
}
}
return (array)$dependencies;
}
protected function resolveClass(ReflectionParameter $parameter) {
var_dump($parameter->getClass()->name);
//Go_To_School
return $this->make($parameter->getClass()->name);
}
}
//实例化IOC容器
$ioc = new Container();
//填充容器
$ioc->bind('Go_To_School', 'Bicycle'); //第一个参数'Go_To_School'是接口,
//第二个参数'Car'是交通工具类
$ioc->bind('studentaaaa', 'Student'); //第一个参数'student'可以理解为服务别名,用make()
//实例化的时候直接使用别名即可,第二个参数'Student'是学生类
//通过容器实现依赖注入,完成类的实例化
//执行回调函数
$student = $ioc->make('studentaaaa');
$student->go_to_school();
现在,我们不仅解除了学生类与交通工具类的依赖关系,而且容器类没有和他们产生任何依赖。我们通过注册、绑定(bind)的方式向容器中添加一段可以被执行的回调(可以是匿名函数、非匿名函数、类的方法)作为创建一个类的实例的方法,只有在真正的创建(make)操作被调用执行时,才会触发。这样一种方式,使得我们更容易在创建一个实例的同时解决其依赖关系,并且更加灵活。当有新的需求,只需另外绑定一个回调即可。例如现在我们想步行去学校,只要再绑定$ioc->bind('Go_To_School', 'Foot');就可以了。用何种方式去学校,我们可以自由的选择。
通过上述例子可以看到IOC容器最核心的功能,解决依赖注入的根本问题。在实现过程中,没有用new关键字来实例化对象,不需要人来关注组件之间的依赖关系,只要在容器填充过程中理顺接口和实现类之间的关系及实现类与依赖接口之间的关系就可以流水线式的完成实现类的实例化过程。