Merge pull request #229 from zhamao-robot/refactor-annotation

重构注解解析器和优化部分代码
This commit is contained in:
Jerry 2023-01-02 23:23:58 +08:00 committed by GitHub
commit 19aab968ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 130 additions and 80 deletions

View File

@ -76,7 +76,8 @@
},
"bin": [
"bin/phpunit-zm",
"bin/zhamao"
"bin/zhamao",
"bin/zhamao.bat"
],
"config": {
"allow-plugins": {

View File

@ -50,6 +50,39 @@ composer require zhamao/framework
如果你打算在 Windows 使用原生的 Win 环境 PHP你需要先安装 PHP 和 Composer然后在任意目录下执行上方 composer 的安装方法即可。
### 包管理安装
Windows 也可以使用包管理安装 PHP、Composer例如你可以使用 Scoop 包管理进行安装:
```powershell
scoop install php
scoop install composer
```
采用这种包管理安装后,可直接使用 `php``composer` 命令在任意位置,无需配置环境变量。
如果你使用包管理或已经安装了 PHP 到系统内,接下来就直接使用 Composer 来安装框架即可!
```powershell
composer create-project zhamao/framework-starter zhamao-v3
cd zhamao-v3
./zhamao plugin:make
./zhamao server
```
### 纯手动安装
如果你不想使用包管理的方式安装 PHP且让 PHP 仅框架独立使用,你可以依次采用以下的方式来安装 PHP、Composer 和框架:
1. 从 GitHub 下载框架的脚手架,地址:<https://github.com/zhamao-robot/zhamao-framework-starter/archive/refs/heads/master.zip>
2. 解压框架脚手架,重命名文件夹名称为你自己喜欢的名称,例如 `zhamao-v3`
3. 从 PHP 官网下载 PHP选择 `Non Thread Safe` 版本PHP 版本选择 8.0 ~ 8.2 均可(推荐 8.1),下载完成后解压到框架目录下的 `runtime\php` 目录,例如 `D:\zhamao-v3\runtime\php\`
4. 从 [Composer 官网](https://getcomposer.org/download/) 或 [阿里云镜像](https://mirrors.aliyun.com/composer/composer.phar) 下载 Composer下载到 `runtime\` 目录。
5. 在你的脚手架目录下执行 `.\runtime\php\php.exe .\runtime\composer.phar install` 安装框架依赖。
6. 执行框架初始化命令:`./zhamao init`
7. 接下来你就可以使用和上方所有框架操作指令相同的内容了,例如 `./zhamao plugin:make``./zhamao server` 等。
8. 如果你需要使用 Composer你可以使用 `.\runtime\php\php.exe .\runtime\composer.phar` 来代替 `composer` 命令。
## 更多的环境部署和开发方式
除了上述方式之外,框架还支持源码模式、守护进程等运行方式,详情请参阅 [进阶开发]。

View File

@ -156,7 +156,7 @@ function darwin_env_check() {
# 询问是否安装 native php
function prompt_install_native_php() {
echo -ne "$(nhead yellow) 检测到系统的 PHP 不存在 swoole 扩展,是否下载安装独立的内建 PHP 和 Composer[Y/n] "
echo -ne "$(nhead yellow) 检测到系统的 PHP 不符合要求,是否下载安装独立的内建 PHP 和 Composer[Y/n] "
read -r y
case $y in
Y|y|"") return 0 ;;

View File

@ -21,7 +21,7 @@ function _zm_setup_loader()
// 如果在排除表就排除,否则就解析注解
if (is_dir(SOURCE_ROOT_DIR . '/' . $v) && !in_array($v, $excludes)) {
// 添加解析路径对应Base命名空间也贴出来
$parser->addRegisterPath(SOURCE_ROOT_DIR . '/' . $v . '/', trim($k, '\\'));
$parser->addPsr4Path(SOURCE_ROOT_DIR . '/' . $v . '/', trim($k, '\\'));
}
}
$parser->addSpecialParser(Setup::class, function (Setup $setup) {
@ -36,7 +36,7 @@ function _zm_setup_loader()
// TODO: 然后加载插件目录下的插件
// 解析所有注册路径的文件,获取注解
$parser->parseAll();
$parser->parse();
return json_encode(['setup' => $_tmp_setup_list]);
} catch (Throwable $e) {

View File

@ -27,16 +27,22 @@ class AnnotationMap
*/
public static array $_map = [];
/**
* 将Parser解析后的注解注册到全局的 AnnotationMap
*
* @param AnnotationParser $parser 注解解析器
*/
public static function loadAnnotationByParser(AnnotationParser $parser): void
public static function loadAnnotationList(array $list): void
{
// 生成后加入到全局list中
self::$_list = array_merge_recursive(self::$_list, $parser->generateAnnotationList());
self::$_map = $parser->getAnnotationMap();
self::$_list = array_merge_recursive(self::$_list, $list);
}
public static function loadAnnotationMap(array $map): void
{
self::$_map = array_merge_recursive(self::$_map, $map);
}
/**
* @return AnnotationBase[]
*/
public static function getAnnotationList(string $class_name): array
{
return self::$_list[$class_name] ?? [];
}
/**

View File

@ -20,9 +20,9 @@ use ZM\Utils\HttpUtil;
class AnnotationParser
{
/**
* @var array 要解析的路径列表
* @var array 要解析的 PSR-4 class 列表
*/
private array $path_list = [];
private array $class_list = [];
/**
* @var float 用于计算解析时间用的
@ -56,6 +56,7 @@ class AnnotationParser
$this->special_parsers = [
Middleware::class => [function (Middleware $middleware) { \middleware()->bindMiddleware([resolve($middleware->class), $middleware->method], $middleware->name, $middleware->params); }],
Route::class => [[$this, 'addRouteAnnotation']],
Closed::class => [function () { return false; }],
];
}
}
@ -71,13 +72,21 @@ class AnnotationParser
$this->special_parsers[$class_name][] = $callback;
}
public function parse(array $path): void
/**
* 解析所有传入的 PSR-4 下识别出来的类及下方的注解
* 返回一个包含三个元素的数组分别是list、map、tree
* 其中list为注解列表key是注解的class名称value是所有此注解的列表[Annotation1, ...]
* map是类、方法映射表关系的三维数组[类名 => [方法名 => [注解1, ...]]]
* tree是解析中间生成的树结构内含反射对象见下方注释
*
* @return array[]
* @throws \ReflectionException
*/
public function parse(): array
{
// 写日志
logger()->debug('parsing annotation in ' . $path[0] . ':' . $path[1]);
// 首先获取路径下所有的类(通过 PSR-4 标准解析)
$all_class = FileSystem::getClassesPsr4($path[0], $path[1]);
$reflection_tree = [];
$annotation_map = [];
$annotation_list = [];
// 读取配置文件中配置的忽略解析的注解名,防止误解析一些别的地方需要的注解,比如@mixin
$conf = config('global.runtime.annotation_reader_ignore');
@ -97,7 +106,7 @@ class AnnotationParser
// 声明一个既可以解析注解又可以解析Attribute的双reader来读取注解和Attribute
$reader = new DualReader(new AnnotationReader(), new AttributeReader());
foreach ($all_class as $v) {
foreach ($this->class_list as $v) {
logger()->debug('正在检索 ' . $v);
// 通过反射实现注解读取
@ -107,7 +116,7 @@ class AnnotationParser
// 这段为新加的:start
// 这里将每个类里面所有的类注解、方法注解通通加到一颗大树上,后期解析
/*
$annotation_map: {
$reflection_tree: {
Module\Example\Hello: {
class_annotations: [
注解对象1, 注解对象2, ...
@ -124,16 +133,16 @@ class AnnotationParser
*/
// 保存对class的注解
$this->annotation_tree[$v]['class_annotations'] = $class_annotations;
$reflection_tree[$v]['class_annotations'] = $class_annotations;
// 保存类成员的方法的对应反射对象们
$this->annotation_tree[$v]['methods'] = $methods;
$reflection_tree[$v]['methods'] = $methods;
// 保存对每个方法获取到的注解们
foreach ($methods as $method) {
$this->annotation_tree[$v]['methods_annotations'][$method->getName()] = $reader->getMethodAnnotations($method);
$reflection_tree[$v]['methods_annotations'][$method->getName()] = $reader->getMethodAnnotations($method);
}
// 因为适用于类的注解有一些比较特殊,比如有向下注入的,有控制行为的,所以需要遍历一下下放到方法里
foreach ($this->annotation_tree[$v]['class_annotations'] as $vs) {
foreach ($reflection_tree[$v]['class_annotations'] as $vs) {
$vs->class = $v;
// 预处理0排除所有非继承于 AnnotationBase 的注解
@ -142,33 +151,30 @@ class AnnotationParser
continue;
}
// 预处理1如果类包含了@Closed注解则跳过这个类
if ($vs instanceof Closed) {
unset($this->annotation_tree[$v]);
continue 2;
}
// 预处理2将适用于每一个函数的注解到类注解重新注解到每个函数下面
if ($vs instanceof ErgodicAnnotation) {
foreach (($this->annotation_tree[$v]['methods'] ?? []) as $method) {
foreach (($reflection_tree[$v]['methods'] ?? []) as $method) {
// 用 clone 的目的是生成个独立的对象,避免和 class 以及方法之间互相冲突
$copy = clone $vs;
$copy->method = $method->getName();
$this->annotation_tree[$v]['methods_annotations'][$method->getName()][] = $copy;
$reflection_tree[$v]['methods_annotations'][$method->getName()][] = $copy;
$annotation_list[get_class($vs)][] = $copy;
}
}
// 预处理3调用自定义解析器
if (($a = $this->parseSpecial($vs)) === true) {
if (($a = $this->parseSpecial($vs, $reflection_tree[$v]['class_annotations'])) === true) {
continue;
}
if ($a === false) {
unset($reflection_tree[$v]);
continue 2;
}
$annotation_list[get_class($vs)][] = $vs;
}
// 预处理3处理每个函数上面的特殊注解就是需要操作一些东西的
foreach (($this->annotation_tree[$v]['methods_annotations'] ?? []) as $method_name => $methods_annotations) {
foreach (($reflection_tree[$v]['methods_annotations'] ?? []) as $method_name => $methods_annotations) {
foreach ($methods_annotations as $method_anno) {
// 预处理3.0:排除所有非继承于 AnnotationBase 的注解
if (!$method_anno instanceof AnnotationBase) {
@ -180,43 +186,31 @@ class AnnotationParser
$method_anno->class = $v;
$method_anno->method = $method_name;
// 预处理3.2:如果包含了@Closed注解则跳过这个方法的注解解析
if ($method_anno instanceof Closed) {
unset($this->annotation_tree[$v]['methods_annotations'][$method_name]);
continue 2;
}
// 预处理3.3:调用自定义解析器
if (($a = $this->parseSpecial($method_anno, $methods_annotations)) === true) {
$a = $this->parseSpecial($method_anno, $methods_annotations);
if ($a === true) {
continue;
}
if ($a === false) {
unset($reflection_tree[$v]['methods_annotations'][$method_name]);
continue 2;
}
// 如果上方没有解析或返回了 true则添加到注解解析列表中
$this->annotation_map[$v][$method_name][] = $method_anno;
$annotation_map[$v][$method_name][] = $method_anno;
$annotation_list[get_class($method_anno)][] = $method_anno;
}
}
}
}
/**
* 注册各个模块类的注解和模块level的排序
*/
public function parseAll(): void
{
// 对每个设置的路径依次解析
foreach ($this->path_list as $path) {
$this->parse($path);
}
logger()->debug('解析注解完毕!');
// ob_dump($annotation_list);
return [$annotation_list, $annotation_map, $reflection_tree];
}
/**
* 生成排序后的注解列表
*/
public function generateAnnotationList(): array
public function generateAnnotationListFromMap(): array
{
$o = [];
foreach ($this->annotation_tree as $obj) {
@ -253,10 +247,11 @@ class AnnotationParser
* @param string $path 注册解析注解的路径
* @param string $indoor_name 起始命名空间的名称
*/
public function addRegisterPath(string $path, string $indoor_name)
public function addPsr4Path(string $path, string $indoor_name)
{
logger()->debug('Add register path: ' . $path . ' => ' . $indoor_name);
$this->path_list[] = [$path, $indoor_name];
$all_class = FileSystem::getClassesPsr4($path, $indoor_name);
$this->class_list = array_merge($this->class_list, $all_class);
}
/**

View File

@ -8,6 +8,7 @@ use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\Level;
/**
* Class Init
@ -17,9 +18,19 @@ use ZM\Annotation\AnnotationBase;
* @since 3.0.0
*/
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)]
class Init extends AnnotationBase
class Init extends AnnotationBase implements Level
{
public function __construct(public int $worker = 0)
public function __construct(public int $worker = 0, public int $level = 20)
{
}
public function getLevel()
{
return $this->level;
}
public function setLevel($level)
{
$this->level = $level;
}
}

View File

@ -151,7 +151,7 @@ class WorkerEventListener
// 如果在排除表就排除,否则就解析注解
if (is_dir(SOURCE_ROOT_DIR . '/' . $v) && !in_array($v, $excludes)) {
// 添加解析路径对应Base命名空间也贴出来
$parser->addRegisterPath(SOURCE_ROOT_DIR . '/' . $v . '/', trim($k, '\\'));
$parser->addPsr4Path(SOURCE_ROOT_DIR . '/' . $v . '/', trim($k, '\\'));
}
}
@ -162,9 +162,9 @@ class WorkerEventListener
continue;
}
match ($name) {
'onebot12' => PluginManager::addPlugin(['name' => $name, 'internal' => true, 'object' => new OneBot12Adapter(parser: $parser)]),
'onebot12-ban-other-ws' => PluginManager::addPlugin(['name' => $name, 'internal' => true, 'object' => new OneBot12Adapter(submodule: $name)]),
'command-manual' => PluginManager::addPlugin(['name' => $name, 'internal' => true, 'object' => new CommandManualPlugin($parser)]),
'onebot12' => PluginManager::addPlugin(['name' => $name, 'version' => '1.0', 'internal' => true, 'object' => new OneBot12Adapter(parser: $parser)]),
'onebot12-ban-other-ws' => PluginManager::addPlugin(['name' => $name, 'version' => '1.0', 'internal' => true, 'object' => new OneBot12Adapter(submodule: $name)]),
'command-manual' => PluginManager::addPlugin(['name' => $name, 'version' => '1.0', 'internal' => true, 'object' => new CommandManualPlugin($parser)]),
};
}
@ -186,9 +186,10 @@ class WorkerEventListener
}
// 解析所有注册路径的文件,获取注解
$parser->parseAll();
[$list, $map] = $parser->parse();
// 将Parser解析后的注解注册到全局的 AnnotationMap
AnnotationMap::loadAnnotationByParser($parser);
AnnotationMap::loadAnnotationList($list);
AnnotationMap::loadAnnotationMap($map);
// 排序所有的
AnnotationMap::sortAnnotationList();
}

View File

@ -46,7 +46,7 @@ class Framework
public const VERSION_ID = 659;
/** @var string 版本名称 */
public const VERSION = '3.0.0-beta4';
public const VERSION = '3.0.0-beta5';
/** @var array 传入的参数 */
protected array $argv;

View File

@ -58,7 +58,7 @@ class OneBot12Adapter extends ZMPlugin
// 处理和声明所有 BotCommand 下的 CommandArgument
$parser->addSpecialParser(BotCommand::class, [$this, 'parseBotCommand']);
// 不需要给列表写入 CommandArgument
$parser->addSpecialParser(CommandArgument::class, [$this, 'parseCommandArgument']);
$parser->addSpecialParser(CommandArgument::class, fn () => true);
break;
case 'onebot12-ban-other-ws':
// 禁止其他类型的 WebSocket 客户端接入
@ -86,14 +86,6 @@ class OneBot12Adapter extends ZMPlugin
return null;
}
/**
* 忽略解析记录 CommandArgument 注解
*/
public function parseCommandArgument(): ?bool
{
return true;
}
/**
* [CALLBACK] 调用 BotCommand 注解的方法
*

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ZM\Plugin;
use Jelix\Version\VersionComparator;
use ZM\Annotation\AnnotationMap;
use ZM\Annotation\AnnotationParser;
use ZM\Annotation\Framework\BindEvent;
@ -166,7 +167,8 @@ class PluginManager
/**
* 启用所有插件
*
* @param AnnotationParser $parser 传入注解解析器,用于将插件中的事件注解解析出来
* @param AnnotationParser $parser 传入注解解析器,用于将插件中的事件注解解析出来
* @throws PluginException
*/
public static function enablePlugins(AnnotationParser $parser): void
{
@ -174,6 +176,15 @@ class PluginManager
if (!isset($plugin['internal'])) {
logger()->info('Enabling plugin: ' . $name);
}
// 先判断下依赖关系,如果声明了依赖,但依赖不合规直接报错崩溃
foreach (($plugin['dependencies'] ?? []) as $dep_name => $dep_version) {
if (!isset(self::$plugins[$dep_name])) {
throw new PluginException('插件 ' . $name . ' 依赖插件 ' . $dep_name . ',但是没有找到这个插件');
}
if (VersionComparator::compareVersionRange(self::$plugins[$dep_name]['version'] ?? '1.0', $dep_version) === false) {
throw new PluginException('插件 ' . $name . ' 依赖插件 ' . $dep_name . ',但是这个插件的版本不符合要求');
}
}
if (isset($plugin['object']) && $plugin['object'] instanceof ZMPlugin) {
$obj = $plugin['object'];
// 将 Event 加入事件监听
@ -197,7 +208,7 @@ class PluginManager
}
} elseif (isset($plugin['autoload'], $plugin['dir'])) {
foreach ($plugin['autoload'] as $k => $v) {
$parser->addRegisterPath($plugin['dir'] . '/' . $v . '/', trim($k, '\\'));
$parser->addPsr4Path($plugin['dir'] . '/' . $v . '/', trim($k, '\\'));
}
}
}