TP5.1 框架解析(三):IOC容器及Facade 深度解析

一、预习知识

预习知识中代码均未基础演示(主要为思想演示),若想用于项目,还需要自行丰富额。

  • 单例模式
  • 注册树模式

1.1 单例模式(三私一公)

解决的问题:如何在整个项目中去创建一个唯一的对象实例?

<?php
class Single
{
    private static $instance = null;

    private function __construct()
    {
        echo 'Single - __construct';
    }

    // 获取实例
    public function getInstance()
    {
        if (!(self::$instance instanceof Single)) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    // 防止克隆
    private function __clone(){}
}

调用

$obj = Single::getInstance()

1.2 注册树模式

<?php

/**
 * 注册树模式
 * Class BenRegister
 */
class BenRegister
{
    // 注册树池
    protected static $objects = null;

    /**
     * 将对象挂载到 注册树上
     * @param $key
     * @param $object
     */
    public static function set($key, $object)
    {
        self::$objects[$key] = $object;
    }

    /**
     * 从树上获取对象,如果没有的时候-注册
     * @param $key
     * @return mixed
     */
    public static function get($key)
    {
        if (!isset(self::$objects[$key])) {
            self::$objects[$key] = new $key;
        }
        return self::$objects[$key];
    }

    /**
     * 从注册树上注销
     * @param $key
     */
    public static function _unset($key)
    {
        unset(self::$objects[$key]);
    }
}

调用

$a = new A();
// 注册
BenRegister::set('a', $a);
// 调用
$obj = BenRegister::get('a');

1.3 反射(实例1)

准备一个类:tp/extend/A.php

<?php

/**
 * Class A
 */
class A
{
    /**
     * 共有属性 $ab
     * @var int
     */
    public $ab = 1;

    /**
     * 私有属性 $bc
     * @var int
     */
    private $bc = 2;

    /**
     * 静态属性 $instance
     * @var null
     */
    public static $instance = null;

    /**
     * A类下的abc方法
     * @param string $a
     * @param string $b
     * @return string
     */
    public function abc(string $a, string $b)
    {
        return 'A类下的abc方法:$a = ' . $a . '、$b=' . $b;
    }

    /**
     * Abc类下的mf方法
     * @param int $type
     * @return string
     */
    public function mf($type = 1)
    {
        return 'A类下的type方法';
    }
}

反射测试:tp/application/index/controller/TestController.php

<?php

namespace app\index\controller;

class TestController
{
    // 反射测试
    public function index()
    {
        $hr = '<hr>';
        $br = '<br>';

        $obj = new \A();
        // dump($obj);

        $obj2 = new \ReflectionClass($obj);
        $instance  =  $obj2->newInstance();  // 相当于实例化类(无参数)
        $obj2->newInstanceArgs($args)   // 相当于实例化类(有参数)

        echo '获取所有方法';
        $methods = $obj2->getMethods();         // 获取所有方法
        dump($methods);
        echo $hr;

        echo '获取 `方法` 的注释信息';
        foreach ($methods as $method) {
            dump($method->getDocComment());     // 获取 方法 的注释信息
        }
        echo $hr;

        echo '获取 `类` 的注释信息';
        $classDocComment = $obj2->getDocComment();   // 获取 类 的注释信息
        dump($classDocComment);
        echo $hr;

        echo '获取所有属性';
        $properties = $obj2->getProperties();
        var_dump($properties);
        echo $hr;

        echo '获取 `属性` 的注释信息';
        foreach ($properties as $property) {
            dump($property->getDocComment());   // 获取 属性 的注释信息
        }
        echo $hr;

        echo '方法一:调用反射类里面的方法' . $br;
        $instance = $obj2->newInstance();    // 相当于实例化类
        echo $instance->abc('abc', 'fff');
        echo $hr;

        echo '方法二:调用反射类里面的方法' . $br;
        $instance = $obj2->newInstance();    // 相当于实例化类
        $method = $obj2->getMethod('abc');
        $res = $method->invokeArgs($instance, ['abcdefg', 'aaaaa']);
        echo $res;  // 打印调用结果
        echo $hr;

        echo '方法三:调用反射类里面的方法' . $br;
        $instance = $obj2->newInstance();    // 相当于实例化类
        $method = $obj2->getMethod('mf');
        // 只能调用 `无参数` 或 参数提供默认值的方法
        echo $method->invoke($instance);
        echo $hr;

        echo '判断某个方法是否是公有' . $br;
        $method = new \ReflectionMethod($obj, 'abc');
        if ($method->isPublic()) {
            echo 'abc 方法是公共的';
        } else {
            echo 'abc 方法不是公共的';
        }
        echo $hr;

        echo '获取某个方法的参数';
        $parameters = $method->getParameters();
        dump($parameters);
        echo $hr;

        echo '获取某个方法的参数的个数';
        $NumberOfParameters = $method->getNumberOfParameters();
        dump($NumberOfParameters);
    }
}

1.3 反射(实例2)

index.php 直接运行即可。

<?php
ini_set('display_errors',1);

// ReflectionClass
// ReflectionFunction
// ReflectionMethod
// ReflectionParameter

define("BR", "<br />");

class People
{

}

class Student
{
    private $name;
    private $year;

    public function __construct(string $name, int $year)
    {
        $this->name = $name;
        $this->year = $year;
    }

    public function getName()
    {
        echo __METHOD__ . BR;
        return $this->name;
    }

    public function seeBase(string $name, int $year)
    {
        $this->name = $name;
        $this->year = $year;
    }

    public function people(People $people, $year = 10)
    {

    }
}

function display($a, $b, People $people)
{
    echo "called" . BR;
}

$reflectionClass = new ReflectionClass(Student::class);
$obj = $reflectionClass->newInstanceArgs(["张三", 50]);   // 创建实例
echo $obj->getName() . BR;   // 调用实例类方法
echo BR . BR;

$reflectionMethod = $reflectionClass->getMethod("seeBase");
$parameters = $reflectionMethod->getParameters();   // 获取所有参数
print_r($reflectionMethod);echo BR;
foreach ($parameters as $key => $parameter) {
    echo "参数{$key}:{$parameter->getName()}" . BR;
}
echo BR . BR;

$reflectionMethod = $reflectionClass->getMethod("people");
print_r($reflectionMethod);echo BR;
$parameters = $reflectionMethod->getParameters();   // 获取所有参数
foreach ($parameters as $key => $parameter) {
    echo "参数{$key}:{$parameter->getName()}" . BR;

    // 这里就是依赖注入的核心了。
    if ($parameter->getClass() !== null) {
        echo "参数{$key}:对应的类名 = {$parameter->getClass()->getName()}" . BR;
    }

    // 检查参数是否有默认值
    if ($parameter->isDefaultValueAvailable()) {
        echo "参数{$key}:对应的默认值 = {$parameter->getDefaultValue()}" . BR;
    }
}
echo BR . BR;

$reflectionFunction = new ReflectionFunction("display");
print_r($reflectionFunction);echo BR;
$parameters = $reflectionFunction->getParameters();
foreach ($parameters as $key => $parameter) {
    echo "参数{$key}:{$parameter->getName()}" . BR;

    // 这里就是依赖注入的核心了。
    if ($parameter->getClass() !== null) {
        echo "参数{$key}:对应的类名 = {$parameter->getClass()->getName()}" . BR;
    }

    // 检查参数是否有默认值
    if ($parameter->isDefaultValueAvailable()) {
        echo "参数{$key}:对应的默认值 = {$parameter->getDefaultValue()}" . BR;
    }
}

1.4 玩转容器思想

1.4.1 tp/extend/di/Car.php

<?php

namespace di;

/**
 * 小汽车类
 * Class Car
 * @package di
 */
class Car
{
    /**
     * 购买小汽车需要支付
     * @return string
     */
    public function pay()
    {
        return '总价:$1234';
    }
}

1.4.2 tp/extend/di/Product.php

<?php

namespace di;

/**
 * 商品类
 * Class Product
 * @package di
 */
class Product
{
    public $obj = null;

    /**
     * 次数使用了依赖注入(通过反射获取到 Car 类的实例)
     * 依赖:Persion类 依赖于 Car类
     * 注入:Car类 注入到 Persion类
     * @return string
     */
    public function __construct(Car $obj, $a=0)
    {
        $this->obj = $obj;
        $this->a = $a;
    }

    /**
     * 购买商品
     * @return string
     */
    public function buy()
    {
        return $this->obj->pay() . '|' . $this->a;
    }
}

1.4.3 tp/extend/di/Container.php

<?php

namespace di;

/**
 * 玩转自己的容器类
 * Class Container
 * @package di
 */
class Container
{
    /**
     * 存放容器的数据 [注册树池]
     * @var array
     */
    public $instances = [];

    /**
     * 容器中的对象实例
     * @var
     */
    protected static $instance;

    private function __construct(){}

    /**
     * 获取当前容器的实例(单例)
     * @return mixed
     */
    public static function getInstance(): Container
    {
        if (!(static::$instance instanceof Container)) {
            static::$instance = new static();
        }
        return static::$instance;
    }

    /**
     * 注册到容器
     * @param $key
     * @param $value
     */
    public function set($key, $value)
    {
        $this->instances[$key] = $value;
    }

    /**
     * 从容器中取实例(使用反射机制)
     * @param string $key 可以传入通过 set 设置的 key,也可以是 真实的类名称
     * @return mixed
     * @throws \ReflectionException
     */
    public function get($key, $dd = false)
    {
        if (!empty($this->instances[$key])) {
            // 获取到真实对应的 类名称
            $key = $this->instances[$key];
        }

        $reflect = new \ReflectionClass($key);
        $c = $reflect->getConstructor();    // 反射:获取 __construct 方法
        // 如果不存在 __construct 方法:直接返回这个类的实例
        if (!$c) {
            return new $key();
        }

        $params = $c->getParameters();      // 反射:获取 __construct 方法的参数
        // 不需要参数
        if (empty($params)) {
            return new $key();
        }

        $args = [];
        foreach ($params as $param) {
            // 获取这个参数对应的类
            // function(Persion $obj){}     ## 可以获取到
            // function($obj){}             ## 返回 null
            $class = $param->getClass();
            if (is_null($class)) {
                // todo...
            } else {
                // 通过调用自身 获取参数的自动实例化
                // dump($class->name);
                $args[] = $this->get($class->name, true);
            }
        }

        // 返回实例化类
        return $reflect->newInstanceArgs($args);
    }
}

1.4.4 控制器调用

tp/application/index/controller/TestController.php

<?php

namespace app\index\controller;

use di\Container;

class TestController
{
    public function index()
    {
        $container = Container::getInstance();

        $container->set('product', 'di\Product');      // 注册(传类名 - 在里面用反射处理)
        $container->set('car', 'di\Car');              // 注册(传类名 - 在里面用反射处理)

        $product = $container->get('product');
        // $car = $container->get('car');

        dump($product->buy());
    }
}

1.4.5 反射知识点

file

二、IOC 容器

2.1 入口文件

tp/public/index.php

.
.
// 执行应用并响应\
Container::get('app')->run()->send();

我们主要分析 Container::get('app') 返回了什么?

2.2 容器类 Container

tp/thinkphp/library/think/Container.php

步骤一 function get()

static::getInstance() 单例模式,获取类的实例(不在分析)。我们来重点关注下 make() 方法。

/**
 * 获取容器中的对象实例
 * @access public
 * @param  string        $abstract       类名或者标识
 * @param  array|true    $vars           变量
 * @param  bool          $newInstance    是否每次创建新的实例
 * @return object
 */
public static function get($abstract, $vars = [], $newInstance = false)
{
    return static::getInstance()->make($abstract, $vars, $newInstance);
}

步骤二 function make()

/**
 * 创建类的实例
 * @access public
 * @param  string        $abstract       类名或者标识
 * @param  array|true    $vars           变量
 * @param  bool          $newInstance    是否每次创建新的实例
 * @return object
 */
public function make($abstract, $vars = [], $newInstance = false)
{
    if (true === $vars) {
        // 总是创建新的实例化对象
        $newInstance = true;
        $vars        = [];
    }

    $abstract = isset($this->name[$abstract]) ? $this->name[$abstract] : $abstract;

    // 注册树
    if (isset($this->instances[$abstract]) && !$newInstance) {
        return $this->instances[$abstract];
    }

    // 默认容器绑定标识
    if (isset($this->bind[$abstract])) {
        // 对应真实类名称,例:think\App
        $concrete = $this->bind[$abstract];

        // 闭包判断
        if ($concrete instanceof Closure) {
            // 执行函数或者闭包方法 支持参数调用
            $object = $this->invokeFunction($concrete, $vars);
        } else {
            // 添加到容器标识别名
            // $this->name['app'] = think\App
            $this->name[$abstract] = $concrete;
            // 这里 $concrete 已经是真实的类名了,例:think\App
            return $this->make($concrete, $vars, $newInstance);
        }
    } else {
        // 核心处理(调用反射执行类的实例化 支持依赖注入)
        $object = $this->invokeClass($abstract, $vars);
    }

    // 添加到注册树
    if (!$newInstance) {
        // $this->instances['think\App'] = $object
        $this->instances[$abstract] = $object;
    }

    return $object;
}

步骤三 function invokeClass()

注册容器:首先如果 __make 方法存在并满足要求 则使用此方法创建实例,否则默认。

/**
 * 调用反射执行类的实例化 支持依赖注入
 * @access public
 * @param  string    $class 类名
 * @param  array     $vars  参数
 * @return mixed
 */
public function invokeClass($class, $vars = [])
{
    try {
        $reflect = new ReflectionClass($class);

        // 是否存在 __make 方法
        if ($reflect->hasMethod('__make')) {
            $method = new ReflectionMethod($class, '__make');
            // 必须是 public + static
            if ($method->isPublic() && $method->isStatic()) {
                $args = $this->bindParams($method, $vars);
                // 通过 __make 方法进行实例化
                return $method->invokeArgs(null, $args);
            }
        }

        // 获取 __construct 方法
        $constructor = $reflect->getConstructor();

        $args = $constructor ? $this->bindParams($constructor, $vars) : [];

        // 实例化
        return $reflect->newInstanceArgs($args);

    } catch (ReflectionException $e) {
        throw new ClassNotFoundException('class not exists: ' . $class, $class);
    }
}

步骤四

file

步骤五

大体上,设计思想与我们预习中所用的思想差不多。主要使用了 单例模式注册树模式依赖注入反射机制等特性完成。

使用很简单:tp/application/index/controller/TestController.php

<?php

namespace app\index\controller;

use think\Container;

class TestController
{
    public function index()
    {
        // 场景一
        dump(Container::get('config')->get('app.'));

        // 场景二(利用助手函数 = tp/thinkphp/helper.php 中定义)
        dump(app('config')->get('app.'));
    }
}

三、Facade 门面模式

  • 门面为容器中的类提供了一个静态调用接口
  • 相比与传统的静态方法调用,带来了更好的可测性和扩展性

3.1 Facade 的使用

tp/application/index/controller/TestController.php

<?php

namespace app\index\controller;

class TestController
{
    public function index()
    {
        // 使用类库别名
        dump(\Config::get('app.'));

        // 使用 Facade 门面
        dump(\think\facade\Config::get('app.'));
    }
}

类库别名是在 tp/thinkphp/base.php 文件中通过 Loader::addClassAlias(array $alias); 定义的 注册类库别名。与我们的门面模式没有关系,了解就可以了。

3.2 步骤一

跟踪 dump(\think\facade\Config::get('app.')); 使用的 ::get() 方法。

文件:tp/thinkphp/library/think/facade/Config.php

file

发现并没有 get 方法,我们继续找它的父类 Facade

3.3. 步骤二

文件:tp/thinkphp/library/think/Facade.php

file

同样也没有 get 方法。但是我们却找到了 __callStatic 魔术方法。

3.4 步骤三

文件:tp/thinkphp/library/think/Facade.php

.
.
// 调用实际类的方法
public static function __callStatic($method, $params)
{
    // static::createFacade() 返回一个实例
    // [static::createFacade(), $method] 实例下的方法
    return call_user_func_array([static::createFacade(), $method], $params);
}
.
.

3.5 步骤四

/**
* 创建Facade实例
* @static
* @access protected
* @param string    $class 类名或标识
* @param array     $args 变量
* @param bool      $newInstance 是否每次创建新的实例
* @return object
*/
protected static function createFacade($class = '', $args = [], $newInstance = false)
{
    $class = $class ?: static::class;

    // 可以在子类中返回真正的 class 名
    $facadeClass = static::getFacadeClass();

    if ($facadeClass) {
        $class = $facadeClass;
    } elseif (isset(self::$bind[$class])) {
        // 如果在业务层创建自定义 Facade 类的时候,就需要用到绑定。
        // 获取绑定对象属性
        $class = self::$bind[$class];
    }

    // 是否创建新的对象实例
    if (static::$alwaysNewInstance) {
        $newInstance = true;
    }

    // 使用容器,创建类的实例。
    return Container::getInstance()->make($class, $args, $newInstance);
}

最后可以看到:Container::getInstance()->make($class, $args, $newInstance); 使用 容器 去创建了对应的实例

四、Facade 应用

4.1 场景一

Guanjie.php

tp/application/common/Guanjie.php

<?php

namespace app\common;

class Guanjie
{
    public function abc()
    {
        return 'common - abc()';
    }
}

facade/Guanjie.php

tp/application/common/facade/Guanjie.php

<?php

namespace app\common\facade;

use think\Facade;

/**
 * @see \app\common\Guanjie
 * @mixin \app\common\Guanjie
 * @method string abc() static 测试一把
 */
class Guanjie extends Facade
{
    /**
     * 获取当前Facade对应类名(或者已经绑定的容器对象标识)
     * @access protected
     * @return string
     */
    protected static function getFacadeClass()
    {
        return '\app\common\Guanjie';
    }
}

TestController.php

tp/application/index/controller/TestController.php

<?php

namespace app\index\controller;

class TestController
{
    /**
     * 传统调用方法
     * @return string
     */
    public function index()
    {
        // 实际的 Guanjie 类调用
        $obj = new \app\common\Guanjie();
        return $obj->abc();
    }

    /**
     * 场景一:扩展门面模式()
     * @return string
     */
    public function index2()
    {
        echo __METHOD__ . '<br>';

        return \app\common\facade\Guanjie::abc();
    }
}

4.1 场景二

Sing.php

tp/application/common/Sing.php

<?php

namespace app\common;

class Sing
{
    public function abc()
    {
        return 'Sing.abc';
    }
}

facade/Sing.php

tp/application/common/facade/Sing.php

<?php

namespace app\common\facade;

use think\Facade;

/**
 * @see \app\common\Guanjie
 * @mixin \app\common\Guanjie
 * @method string abc() static 再测试一把
 */
class Sing extends Facade
{
    // 我这里不再重写 getFacadeClass 方法
}

TestController.php

tp/application/index/controller/TestController.php

<?php

namespace app\index\controller;

class TestController
{
    /**
     * 传统调用方法
     * @return string
     */
    public function index()
    {
        // 实际的 Guanjie 类调用
        $obj = new \app\common\Guanjie();
        return $obj->abc();
    }

    /**
     * 场景二:扩展门面模式()
     * @return string
     */
    public function index3()
    {
        echo __METHOD__ . '<br>';

        // 绑定到门面
        \think\Facade::bind('app\common\facade\Sing', '\app\common\Sing');
        return \app\common\facade\Sing::abc();
    }
}
讨论数量: 0

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!