今天看到Swoft实现了注解引入依赖容器的效果,然后查了下原来这个Swoole框架的作者是Java出身的,怪不得有这功能。

然后想了下其实这功能挺强大的,我们用原生PHP也来实现下。

PHP的注解读取,依赖ReflectionClass反射类,不懂的朋友先去学下。

具体的实现流程分为:

  1. App.php 应用载入器
  2. Annotate.php 容器注入类
  3. DocParser.php 注解解析类
  4. 若干个应用类
  5. ...

下面的示例中,我们把助手类都统一放置在vendor目录下。

话不多说,具体的上代码,vendor/App.php应用载入器,该类主要实现跟MVC框架一样,加载指定的Controller/Action

  1. class App {
  2. public static function run($class, $function, ...$args) {
  3. // 基于反射类实例化
  4. $obj = new Annotate();
  5. $obj->run($class, $function, ...$args);
  6. }
  7. }

vendor/Annotate.php容器注入类,该类主要实现根据指定的Controller/Action,调用反射类获取注解内容并实例化对应的对象和方法:

  1. class Annotate {
  2. private $Reflection;
  3. private $instance;
  4. private $method;
  5. /**
  6. * 启动注解解析
  7. * @todo 无
  8. * @author 小黄牛
  9. * @version v1.0.1 + 2020.05.25
  10. * @deprecated 暂不启用
  11. * @global 无
  12. * @param string $class 对应的控制器
  13. * @param mixed $action 对应的操作
  14. * @param array $args 操作的参数
  15. * @return void
  16. */
  17. public function run($class, $action, ...$args) {
  18. if (!class_exists($class)) die('该类不存在');
  19. # 反射方法
  20. $method = new \ReflectionMethod($class, $action);
  21. # 不是公共方法,不给执行
  22. if (!$method->isPublic()) {
  23. die("{$class}::{$action}不是public方法");
  24. }
  25. # 使用ReflectionClass类
  26. $this->Reflection = new \ReflectionClass($class);
  27. # 解析注解
  28. $doc = DocParserFactory::getInstance()->parse($this->getClass());
  29. # 通过反射类进行实例化
  30. $this->instance = $this->Reflection->newInstance();
  31. # 获取方法的实例-但未执行
  32. $this->method = $this->Reflection->getmethod($action);
  33. # 注入全局容器
  34. $this->Ioc($doc);
  35. # 局部的注解
  36. $doc = DocParserFactory::getInstance()->parse($this->getFunction($action));
  37. # 注入局部容器
  38. $this->Ioc($doc);
  39. # 调用对应的方法
  40. $this->method->invokeArgs($this->instance, $args);
  41. }
  42. // 容器注入
  43. private function Ioc($doc) {
  44. # 解析容器注入 - 因为是Controller等于全局容器注入
  45. foreach ($doc as $val) {
  46. foreach ($val as $key=>$value) {
  47. # IOC容器注入
  48. if ($key == 'Ioc') {
  49. # 这里你可能还要检查注入的类、方法是否存在
  50. # name指定的属性名称是否被冲突
  51. $args = [];
  52. if (!empty($value['args'])) {
  53. $args = $value['args'];
  54. }
  55. $name = $value['name'];
  56. $reflection = new \ReflectionClass($value['class']);
  57. $obj = $reflection->newInstance();
  58. # 动态属性注入
  59. if (!$this->method->isStatic()) {
  60. if ($value['function']) {
  61. $method = $reflection->getmethod($value['function']);
  62. $this->instance->$name = $method->invokeArgs($obj, $args);
  63. } else {
  64. $this->instance->$name = $obj;
  65. }
  66. # 静态属性注入
  67. } else {
  68. if ($value['function']) {
  69. $method = $reflection->getmethod($value['function']);
  70. $this->Reflection->setStaticPropertyValue($name, $method->invokeArgs($obj, $args));
  71. } else {
  72. $this->Reflection->setStaticPropertyValue($name, $obj);
  73. }
  74. }
  75. }
  76. }
  77. }
  78. }
  79. // 获得类对应的注解
  80. public function getClass() {
  81. return $this->Reflection->getDocComment();
  82. }
  83. // 获得指定方法对应的注解
  84. public function getFunction($action) {
  85. # 获得成员函数名称
  86. $methods = $this->Reflection->getMethods();
  87. foreach ($methods as $method) {
  88. # 只要指定方法的
  89. if ($method->name == $action) {
  90. # 获得成员函数对应的注释内容
  91. return $method->getDocComment();
  92. }
  93. }
  94. return false;
  95. }
  96. }

vendor/DocParser.php注解解析类,该类主要用于切割注解内容,并解析出容器的注释风格,最终返回容器的调用参数数组:

  1. class DocParser {
  2. private $params = [];
  3. /**
  4. * args的单引号占位符
  5. */
  6. private $placeholder = '%SSSSS%';
  7. /**
  8. * 切割注解
  9. * @todo 无
  10. * @author 小黄牛
  11. * @version v1.0.1 + 2020.05.25
  12. * @deprecated 暂不启用
  13. * @global 无
  14. * @param string $doc 注解
  15. * @return array
  16. */
  17. public function parse($doc = '') {
  18. // 清空单例缓存
  19. $this->params = [];
  20. if ($doc == '') return $this->params;
  21. // 使用正则匹配出/***/
  22. if (preg_match('#^/\*\*(.*)\*/#s', $doc, $comment ) === false) return $this->params;
  23. // 获取注解
  24. $comment = trim($comment[1]);
  25. // 将注解按*号切割
  26. if (preg_match_all('#^\s*\*(.*)#m', $comment, $lines ) === false) return $this->params;
  27. // 开始解析注解
  28. $this->parseLines($lines[1]);
  29. return $this->params;
  30. }
  31. /**
  32. * 注解按行解析
  33. * @todo 无
  34. * @author 小黄牛
  35. * @version v1.0.1 + 2020.05.25
  36. * @deprecated 暂不启用
  37. * @global 无
  38. * @param array $lines 注解
  39. * @return void
  40. */
  41. private function parseLines($lines) {
  42. foreach ($lines as $line) {
  43. $doc = $this->parseLine($line);
  44. if ($doc) $this->params[] = $doc;
  45. }
  46. }
  47. /**
  48. * 注解解析行
  49. * @todo 无
  50. * @author 小黄牛
  51. * @version v1.0.1 + 2020.05.25
  52. * @deprecated 暂不启用
  53. * @global 无
  54. * @param string $line 每行的注解
  55. * @return array
  56. */
  57. private function parseLine($line) {
  58. // 删除左右两侧空格
  59. $line = trim ( $line );
  60. if (empty($line)) return false;
  61. $return = [];
  62. if (strpos($line, '@') === 0) {
  63. $string = substr($line, 1, strlen($line));
  64. $array = explode('(', $string);
  65. $param = $array[0];
  66. $string = str_replace($param.'(', '', $string);
  67. $value = substr($string, 0, strlen($string)-1);
  68. $value = preg_replace ( "/\s(?=\s)/","\\1", $value );
  69. $value = str_replace('" ,', '",', $value);
  70. $value_list = explode('",', $value);
  71. foreach ($value_list as $v) {
  72. $length = strpos($v, '=');
  73. $key = trim(substr($v, 0, $length));
  74. $val = str_replace('"', '', trim(substr($v, $length+1, strlen($v))));
  75. # 参数要特殊处理
  76. if ($key == 'args') {
  77. $val = $this->parseArgs($val);
  78. }
  79. $return[$param][$key] = $val;
  80. }
  81. return $return;
  82. }
  83. return false;
  84. }
  85. /**
  86. * 特殊处理传递的参数
  87. * @todo 无
  88. * @author 小黄牛
  89. * @version v1.0.1 + 2020.05.12
  90. * @deprecated 暂不启用
  91. * @global 无
  92. * @param string $val 参数
  93. * @return array
  94. */
  95. private function parseArgs($val) {
  96. # 提取单引号里的内容
  97. preg_match_all('/([\'"])([^\'"\.]*?)\1/',$val, $match);
  98. $list = $match[2];
  99. foreach ($list as $v) {
  100. # 加上单引号
  101. $v = "'".$v."'";
  102. # 植入占位符
  103. $string = str_replace(",", $this->placeholder, $v);
  104. # 替换内容
  105. $val = str_replace($v, $string, $val);
  106. }
  107. # 切割参数
  108. $param = explode(',', $val);
  109. # 把占位符取消掉
  110. foreach ($param as $k=>$v) {
  111. $param[$k] = str_replace($this->placeholder, ",", $v);
  112. }
  113. return $param;
  114. }
  115. }
  116. class DocParserFactory{
  117. private static $p;
  118. private function __construct(){
  119. }
  120. public static function getInstance(){
  121. if(self::$p == null){
  122. self::$p = new DocParser ();
  123. }
  124. return self::$p;
  125. }
  126. }

index.php应用类,这就不需要多说拉,就相当于Controller/Action类:

  1. /**
  2. * 注解说明:
  3. * @Ioc(class="类的路径,例如:\think\Db::name()", function="", name="test", args="1,2,3")
  4. * 容器注入:
  5. * class=容器的地址
  6. * function=容器的执行方法
  7. * name=要绑定的成员属性名称
  8. * args=需要传入function的参数
  9. */
  10. require_once 'vendor/App.php'; // 应用类
  11. require_once 'vendor/Annotate.php'; // 注解类
  12. require_once 'vendor/DocParser.php'; // 注解解析类
  13. echo '<pre>';
  14. // 启动应用加载
  15. App::run('Test', 'A', '小黄牛', '真帅~');
  16. /**
  17. * 假设我是一个 静态的Test控制器
  18. * 静态的容器注入,需要提前声明被注入的成员属性,并且需要是public类型的,否则无法注入
  19. * @Ioc(class="Demo", name="all")
  20. */
  21. class Test {
  22. /**
  23. * 全局容器
  24. */
  25. public static $all;
  26. /**
  27. * 局部容器
  28. */
  29. public static $local;
  30. /**
  31. * 尼妹的函数
  32. * @Ioc(class="Demo", name="local", function="_echo", args="'1,1',2,'1,3,0'")
  33. * @param string $name 打死你
  34. * @return void
  35. */
  36. public static function A($a=0, $b=1) {
  37. // 调用全局容器
  38. var_dump(self::$all->_echo(4,5,6));
  39. // 调用局部容器
  40. var_dump(self::$local);
  41. // 调用自身变量
  42. var_dump($a, $b);
  43. }
  44. }
  45. /**
  46. * 假设我是一个 动态的Test2控制器
  47. * @Ioc(class="Demo", name="all")
  48. */
  49. class Test2 {
  50. /**
  51. * 尼妹的函数
  52. * @Ioc(class="Demo", name="local", function="_echo", args="'1,1',2,'1,3,0'")
  53. * @param string $name 打死你
  54. * @return void
  55. */
  56. public function A($a=0, $b=1) {
  57. // 调用全局容器
  58. var_dump($this->all->_echo(4,5,6));
  59. // 调用局部容器
  60. var_dump($this->local);
  61. // 调用自身变量
  62. var_dump($a, $b);
  63. }
  64. }
  65. // 而我是需要被注入的容器
  66. class Demo {
  67. public function _echo ($a, $b, $c) {
  68. return [$a, $b, $c];
  69. }
  70. }

完整案例代码:下载