thinkphp5源码剖析系列3-容器与门面
容器和门面是tp框架的精髓所在
涉及容器与门面之前我们先链接几个简单的设计模式
- 单例模式
<?php
namespace sjms;
// 单例模式
class Single{
private static $me = null;
// 防止对象被new
private function __construct(){
}
public static function getInstance(){
self::$me || self::$me = new self;
return self::$me;
}
}
- 注册树模式
<?php
namespace sjms;
// 注册树模式
class Register{
// 注册树池子
private static $objects = [];
// 注册对象
public static function set($key, $object){
self::$objects[$key] = $object;
}
// 从注册树获取对象,如果没有注册对象
public static function get($key){
if(!isset(self::$objects[$key])){
self::$objects[$key] = new $key;
}
return self::$objects[$key];
}
// 注销注册树对象
public static function _unset($key){
if(isset(self::$objects[$key]))
unset(self::$objects[$key]);
}
}
- 依赖注入
<?php
namespace sjms\di;
class Person{
/*
* 依赖注入
* 依赖:Person类Car类
* 注入:将Car类Person类
*/
public function buy($obj){
// $bmw = new Car();
$price = $obj->pay();
return $price;
}
}
- 反射机制
public function index()
{
// 实例化对象
$obj1 = new Car();
// 对象的反射类
$obj2 = new \ReflectionClass($obj1);
// 相当于实例化这个类
$instance = $obj2->newInstance();
// 获取这个类的所有方法[返回对象数组集
$methods = $obj2->getMethods();
// 执行某个方法
echo $instance->pay();
// 获取单个方法[返回对象
$method = $obj2->getMethod('pay');
// 判断某个方法是否是公用的[返回对象
$method = new \ReflectionMethod($instance, 'pay');
$isPublic = $method->isPublic();
var_export("is public : {$isPublic}");
// 获取方法的所有形参[返回对象数组集
var_dump($method->getParameters());
}
- 构造自己的容器类
<?php
namespace sjms\di;
class Car{
/*
* 依赖注入
*/
public function pay($name){
echo $name . ' buy car';
}
}
<?php
namespace sjms\di;
class Person{
public function __construct(Car $c,$name)
{
$this->name = $name;
$this->obj = $c;
}
/*
* 依赖注入
* 依赖:Person类Car类
* 注入:将Car类Person类
*/
public function buy(){
$this->obj->pay($this->name);
}
}
<?php
namespace sjms;
// 自己的容器类
class Continer{
private $instances = [];
private static $instance = NULL;
public static function getInstance(){
if(is_null(static::$instance)){
static::$instance = new static;
}
return static::$instance;
}
public function set($key, $value){
$this->instances[$key] = $value;
}
// 获取容器里面的实例,会用到反射机制
public function get($key,$data = []){
if(!empty($this->instances[$key])){
$key = $this->instances[$key];
}
// 反射类
$reflect = new \ReflectionClass($key);
// 获取类的构造函数
$c = $reflect->getConstructor();
if(!$c){
return new $key;
}
// 获取类的构造函数所有的参数
$params = $c->getParameters();
if(empty($params)){
return new $key;
}
// 如果构造函数有参
foreach($params as $param){
$class = $param->getClass();
if(!$class){
// 如果不是类参数
$paramName = $param->name;
// 自动传参
if(isset($data[$paramName])){
$args[] = $data[$paramName];
}
// todo
}else{
// 递归[依赖注入的思维]
$args[] = $this->get($class->name);
}
}
return $reflect->newInstanceArgs($args);
}
}
public function index()
{
$c = Continer::getInstance();
$c->get('\sjms\di\Person',['name' => 'tom'])->buy();
}
- tp5中的容器类,Container.php
/**
* 获取容器中的对象实例
* @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);
}
/**
* 创建类的实例
* @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])) {
$concrete = $this->bind[$abstract];
if ($concrete instanceof Closure) {
$object = $this->invokeFunction($concrete, $vars);
} else {
// 绑定别名
$this->name[$abstract] = $concrete;
return $this->make($concrete, $vars, $newInstance);
}
} else {
$object = $this->invokeClass($abstract, $vars);
}
// 挂载注册树
if (!$newInstance) {
$this->instances[$abstract] = $object;
}
return $object;
}
/**
* 调用反射执行类的实例化 支持依赖注入
* @access public
* @param string $class 类名
* @param array $vars 参数
* @return mixed
*/
public function invokeClass($class, $vars = [])
{
try {
$reflect = new ReflectionClass($class);
// 是否有构造类的__make方法,如果有则直接make
if ($reflect->hasMethod('__make')) {
$method = new ReflectionMethod($class, '__make');
if ($method->isPublic() && $method->isStatic()) {
$args = $this->bindParams($method, $vars);
return $method->invokeArgs(null, $args);
}
}
// 获取构造函数
$constructor = $reflect->getConstructor();
// 处理依赖注入
$args = $constructor ? $this->bindParams($constructor, $vars) : [];
return $reflect->newInstanceArgs($args);
} catch (ReflectionException $e) {
throw new ClassNotFoundException('class not exists: ' . $class, $class);
}
}
- 门面模式
门面为容器中的类提供了一个静态调用接口
相对于传统的静态方法调用,带来了更好的可测试性和扩展性
门面模式其实就是通过静态的方式调用类的方法
门面模式的实现:
1:门面类继承门面基类,,覆盖父类的getFacadeClass()方法,此方法是映射装饰类的类名(由于容器的绑定标识机制,app对应的是 \thin\App::class
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\facade;
use think\Facade;
/**
* @see \think\App
* @mixin \think\App
* @method \think\App bind(string $bind) static 绑定模块或者控制器
* @method void initialize() static 初始化应用
* @method void init(string $module='') static 初始化模块
* @method \think\Response run() static 执行应用
* @method \think\App dispatch(\think\route\Dispatch $dispatch) static 设置当前请求的调度信息
* @method void log(mixed $log, string $type = 'info') static 记录调试信息
* @method mixed config(string $name='') static 获取配置参数
* @method \think\route\Dispatch routeCheck() static URL路由检测(根据PATH_INFO)
* @method \think\App routeMust(bool $must = false) static 设置应用的路由检测机制
* @method \think\Model model(string $name = '', string $layer = 'model', bool $appendSuffix = false, string $common = 'common') static 实例化模型
* @method object controller(string $name, string $layer = 'controller', bool $appendSuffix = false, string $empty = '') static 实例化控制器
* @method \think\Validate validate(string $name = '', string $layer = 'validate', bool $appendSuffix = false, string $common = 'common') static 实例化验证器类
* @method \think\db\Query db(mixed $config = [], mixed $name = false) static 数据库初始化
* @method mixed action(string $url, $vars = [], $layer = 'controller', $appendSuffix = false) static 调用模块的操作方法
* @method string parseClass(string $module, string $layer, string $name, bool $appendSuffix = false) static 解析应用类的类名
* @method string version() static 获取框架版本
* @method bool isDebug() static 是否为调试模式
* @method string getModulePath() static 获取当前模块路径
* @method void setModulePath(string $path) static 设置当前模块路径
* @method string getRootPath() static 获取应用根目录
* @method string getAppPath() static 获取应用类库目录
* @method string getRuntimePath() static 获取应用运行时目录
* @method string getThinkPath() static 获取核心框架目录
* @method string getRoutePath() static 获取路由目录
* @method string getConfigPath() static 获取应用配置目录
* @method string getConfigExt() static 获取配置后缀
* @method string setNamespace(string $namespace) static 设置应用类库命名空间
* @method string getNamespace() static 获取应用类库命名空间
* @method string getSuffix() static 是否启用类库后缀
* @method float getBeginTime() static 获取应用开启时间
* @method integer getBeginMem() static 获取应用初始内存占用
* @method \think\Container container() static 获取容器实例
*/
class App extends Facade
{
/**
* 获取当前Facade对应类名(或者已经绑定的容器对象标识)
* @access protected
* @return string
*/
protected static function getFacadeClass()
{
return 'app';
}
}
2:门面基类,门面基类实现 __callStatic魔术方法,将静态调用转发到 装饰类的对象调用
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think;
class Facade
{
/**
* 绑定对象
* @var array
*/
protected static $bind = [];
/**
* 始终创建新的对象实例
* @var bool
*/
protected static $alwaysNewInstance;
/**
* 绑定类的静态代理
* @static
* @access public
* @param string|array $name 类标识
* @param string $class 类名
* @return object
*/
public static function bind($name, $class = null)
{
if (__CLASS__ != static::class) {
return self::__callStatic('bind', func_get_args());
}
if (is_array($name)) {
self::$bind = array_merge(self::$bind, $name);
} else {
self::$bind[$name] = $class;
}
}
/**
* 创建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;
$facadeClass = static::getFacadeClass();
if ($facadeClass) {
$class = $facadeClass;
} elseif (isset(self::$bind[$class])) {
$class = self::$bind[$class];
}
if (static::$alwaysNewInstance) {
$newInstance = true;
}
return Container::getInstance()->make($class, $args, $newInstance);
}
/**
* 获取当前Facade对应类名(或者已经绑定的容器对象标识)
* @access protected
* @return string
*/
protected static function getFacadeClass()
{}
/**
* 带参数实例化当前Facade类
* @access public
* @return mixed
*/
public static function instance(...$args)
{
if (__CLASS__ != static::class) {
return self::createFacade('', $args);
}
}
/**
* 调用类的实例
* @access public
* @param string $class 类名或者标识
* @param array|true $args 变量
* @param bool $newInstance 是否每次创建新的实例
* @return mixed
*/
public static function make($class, $args = [], $newInstance = false)
{
if (__CLASS__ != static::class) {
return self::__callStatic('make', func_get_args());
}
if (true === $args) {
// 总是创建新的实例化对象
$newInstance = true;
$args = [];
}
return self::createFacade($class, $args, $newInstance);
}
// 调用实际类的方法
public static function __callStatic($method, $params)
{
var_dump($method);
return call_user_func_array([static::createFacade(), $method], $params);
}
}
- 门面模式调用
public function index()
{
// 门面模式的静态调用 相当于 就是容器类调用对象方法
$boo = \Config::get('app.') == Container::get('config')->get('app.');
var_dump($boo);
}