今天看到Swoft实现了注解引入依赖容器的效果,然后查了下原来这个Swoole框架的作者是Java出身的,怪不得有这功能。
然后想了下其实这功能挺强大的,我们用原生PHP也来实现下。
PHP的注解读取,依赖ReflectionClass
反射类,不懂的朋友先去学下。
具体的实现流程分为:
App.php 应用载入器
Annotate.php 容器注入类
DocParser.php 注解解析类
若干个应用类
...
下面的示例中,我们把助手类都统一放置在vendor
目录下。
话不多说,具体的上代码,vendor/App.php
应用载入器,该类主要实现跟MVC框架一样,加载指定的Controller/Action
:
class App {
public static function run($class, $function, ...$args) {
// 基于反射类实例化
$obj = new Annotate();
$obj->run($class, $function, ...$args);
}
}
vendor/Annotate.php
容器注入类,该类主要实现根据指定的Controller/Action
,调用反射类获取注解内容并实例化对应的对象和方法:
class Annotate {
private $Reflection;
private $instance;
private $method;
/**
* 启动注解解析
* @todo 无
* @author 小黄牛
* @version v1.0.1 + 2020.05.25
* @deprecated 暂不启用
* @global 无
* @param string $class 对应的控制器
* @param mixed $action 对应的操作
* @param array $args 操作的参数
* @return void
*/
public function run($class, $action, ...$args) {
if (!class_exists($class)) die('该类不存在');
# 反射方法
$method = new \ReflectionMethod($class, $action);
# 不是公共方法,不给执行
if (!$method->isPublic()) {
die("{$class}::{$action}不是public方法");
}
# 使用ReflectionClass类
$this->Reflection = new \ReflectionClass($class);
# 解析注解
$doc = DocParserFactory::getInstance()->parse($this->getClass());
# 通过反射类进行实例化
$this->instance = $this->Reflection->newInstance();
# 获取方法的实例-但未执行
$this->method = $this->Reflection->getmethod($action);
# 注入全局容器
$this->Ioc($doc);
# 局部的注解
$doc = DocParserFactory::getInstance()->parse($this->getFunction($action));
# 注入局部容器
$this->Ioc($doc);
# 调用对应的方法
$this->method->invokeArgs($this->instance, $args);
}
// 容器注入
private function Ioc($doc) {
# 解析容器注入 - 因为是Controller等于全局容器注入
foreach ($doc as $val) {
foreach ($val as $key=>$value) {
# IOC容器注入
if ($key == 'Ioc') {
# 这里你可能还要检查注入的类、方法是否存在
# name指定的属性名称是否被冲突
$args = [];
if (!empty($value['args'])) {
$args = $value['args'];
}
$name = $value['name'];
$reflection = new \ReflectionClass($value['class']);
$obj = $reflection->newInstance();
# 动态属性注入
if (!$this->method->isStatic()) {
if ($value['function']) {
$method = $reflection->getmethod($value['function']);
$this->instance->$name = $method->invokeArgs($obj, $args);
} else {
$this->instance->$name = $obj;
}
# 静态属性注入
} else {
if ($value['function']) {
$method = $reflection->getmethod($value['function']);
$this->Reflection->setStaticPropertyValue($name, $method->invokeArgs($obj, $args));
} else {
$this->Reflection->setStaticPropertyValue($name, $obj);
}
}
}
}
}
}
// 获得类对应的注解
public function getClass() {
return $this->Reflection->getDocComment();
}
// 获得指定方法对应的注解
public function getFunction($action) {
# 获得成员函数名称
$methods = $this->Reflection->getMethods();
foreach ($methods as $method) {
# 只要指定方法的
if ($method->name == $action) {
# 获得成员函数对应的注释内容
return $method->getDocComment();
}
}
return false;
}
}
vendor/DocParser.php
注解解析类,该类主要用于切割注解内容,并解析出容器的注释风格,最终返回容器的调用参数数组:
class DocParser {
private $params = [];
/**
* args的单引号占位符
*/
private $placeholder = '%SSSSS%';
/**
* 切割注解
* @todo 无
* @author 小黄牛
* @version v1.0.1 + 2020.05.25
* @deprecated 暂不启用
* @global 无
* @param string $doc 注解
* @return array
*/
public function parse($doc = '') {
// 清空单例缓存
$this->params = [];
if ($doc == '') return $this->params;
// 使用正则匹配出/***/
if (preg_match('#^/\*\*(.*)\*/#s', $doc, $comment ) === false) return $this->params;
// 获取注解
$comment = trim($comment[1]);
// 将注解按*号切割
if (preg_match_all('#^\s*\*(.*)#m', $comment, $lines ) === false) return $this->params;
// 开始解析注解
$this->parseLines($lines[1]);
return $this->params;
}
/**
* 注解按行解析
* @todo 无
* @author 小黄牛
* @version v1.0.1 + 2020.05.25
* @deprecated 暂不启用
* @global 无
* @param array $lines 注解
* @return void
*/
private function parseLines($lines) {
foreach ($lines as $line) {
$doc = $this->parseLine($line);
if ($doc) $this->params[] = $doc;
}
}
/**
* 注解解析行
* @todo 无
* @author 小黄牛
* @version v1.0.1 + 2020.05.25
* @deprecated 暂不启用
* @global 无
* @param string $line 每行的注解
* @return array
*/
private function parseLine($line) {
// 删除左右两侧空格
$line = trim ( $line );
if (empty($line)) return false;
$return = [];
if (strpos($line, '@') === 0) {
$string = substr($line, 1, strlen($line));
$array = explode('(', $string);
$param = $array[0];
$string = str_replace($param.'(', '', $string);
$value = substr($string, 0, strlen($string)-1);
$value = preg_replace ( "/\s(?=\s)/","\\1", $value );
$value = str_replace('" ,', '",', $value);
$value_list = explode('",', $value);
foreach ($value_list as $v) {
$length = strpos($v, '=');
$key = trim(substr($v, 0, $length));
$val = str_replace('"', '', trim(substr($v, $length+1, strlen($v))));
# 参数要特殊处理
if ($key == 'args') {
$val = $this->parseArgs($val);
}
$return[$param][$key] = $val;
}
return $return;
}
return false;
}
/**
* 特殊处理传递的参数
* @todo 无
* @author 小黄牛
* @version v1.0.1 + 2020.05.12
* @deprecated 暂不启用
* @global 无
* @param string $val 参数
* @return array
*/
private function parseArgs($val) {
# 提取单引号里的内容
preg_match_all('/([\'"])([^\'"\.]*?)\1/',$val, $match);
$list = $match[2];
foreach ($list as $v) {
# 加上单引号
$v = "'".$v."'";
# 植入占位符
$string = str_replace(",", $this->placeholder, $v);
# 替换内容
$val = str_replace($v, $string, $val);
}
# 切割参数
$param = explode(',', $val);
# 把占位符取消掉
foreach ($param as $k=>$v) {
$param[$k] = str_replace($this->placeholder, ",", $v);
}
return $param;
}
}
class DocParserFactory{
private static $p;
private function __construct(){
}
public static function getInstance(){
if(self::$p == null){
self::$p = new DocParser ();
}
return self::$p;
}
}
index.php
应用类,这就不需要多说拉,就相当于Controller/Action
类:
/**
* 注解说明:
* @Ioc(class="类的路径,例如:\think\Db::name()", function="", name="test", args="1,2,3")
* 容器注入:
* class=容器的地址
* function=容器的执行方法
* name=要绑定的成员属性名称
* args=需要传入function的参数
*/
require_once 'vendor/App.php'; // 应用类
require_once 'vendor/Annotate.php'; // 注解类
require_once 'vendor/DocParser.php'; // 注解解析类
echo '<pre>';
// 启动应用加载
App::run('Test', 'A', '小黄牛', '真帅~');
/**
* 假设我是一个 静态的Test控制器
* 静态的容器注入,需要提前声明被注入的成员属性,并且需要是public类型的,否则无法注入
* @Ioc(class="Demo", name="all")
*/
class Test {
/**
* 全局容器
*/
public static $all;
/**
* 局部容器
*/
public static $local;
/**
* 尼妹的函数
* @Ioc(class="Demo", name="local", function="_echo", args="'1,1',2,'1,3,0'")
* @param string $name 打死你
* @return void
*/
public static function A($a=0, $b=1) {
// 调用全局容器
var_dump(self::$all->_echo(4,5,6));
// 调用局部容器
var_dump(self::$local);
// 调用自身变量
var_dump($a, $b);
}
}
/**
* 假设我是一个 动态的Test2控制器
* @Ioc(class="Demo", name="all")
*/
class Test2 {
/**
* 尼妹的函数
* @Ioc(class="Demo", name="local", function="_echo", args="'1,1',2,'1,3,0'")
* @param string $name 打死你
* @return void
*/
public function A($a=0, $b=1) {
// 调用全局容器
var_dump($this->all->_echo(4,5,6));
// 调用局部容器
var_dump($this->local);
// 调用自身变量
var_dump($a, $b);
}
}
// 而我是需要被注入的容器
class Demo {
public function _echo ($a, $b, $c) {
return [$a, $b, $c];
}
}
完整案例代码:下载