下面由laravel框架教程栏目给大家详解laravel—ioc容器,希望对需要的朋友有所帮助!
1.依赖ioc( inversion of controller )叫做控制反转模式,也可以称为(dependency injection ) 依赖注入模式。要理解依赖注入的概念我们先理解下什么依赖
//支付宝支付class alipay { public function __construct(){} public function pay() { echo 'pay bill by alipay'; }}//微信支付class wechatpay { public function __construct(){} public function pay() { echo 'pay bill by wechatpay'; }}//银联支付class unionpay{ public function __construct(){} public function pay() { echo 'pay bill by unionpay'; }}//支付账单class paybill { private $paymethod; public function __construct( ) { $this->paymethod= new alipay (); } public function paymybill() { $this->paymethod->pay(); }}$pb = new paybill ();$pb->paymybill();
通过上面的代码我们知道,当我们创建一个class paybill 的实例的时候, paybill的构造函数里面有{ $this->paymethod= new alipay (); }, 也就是实例化了一个class alipay . 这个时候依赖就产生了, 这里可以理解为当我想用支付宝支付的时候, 那我首先要获取到一个支付宝的实例,或者理解为获取支付宝的功能支持. 当用我们完 new 关键字的时候, 依赖其实已经解决了,因为我们获取了alipay 的实例.
其实在我知道ioc概念之前,我的代码中大部分都是这种模式 ~ _ ~ . 这种有什么问题呢, 简单来说, 比如当我想用的不是支付宝而是微信的时候怎么办, 你能做的就是修改payment 的构造函数的代码,实例化一个微信支付wechatpay.
如果我们的程序不是很大的时候可能还感觉不出什么,但是当你的代码非常复杂,庞大的时候,如果我们的需求经常改变,那么修改代码就变的非常麻烦了。所以ioc 的思想就是不要在 class payment 里面用new 的方式去实例化解决依赖, 而且转为由外部来负责,简单一点就是内部没有new 的这个步骤,通过依赖注入的方式同样的能获取到支付的实例.
2.依赖注入依赖我们知道了是什么意思,那依赖注入又是什么意思呢,我们把上面的代码拓展一下
//支付类接口interface pay{ public function pay();}//支付宝支付class alipay implements pay { public function __construct(){} public function pay() { echo 'pay bill by alipay'; }}//微信支付class wechatpay implements pay { public function __construct(){} public function pay() { echo 'pay bill by wechatpay'; }}//银联支付class unionpay implements pay { public function __construct(){} public function pay() { echo 'pay bill by unionpay'; }}//付款class paybill { private $paymethod; public function __construct( pay $paymethod) { $this->paymethod= $paymethod; } public function paymybill() { $this->paymethod->pay(); }}//生成依赖$paymethod = new alipay();//注入依赖$pb = new paybill( $paymethod );$pb->paymybill();
上面的代码中,跟之前的比较的话,我们加入一个pay 接口, 然后所有的支付方式都继承了这个接口并且实现了pay 这个功能. 可能大家会问为什么要用接口,这个我们稍后会讲到.
当我们实例化paybill的之前, 我们首先是实例化了一个alipay,这个步骤就是生成了依赖了,然后我们需要把这个依赖注入到paybill 的实例当中,通过代码我们可以看到 { $pb = new paybill( paymethod ); }, 我们是通过了构造函数把这个依赖注入了paybill 里面. 这样一来 $pb 这个paybill 的实例就有了支付宝支付的能力了.
把class alipay 的实例通过constructor注入的方式去实例化一个 class paybill. 在这里我们的注入是手动注入, 不是自动的. 而laravel 框架实现则是自动注入.
3.反射在介绍ioc 的容器之前我们先来理解下反射的概念(reflection),因为ioc 容器也是要通过反射来实现的.从网上抄了一段来解释反射是什么意思
“反射它指在php运行状态中,扩展分析php程序,导出或提取出关于类、方法、属性、参数等的详细信息,包括注释。这种动态获取的信息以及动态调用对象的方法的功能称为反射api。反射是操纵面向对象范型中元模型的api,其功能十分强大,可帮助我们构建复杂,可扩展的应用。其用途如:自动加载插件,自动生成文档,甚至可用来扩充php语言”
举个简单的例子
class b{}class a { public function __construct(b $args) { } public function dosomething() { echo 'hello world'; }}//建立class a 的反射$reflection = new reflectionclass('a');$b = new b();//获取class a 的实例$instance = $reflection ->newinstanceargs( [ $b ]);$instance->dosomething(); //输出 ‘hellow world’$constructor = $reflection->getconstructor();//获取class a 的构造函数$dependencies = $constructor->getparameters();//获取class a 的依赖类dump($constructor);dump($dependencies);
dump 的得到的$constructor 和 $dependencies 結果如下
//constructorreflectionmethod {#351 +name: "__construct" +class: "a" parameters: array:1 [] extra: array:3 [] modifiers: "public"}//$dependenciesarray:1 [ 0 => reflectionparameter {#352 +name: "args" position: 0 typehint: "b" }]
通过上面的代码我们可以获取到 class a 的构造函数,还有构造函数依赖的类,这个地方我们依赖一个名字为 ‘args’ 的量,而且通过typehint可以知道他是类型为 class b; 反射机制可以让我去解析一个类,能过获取一个类里面的属性,方法 ,构造函数, 构造函数需要的参数。 有个了这个才能实现laravel 的ioc 容器.
4.ioc容器接下来介绍一下laravel 的ioc服务容器概念. 在laravel框架中, 服务容器是整个laravel的核心,它提供了整个系统功能及服务的配置, 调用. 容器按照字面上的理解就是装东西的东西,比如冰箱, 当我们需要冰箱里面的东西的时候直接从里面拿就行了. 服务容器也可以这样理解, 当程序开始运行的时候,我们把我们需要的一些服务放到或者注册到(bind)到容器里面,当我需要的时候直接取出来(make)就行了. 上面提到的 bind 和 make 就是注册 和 取出的 两个动作.
5. ioc 容器代码好了,说了这么多,下面要上一段容器的代码了. 下面这段代码不是laravel 的源码, 而是来自一本书《laravel 框架关键技术解析》. 这段代码很好的还原了laravel 的服务容器的核心思想. 代码有点长, 小伙伴们要耐心看. 当然小伙伴完全可以试着运行一下这段代码,然后调试一下,这样会更有助于理解.
<?php //容器类装实例或提供实例的回调函数class container { //用于装提供实例的回调函数,真正的容器还会装实例等其他内容 //从而实现单例等高级功能 protected $bindings = []; //绑定接口和生成相应实例的回调函数 public function bind($abstract, $concrete=null, $shared=false) { //如果提供的参数不是回调函数,则产生默认的回调函数 if(!$concrete instanceof closure) { $concrete = $this->getclosure($abstract, $concrete); } $this->bindings[$abstract] = compact('concrete', 'shared'); } //默认生成实例的回调函数 protected function getclosure($abstract, $concrete) { return function($c) use ($abstract, $concrete) { $method = ($abstract == $concrete) ? 'build' : 'make'; return $c->$method($concrete); }; } public function make($abstract) { $concrete = $this->getconcrete($abstract); if($this->isbuildable($concrete, $abstract)) { $object = $this->build($concrete); } else { $object = $this->make($concrete); } return $object; } protected function isbuildable($concrete, $abstract) { return $concrete === $abstract || $concrete instanceof closure; } //获取绑定的回调函数 protected function getconcrete($abstract) { if(!isset($this->bindings[$abstract])) { return $abstract; } return $this->bindings[$abstract]['concrete']; } //实例化对象 public function build($concrete) { if($concrete instanceof closure) { return $concrete($this); } $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(); $instances = $this->getdependencies($dependencies); return $reflector->newinstanceargs($instances); } //解决通过反射机制实例化对象时的依赖 protected function getdependencies($parameters) { $dependencies = []; foreach($parameters as $parameter) { $dependency = $parameter->getclass(); if(is_null($dependency)) { $dependencies[] = null; } else { $dependencies[] = $this->resolveclass($parameter); } } return (array)$dependencies; } protected function resolveclass(reflectionparameter $parameter) { return $this->make($parameter->getclass()->name); }}
上面的代码就生成了一个容器,下面是如何使用容器
$app = new container();$app->bind("pay", "alipay");//pay 为接口, alipay 是 class alipay$app->bind("trytopaymybill", "paybill"); //trytopaymybill可以当做是class paybill 的服务别名//通过字符解析,或得到了class paybill 的实例$paybill = $app->make("trytopaymybill"); //因为之前已经把pay 接口绑定为了 alipay,所以调用pay 方法的话会显示 'pay bill by alipay '$paybill->paymybill();
当我们实例化一个container得到 $app 后, 我们就可以向其中填充东西了
$app->bind("pay", "alipay");$app->bind("trytopaymybill", "paybill");
当执行完这两行绑定码后, $app 里面的属性 $bindings 就已经有了array 值,是啥样的呢,我们来看下
array:2 [ "app\http\controllers\pay" => array:2 [ "concrete" => closure {#355 class: "app\http\controllers\container" this:container{[#354](http://127.0.0.4/ioc#sf-dump-254248394-ref2354) …} parameters: array:1 [ "$c" => [] ] use: array:2 [ "$abstract" => "app\http\controllers\pay" "$concrete" => "app\http\controllers\alipay" ] file: "c:\project\test\app\http\controllers\ioccontroller.php" line: "119 to 122" } "shared" => false ]"trytopaymybill" => array:2 [ "concrete" => closure {#359 class: "app\http\controllers\container" this:container{[#354](http://127.0.0.4/ioc#sf-dump-254248394-ref2354) …} parameters: array:1 [ "$c" => [] ] use: array:2 [ "$abstract" => "trytopaymybill" "$concrete" => "\app\http\controllers\paybill" ] file: "c:\project\test\app\http\controllers\ioccontroller.php" line: "119 to 122" } "shared" => false ]]
当执行 $paybill = $app->make(“trytopaymybill”); 的时候, 程序就会用make方法通过闭包函数的回调开始解析了.
解析’trytopaybill’ 这个字符串, 程序通过闭包函数 和build方法会得到 ‘paybill’ 这个字符串,该字符串保存在$concrete 上. 这个是第一步. 然后程序还会以类似于递归方式 将$concrete 传入 build() 方法. 这个时候build里面就获取了$concrete = ‘paybill’. 这个时候反射就派上了用场, 大家有没有发现,paybill 不就是 class paybill 吗? 然后在通过反射的方法reflectionclass(‘paybill’) 获取paybill 的实例. 之后通过getconstructor(),和getparameters() 等方法知道了 class paybill 和 接口pay 存在依赖
//$constructor = $reflector->getconstructor();reflectionmethod {#374 +name: "__construct" +class: "app\http\controllers\paybill" parameters: array:1 [ "$paymethod" => reflectionparameter {#371 +name: "paymethod" position: 0 typehint: "app\http\controllers\pay" } ] extra: array:3 [ "file" => "c:\project\test\app\http\controllers\ioccontroller.php" "line" => "83 to 86" "isuserdefined" => true ] modifiers: "public"}//$dependencies = $constructor->getparameters();array:1 [ 0 => reflectionparameter {#370 +name: "paymethod" position: 0 typehint: "app\http\controllers\pay" }]
接着,我们知道了有’pay’这个依赖之后呢,我们要做的就是解决这个依赖,通过 getdependencies($parameters), 和 resolveclass(reflectionparameter $parameter) ,还有之前的绑定$app->bind(“pay”, “alipay”); 在build 一次的时候,通过 return new $concrete;到这里我们得到了这个alipay 的实例
if(is_null($constructor)) { return new $concrete; }
到这里我们总算结局了这个依赖, 这个依赖的结果就是实例化了一个 alipay. 到这里还没结束
$instances = $this->getdependencies($dependencies);
上面的$instances 数组只有一个element 那就是 alipay 实例
array:1 [0 =>alipay {#380} ]
最终通过 newinstanceargs() 方法, 我们获取到了 paybill 的实例。
return $reflector->newinstanceargs($instances);
到这里整个流程就结束了, 我们通过 bind 方式绑定了一些依赖关系, 然后通过make 方法 获取到到我们想要的实例. 在make中有牵扯到了闭包函数,反射等概念.
好了,当我们把容器的概念理解了之后,我们就可以理解下为什么要用接口这个问题了. 如果说我不想用支付宝支付,我要用微信支付怎么办,too easy.
$app->bind("pay", "wechatpay");$app->bind("trytopaymybill", "paybill");$paybill = $app->make("trytopaymybill"); $paybill->paymybill();
是不是很简单呢, 只要把绑定从’alipay’ 改成 ‘wechatpay’ 就行了,其他的都不用改. 这就是为什么我们要用接口. 只要你的支付方式继承了pay 这个接口,并且实现pay 这个方法,我们就能够通过绑定正常的使用. 这样我们的程序就非常容易被拓展,因为以后可能会出现成百上千种的支付方式.
好了,到这里不知道小伙伴有没有理解呢,我建议大家可以试着运行下这些代码, 这样理解起来会更快.同时推荐大家去看看 《laravel 框架关键技术解析》这本书,写的还是不错的.
以上就是详解laravel—ioc容器的详细内容。