对于php
的框架,不管是yii
,symfony
或者是laravel
,你们都在工做中有涉猎。对于在框架中的存放着资源包vendor
文件夹,入口文件(index.php
或者 app.php
),你们也都与他们天天碰面。可是你真的熟悉这些文件/文件夹吗?一个完整的项目是如何从一个纯净框架发展而来?各个部分又在框架这个大厦中又起到了怎么样的做用?php
依赖注入不是sql注入o( ̄ヘ ̄o#)
当提到依赖注入DI
的时候,90%的文章都会提到一个叫作 Reflection
的东西,致使不少的看官(也包括我)认为二自己就是一体的,但实际上双方是两个风马牛不相及的概念。Reflection
是反射类,而是一个依赖注入DI
是一种设计模式,反射类怎么会和设计模式属于同一种东西?因此他们的关系是依赖关系——既依赖注入是依赖于反射类而完成的设计模式。laravel
写一个很是简单的类A,这个类里面只有一个对象$a
,一个方法a()
。sql
class A { public $a; public function a() { echo __FUNCTION__; } }
以后咱们开始调用反射类Reflection
设计模式
$A = new A(); $reflect = new ReflectionObject($A); $props = $reflect->getProperties(); // 获取该类全部的对象 ...
咱们能够经过反射知道该类全部咱们想知道的内容,好像是x光同样获取一个类里面的全部细节。可是,当我完反射类以后,又陷入了迷茫。设计这种反射类究竟是为了什么?若是想知道原来类里面有多少方法/对象直接看原来的类不就能够了吗?为何要画蛇添足呢?这又和依赖注入有什么关系呢?
先不要着急,不妨让咱们先说说别的,而后回头来看看。app
如今有这样的一个场景,我想实例化B
,可是B
依赖于A
。这该怎么作?很简单以下就能够解决。框架
Class A{ // do sth. } Class B{ public function __construct(A $a){ // B类依赖于A } } $a = new A(); $b = new B($a);
很简单,对不对?两下就能够解决,这没有什么困难的。可是若是如今出现了这样一种状况:一共有A-Z
一共26个类,B
类依赖于A
,C
类依赖于B
,以此类推。那么我若是想实例化Z
类,你须要怎么作?难道还使用上图中的方法?那岂不是实例化Z
须要写26行?yii
那么有没有一种方法可以直接实例化B,而后就能够把其余的类所有自动加载进来?还真有。就须要用到上面刚刚讲过的反射类。函数
<?php /** * 工具类,使用该类来实现自动依赖注入。 */ class Ioc { // 得到类的对象实例 public static function getInstance($className) { $paramArr = self::getMethodParams($className); return (new ReflectionClass($className))->newInstanceArgs($paramArr); } /** * 得到类的方法参数,只得到有类型的参数。 * 经过递归方法 * @param [type] $className [类名] * @param [type] $methodsName [方法名] * @return [mixed] */ protected static function getMethodParams($className, $methodsName = '__construct') { // 经过反射得到该类 $class = new ReflectionClass($className); $paramArr = []; // 记录参数,和参数类型 // 判断该类是否有构造函数 if ($class->hasMethod($methodsName)) { // 得到构造函数 $construct = $class->getMethod($methodsName); // 判断构造函数是否有参数 $params = $construct->getParameters(); if (count($params) > 0) { // 判断参数类型 foreach ($params as $key => $param) { if ($paramClass = $param->getClass()) { // 得到参数类型名称 $paramClassName = $paramClass->getName(); /** * 得到参数类型 * 在这里使用递归 */ $args = self::getMethodParams($paramClassName); $paramArr[] = (new ReflectionClass($paramClass->getName()))->newInstanceArgs($args); } } } } return $paramArr; } /** * 执行类的方法 * @param [type] $className [类名] * @param [type] $methodName [方法名称] * @param [type] $params [额外的参数] * @return [type] [description] */ public static function make($className, $methodName, $params = []) { // 获取类的实例 $instance = self::getInstance($className); // 获取该方法所须要依赖注入的参数 $paramArr = self::getMethodParams($className, $methodName); return $instance->{$methodName}(...array_merge($paramArr, $params)); } } class A { // ... } class B { public function __construct(A $a) { // ... } } class C { public function __construct(B $b) { // ... } } $cObj = Ioc::getInstance('C');
让咱们来着重的看一下Ioc
这个类的getMethodParams
方法。工具
先说一下大致的状况:既该方法是使用递归对被加载的类进行遍从来达到将全部被依赖的类所有加载进来的目的。this
这个方法首先实例化Reflection
,而后使用getMethod
方法获取被加载进来的类的__construct
方法,而后看看这个__construct
中是否有其余的类做为参数,若是有,则递归重复
以上流程,直到没有依赖为止。
以上就是使用Reflection
进行依赖注入的一个简单例子。虽然在实际框架中会比上面的例子复杂一些,不过依然大同小异。
好比说,在不少的框架中,会使用一种叫作容器container
的概念--使用$this->getContainer()->get(YOUR_CLASS_NAME)
的方式获取类。这个容器是什么高科技的东西?不用担忧,说到底他仍是依赖注入。
咱们会在后面刨根问底,来看看symfony的container是怎么设计的。