tp5学习-容器

0x01 基础设计模式

参考大话PHP设计模式

单例模式

单例模式就是只有一个实例化存在。

  • 拥有一个构造函数,并且为private
  • 拥有一个静态成员变量用来保持类的实例
  • 拥有一个访问这个实例的静态方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php

class test_instance{
public static $is_instance = null;

private function __construct(){
echo "new a instance";
echo "<br />";
}

public static function isInstance(){
if (self::$is_instance===null)
{
self::$is_instance = new test_instance();
return self::$is_instance;
}
else
{
return self::$is_instance;
}
}
}

# 这里不能 new了, 要访问公共静态方法new实例进$is_instance
$a = test_instance::isInstance();
$b = test_instance::isInstance();
$c = test_instance::isInstance();

这是后只能访问一次构造函数,因此只有一个实例。这里的实例化方法isInstance和标量$is_instance都要是静态方法。(为了防止实例化,isInstance静态方法,这时候要用$is_instance也是没有被实例化的静态方法)。

工厂模式

如果一个类名进行了修改, 使用工厂模式不用把每一处的new都修改, 而是直接修改工厂方法即可。作为一个统一的调度。

1
2
3
4
5
6
7
8
9
10
<?php
class Factory{
static function createDatabase(){
$db = new Database();
return $db;
}
}

# $db = new Database();
$db = createDatabase();

引入了工厂模式,我们不需要在业务层去修改代码了。这样有利于整个软件框架稳定,比如在model层调用不同的Driver实例化。使用工厂模式只要在工厂中选择想使用的数据库Driver就可以实现切换。

思想就是业务层不用new具体的类了,而是调用统一接口。由接口new并返回。工厂模式是许多高级模式里惯用的模式,如注册树模式。

注册树模式

  • 注册树模式通过将对象实例注册到一棵全局的对象树上, 需要的时候从对象树上采摘下来使用。

    一个软件的底层有许多不同的类库, 在业务层, 我们要调用这些类库的使用。 这些底层类很乱不便于管理,这里就使用注册树模式,便于管理。(类似于配置函数生成的配置静态变量,这种操作应用于类上)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    class regTree{
    //注册树池子
    protected static $objects = null;

    //挂载
    public static function _set($key, $object){
    self::$objects[$key] = $object;
    }

    //获取
    public static function _get($key,){
    if (!isset(self::$objects[$key]))
    {
    self::$objects[$keys] = new $key;
    }

    return self::$objects[$key];
    }

    //注销
    public static function _unset($key){
    unset(self::$objects[$key]);
    }
    }

    此时我们需要使用底层函数的时候就不需要去new了,而是像读配置一样,去注册树这个配置数组中去读。

    set:

    $a =new target();

    regTree::_set('target', $a);

    get:

    $target = regTree::_get('target');

简单的思想,大概就是这样了。。。

0x02 容器

TP5的容器类位于think/thinkphp/librarythink/Container.php

1
2
3
4
5
6
7
8
9
10
11
use ArrayAccess;
use ArrayIterator;
use Closure;
use Countable;
use InvalidArgumentException;
use IteratorAggregate;
use ReflectionClass;
use ReflectionException;
use ReflectionFunction;
use ReflectionMethod;
use think\exception\ClassNotFoundException;

这里可以看出使用了反射机制。还使用了一堆PHP的内置接口。

看类声明:

1
class Container implements ArrayAccess, IteratorAggregate, Countable
  • ArrayAccess: 像数组一样使用对象
  • IteratorAggregat: 聚合式的迭代器
  • Countable: 用来统计对象中某个元素的个数

往下:

1
2
3
4
5
6
7
8
9
10
/**
* 容器对象实例
* @var Container
*/
protected static $instance;
/**
* 容器中的对象实例
* @var array
*/
protected $instances = [];

一看就像个单例模式,注册树的套路。

往下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 容器绑定标识
* @var array
*/
protected $bind = [
'app' => App::class,
'build' => Build::class,
'cache' => Cache::class,
'config' => Config::class,
'cookie' => Cookie::class,
'debug' => Debug::class,
'env' => Env::class,
'hook' => Hook::class,
'lang' => Lang::class,
'log' => Log::class,
'middleware' => Middleware::class,
'request' => Request::class,
'response' => Response::class,
'route' => Route::class,
'session' => Session::class,
'template' => Template::class,
'url' => Url::class,
'validate' => Validate::class,
'view' => View::class,
'rule_name' => route\RuleName::class,
// 接口依赖注入
'think\LoggerInterface' => Log::class,
];

一个映射关系, 为容器标识别名。

1
2
3
4
5
6
/**
* 容器标识别名
* @var array
*/
protected $name = [];

看入口文件:

1
2
Container::get('app')->path(__DIR__ . '/application/')->initialize();

这个地方就是静态调用Container里的get方法:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 获取容器中的对象实例
* @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);
}

看第一个参数, 传入以后,调用单例模式得到一个容器对象, 然后调用make方法。重点环节:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* 创建类的实例
* @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];
}
#!标示里有没有$abstract
if (isset($this->bind[$abstract])) {
$concrete = $this->bind[$abstract];
#! 判断是否是闭包方法
if ($concrete instanceof Closure) {
#! 这里通过反射机制执行函数
$object = $this->invokeFunction($concrete, $vars);
} else {
#! 再次调用`make`方法,传入的$concrete是对象的具体位置了。
$this->name[$abstract] = $concrete;
return $this->make($concrete, $vars, $newInstance);
}
} else {
#! 核心, 反射调用实例
$object = $this->invokeClass($abstract, $vars);
}

if (!$newInstance) {
#!挂载注册树
$this->instances[$abstract] = $object;
}

return $object;
}

看类反射的具体逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 调用反射执行类的实例化 支持依赖注入
* @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');
#!'__make'方法是public且static
if ($method->isPublic() && $method->isStatic()) {
#! 绑定参数, 实例化
$args = $this->bindParams($method, $vars);
return $method->invokeArgs(null, $args);
}
}

#! 类中是不是有构造函数,$vars就是传入容器的第二个参数(参数数组)
$constructor = $reflect->getConstructor();

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

return $reflect->newInstanceArgs($args);

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