第2章 解析框架 IOC 与 DI 实现原理

2.1 理解PHP反射

1. 什么是反射 ?

反射是指程序可以访问、检测和修改它本身状态或行为的一种能力。

反射可以做什么 :

  • 获取类型的相关信息
  • 动态调用方法
  • 动态构造对象
  • 从程序集中获得类型

2. 怎么实现反射?

可通过反射类 ( Reflection ) 或者 内省 ( Introspection ) 来完成对自己本身的状态检查和修改。

那反射和内省有啥区别,前者是 PHP 官方提供的一个反射类,实例化反射类就可做到对自身状态的修改,后者则是根据 PHP 提供的相关操作方法来完成本身的状态修改。

Introspection 具有操作方法如下 :

class_exists()
method_exists()
property_exists() //检查对象或类是否具有该属性
trait_exists() //检查指定的 trait 是否存在
class_alias() //为一个类创建别名
get_class()
get_parent_class() //返回对象或类的父类名
get_called_class() //获取静态方法调用的类名。
get_class_methods()//取得class name 类的所有的方法名,并且组成一个数组
get_class_vars() // 返回由类的默认属性组成的数组
get_object_vars() //返回一个数组。获取$object对象中的属性,组成一个数组
is_subclass_of()  // 判断一个对象是否为一个类的子类
is_a() //某对象是否属于该类 或 该类是此对象的父类

案例测试代码如下 :

<?php
/**
   * @param class_exists();  类是否存在
   * @param get_class();  返回对象的类名
   * @param get_parent_class();返回对象的类的父类名
   * @param is_subclass_of();检查一个对象是否是父类定义的子类
   * 
   */
class Introspection
{
   
    public function description() {
   
       echo "我是纳西妲";
    }
}class Winner extends Introspection
{
   
    public function description() {
   
       echo "我自己" . get_class($this) , "<br>";
       echo "parent是谁:" . get_parent_class($this) , "<br>";
    }
}if (class_exists("Introspection")) {
   
       $introspection = new Introspection();
       echo "类名是: " . get_class($introspection) . "是父类"."<br>"; 
       $introspection->description();
}if (class_exists("Nahida")) {
   
       $nahida = new Nahida();
       $nahida->description();if (is_subclass_of($nahida, "Introspection")) {
   
       echo "Yes, " . get_class($nahida) . "是Introspection的子类";
    }
    else {
   
       echo "No, " . get_class($nahida) . " 不是Introspection的子类";
    }
}

Reflector具有参数如下

类型 说明
Reflector Reflector 是一个接口,被所有可导出的反射类所实现(implement)
Reflection 反射(reflection)类
ReflectionClass 报告了一个类的有关信息
ReflectionZendExtension 报告 Zend 扩展的相关信息
ReflectionExtension 报告了 PHP 扩展的有关信息
ReflectionFunction 报告了一个函数的有关信息
ReflectionFunctionAbstract ReflectionFunction 的父类
ReflectionMethod 报告了一个方法的有关信息
ReflectionObject 报告了一个对象(object)的相关信息
ReflectionParameter 取回了函数或方法参数的相关信息
ReflectionProperty 报告了类的属性的相关信息
<?php
namespace Six;

class Nahida{
   
	public $name;
	private $url;
	const TITLE = '纳西妲';
	public function __construct($name, $url){
   
    	$this->name = $name;
    	$this->url = $url;
	}
	public function getName(){
   
    	return $this->name;
	}
    /**
     * 获取连接
     * @return [type] [description]
     */
    private function getUrl(){
   
    	return $this->url;
	}
}
?>
-------------------------分割线------------------------------
<?php
$class  = new \ReflectionClass('Nahida');  // 以类名 Nahida 作为参数,即创建 Nahida 类的反射类
$properties = $class->getProperties(); // 以数组的形式返回 Nahida 类的所有属性
$property   = $class->getProperty('name');  // 获取 Nahida 类的 name 属性
$methods    = $class->getMethods();  // 以数组的形式返回 Nahida 类的所有方法
$method     = $class->getMethod('getName'); // 获取 Nahida 类的 getName 方法
$constants  = $class->getConstants(); // 以数组的形式获取所有常量
$constant   = $class->getConstant('TITLE'); // 获取 TITLE 常量
$namespace      = $class->getNamespaceName();   // 获取类的命名空间
$comment_class  = $class->getDocComment();      // 获取 Nahida 类的注释文档,即定义在类之前的注释
$comment_method = $class->getMethod('getUrl')->getDocComment();  // 获取 Website 类中 getUrl 方法的注释文档
?>  

2.2 理解 IOC

IOC 是Inversion of Control 的缩写, 即 “控制反转",它是一种设计思想。 IOC 意味着将类实例化的工作交给容器来完成,而不是传统的在你的对象内部直接进行实例化。然后在调用对象的方法。

如何理解 IOC 呢 ? 首先需要明确谁控制谁,控制什么,什么是反转(有反转就应该有正转了),在哪些反转了 ?

  • **谁控制谁,控制什么:**传统 PHP 程序开发中,我们是直接在对象内部通过 new 进行创建对象,是程序主动去创建依赖对象;这就是正转。而 IOC 是有专门的一个容器来创建这些对象,即由 IOC 容器来控制对 象的创建。

    那谁控制谁: 就是 IOC 容器控制了对象。

    **控制了什么:**容器主要控制了外部资源获取(不只是对象,包括比如文件等)。

  • **为何是反转,哪些方面反转了:**反转是由容器到应用程序中创建及注入依赖对象。

    **什么是反转:**因为由容器帮我们查找及注入依赖对象,应用程序只是被动的接受依赖对象,所以是反转。

    **哪里反转了:**依赖对象的获取被反转了。

在这里插入图片描述

2.3 理解 DI

DI ( Dependency Injection) 即 “依赖注入” :组件之间的依赖关系由容器动态的将某个类与类的依赖关系注入到组件之中。依赖注入的目的就是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,只需要通过简单的配置、注入方法接口,就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

怎么理解 DI ?

“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”

● 谁依赖于谁:应用程序(框架开发的业务对象)依赖于 IOC 容器。

● 为什么需要依赖:应用程序需要 IOC 容器来提供对象需要的外部资源。

● 谁注入谁:IOC 容器注入了应用程序依赖的某个对象。

● 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

IOC 和 DI 有什么关系呢?

其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以 2004 年大师级人物 Martin Fowler 又给出了一个新的名字:“依赖注入”,相对 IOC 而言,“依赖注入”明确描述了“被注入对象依赖 IOC 容器配置依赖对象 ”。

2.4 从零实现 IOC

如何实现 IOC 容器 ?

按照上面所诉,需要一个容器来存放依赖对象,同时需要一个依赖注入的方法,好让外面的内容可注入到对象中。同时容器自身需要提供获取依赖对象的方法。

<?php
/**
 * 单身同胞 和 你的对象御女&文雅
 */
class 单身同胞
{
   
    private $r;
    // 找女朋友 / 男朋友
    // 标准 -> 我喜欢的类型
    public function __construct(女和男 $御女和文雅)
    {
   
        $this->r = $御女和文雅;
    }
    // 谈恋爱
    public function 一起吃饭()
    {
   
		return '和'.$this->r->name.'一起吃饭';
    }
    public function 一起看电影()
    {
   
		return '和'.$this->r->name.'一起看电影';
    }
    public function f一起去玩()
    {
   
        return '和'.$this->r->name.'一起去玩';
    }
}
------------------------------------------------------------
class 抠脚大汉
{
   
   public $name = '抠脚大汉';
}
$抠脚大汉 = new 抠脚大汉();

interface 女和男 {
   
}

class 御女和文雅 implements 女和男
{
   
    public $name = '御女和文雅';
}

class 萝莉和文雅 implements 女和男
{
   
    public $name = '萝莉和文雅';
}

class 女神和文雅 implements 女和男
{
   
    public $name = '女神和文雅';
}

$御女和文雅 = new 御女和文雅();

$单身 = new 单身同胞($御女和文雅);

echo $单身->f一起去玩();

// ioc容器 为什么需要呢?
/**
 * 相亲平台 : 记录各种用户的信息
 */
class 相亲平台
{
      // $bindings
    private $信息 = [];
    // $resolved
    private $相亲成功 = [];
    // $instances
    private $共享对象 = [];
    // $aliases / $abstractAliases
    private $网名 = [];

    public function 登记注入($类型, $详细信息)
    {
   
		$this->信息[$类型] = $详细信息;
    }
    public function 网名登记注入($类型, $详细信息)
    {
   
		$this->信息[$类型] = $详细信息;
    }
    public function 查找($类型)
    {
   
        return ($this->信息[$类型])();// 因为闭包所以让其执行
    }
}
$app = new 相亲平台();

$app->登记注入('御女和文雅' , function(){
   
    return new 御女和文雅();
});
$app->登记注入('萝莉和文雅' , function(){
   
    return new 萝莉和文雅();
});

$app->登记注入('单身同胞' , function(萝莉和文雅 $r) use ($app){
   
    return new 单身同胞($app->查找('萝莉和文雅'));
});

echo $app->查找('单身同胞')->f一起去玩();

2.5 探索框架底层 IOC 与 DI 实现

因在第一章分析到框架的整体流程,IOC 和 DI 的相关特性已经在源码中,这里就带大家看到重要的环节就可以了。从容器对象、解析对象、依赖注入对象。

class Container implements ArrayAccess, ContainerContract
{
   
    /**
     * 全局可用容器,即把相关对象都注入到容器中
     * @var static
     */
    protected static $instance;
    //注入框架自带的核心类库与对应别名,想当于是依赖注入。即容器注入应用程序依赖的对象
    public function registerCoreContainerAliases()
    {
   
		stream_context_set_option();
		foreach ([
    		'app'  => [
                self::class, 
                \Illuminate\Contracts\Container\Container::class, 
                \Illuminate\Contracts\Foundation\Application::class, 
                \Psr\Container\ContainerInterface::class
            ],
    		'auth' => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
    		'session'      => [\Illuminate\Session\SessionManager::class],
    		'session.store'=> [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class],
    		'url'  => [\Illuminate\Routing\UrlGenerator::class, \Illuminate\Contracts\Routing\UrlGenerator::class],
    		'validator'    => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class],
    		'view' => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
		] as $key => $aliases) {
   
    		foreach ($aliases as $alias) {
   
				$this->alias($key, $alias);
    		}
		}
    }
    //绑定相关类库到容器中,即注入相关外部资源类库,不单单是框架自带的常用组件类。这里会绑定一个闭包函数
    public function bind($abstract, $concrete = null, $shared = false)
    {
   
		$this->dropStaleInstances($abstract);

		if (is_null($concrete)) {
   
    		$concrete = $abstract;
		}

		if (! $concrete instanceof Closure) {
   
    		if (! is_string($concrete)) {
   
				throw new \TypeError(self::class.'::bind(): Argument #2 ($concrete) must be of type Closure|string|null');
    		}

    		$concrete = $this->getClosure($abstract, $concrete);
		}

		$this->bindings[$abstract] = compact('concrete', 'shared');

		if ($this->resolved($abstract)) {
   
    		$this->rebound($abstract);
		}
    }
    
    //从容器中解析给定类型的资源,即获取容器中的对象
    public function make($abstract, array $parameters = [])
    {
   
		return $this->resolve($abstract, $parameters);
    }
    
    //先获取注入到容器中的类,然后通过反射机制进行类的实例化创建,最后返回对象到应用
    protected function resolve($abstract, $parameters = [], $raiseEvents = true)
    {
   
		$abstract = $this->getAlias($abstract);

		$concrete = $this->getContextualConcrete($abstract);

		$needsContextualBuild = ! empty($parameters) || ! is_null($concrete);
		if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
   
    		return $this->instances[$abstract];
		}

		$this->with[] = $parameters;

		if (is_null($concrete)) {
   
    		$concrete = $this->getConcrete($abstract);
		}

        if ($this->isBuildable($concrete, $abstract)) {
   
            $object = $this->build($concrete);
        } else {
   
            $object = $this->make($concrete);
        }

        foreach ($this->getExtenders($abstract) as $extender) {
   
            $object = $extender($object, $this);
        }

        if ($this->isShared($abstract) && ! $needsContextualBuild) {
   
            $this->instances[$abstract] = $object;
        }

        if ($raiseEvents) {
   
            $this->fireResolvingCallbacks($abstract, $object);
        }

        $this->resolved[$abstract] = true;

        array_pop($this->with);

        return $object;
    }
}

相关推荐

  1. Electron框架初识:原理实践优势深度解读

    2023-12-09 19:56:02       15 阅读
  2. ceph之rados设计原理实现:存储的基石OSD

    2023-12-09 19:56:02       39 阅读

最近更新

  1. TCP协议是安全的吗?

    2023-12-09 19:56:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-09 19:56:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-09 19:56:02       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-09 19:56:02       20 阅读

热门阅读

  1. LeetCode 每日一题 Day 7(dp动态规划)

    2023-12-09 19:56:02       40 阅读
  2. ES6 箭头函数

    2023-12-09 19:56:02       43 阅读
  3. React拖拽实践

    2023-12-09 19:56:02       45 阅读
  4. 【LeetCode】2620. 计数器

    2023-12-09 19:56:02       36 阅读
  5. 面试-算法

    2023-12-09 19:56:02       37 阅读
  6. 大一C语言作业 12.8

    2023-12-09 19:56:02       31 阅读
  7. 如何配置git的ssh密钥

    2023-12-09 19:56:02       37 阅读