前言
对于php
的框架,无论是yii
,symfony
或者是laravel
,大家都在工作中有涉猎。对于在框架中的存放着资源包vendor
文件夹,入口文件(index.php
或者 app.php
),大家也都与他们每天碰面。但是你真的熟悉这些文件/文件夹吗?一个完整的项目是如何从一个纯净框架发展而来?各个部分又在框架这个大厦中又起到了怎么样的作用?
一、依赖注入
依赖注入不是sql注入o( ̄ヘ ̄o#)
1.1 说一说依赖注入和反射类的关系
当提到依赖注入DI
的时候,90%的文章都会提到一个叫做 Reflection
的东西,导致很多的看官(也包括我)认为二本身就是一体的,但实际上双方是两个风马牛不相及的概念。Reflection
是反射类,而是一个依赖注入DI
是一种设计模式,反射类怎么会和设计模式属于同一种东西?所以他们的关系是依赖关系——既依赖注入是依赖于反射类而完成的设计模式。
1.2 什么是Reflection
写一个非常简单的类A,这个类里面只有一个对象$a
,一个方法a()
。
class A
{
public $a;
public function a()
{
echo __FUNCTION__;
}
}
之后我们开始调用反射类Reflection
$A = new A();
$reflect = new ReflectionObject($A);
$props = $reflect->getProperties(); // 获取该类所有的对象
...
我们可以通过反射知道该类所有我们想知道的内容,好像是x光一样获取一个类里面的所有细节。但是,当我完反射类之后,又陷入了迷茫。设计这种反射类到底是为了什么?如果想知道原来类里面有多少方法/对象直接看原来的类不就可以了吗?为什么要多此一举呢?这又和依赖注入有什么关系呢?
先不要着急,不妨让我们先说说别的,然后回头来看看。
1.3 都是依赖怎么办
现在有这样的一个场景,我想实例化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行?
那么有没有一种方法能够直接实例化B,然后就可以把其他的类全部自动加载进来?还真有。就需要用到上面刚刚讲过的反射类。
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
方法。
先说一下大体的情况:既该方法是使用递归对被加载的类进行遍历来达到将所有被依赖的类全部加载进来的目的。
这个方法首先实例化Reflection
,然后使用getMethod
方法获取被加载进来的类的__construct
方法,然后看看这个__construct
中是否有其他的类作为参数,如果有,则递归重复
以上流程,直到没有依赖为止。
以上就是使用Reflection
进行依赖注入的一个简单例子。虽然在实际框架中会比上面的例子复杂一些,不过依然大同小异。
比如说,在很多的框架中,会使用一种叫做容器container
的概念--使用$this->getContainer()->get(YOUR_CLASS_NAME)
的方式获取类。这个容器是什么高科技的东西?不用担心,说到底他还是依赖注入。
我们会在后面刨根问底,来看看symfony的container是怎么设计的。