add PluginMeta to format plugin meta information

This commit is contained in:
crazywhalecc 2023-01-12 09:45:01 +08:00
parent 8c491e4290
commit 6854304d64
No known key found for this signature in database
GPG Key ID: 4B0FFA175E762022
12 changed files with 448 additions and 149 deletions

View File

@ -56,8 +56,9 @@ $config['runtime'] = [
/* 允许加载插件形式 */
$config['plugin'] = [
'enable' => true,
'load_dir' => 'plugins',
'enable' => true, // 是否启动插件系统,默认为 true如果为否则只能使用 src 模式编写用户代码
'load_dir' => 'plugins', // 插件目录相对目录时代表WORKING_DIR下的目录绝对目录按照绝对目录来
'composer_plugin_enable' => true, // 是否加载 Composer 依赖的插件,如果为 true 则读取 vendor/composer/installed.json 遍历加载
];
/* 内部默认启用的插件 */

View File

@ -43,6 +43,11 @@ const ZM_PROMPT_TIMEOUT_MENTION_USER = 4; // 回复超时时 at 该用户
const ZM_PROMPT_TIMEOUT_QUOTE_SELF = 8; // 回复超时时引用自己回复的提示语句
const ZM_PROMPT_TIMEOUT_QUOTE_USER = 16; // 回复超时时引用用户的消息
const ZM_PLUGIN_TYPE_NATIVE = 0; // 原生类型的插件特指内部插件、ZMApplication 继承的插件
const ZM_PLUGIN_TYPE_PHAR = 1; // Phar 类型的插件
const ZM_PLUGIN_TYPE_SOURCE = 2; // 源码类型的插件
const ZM_PLUGIN_TYPE_COMPOSER = 3; // Composer 依赖的插件
const LOAD_MODE_VENDOR = 0; // 从 vendor 加载
const LOAD_MODE_SRC = 1; // 从 src 加载

View File

@ -21,6 +21,13 @@ class BotAction extends AnnotationBase implements Level
{
}
public static function make(callable $callback, string $action, bool $need_response = false, int $level = 20): BotAction
{
$action = new BotAction($action, $need_response, $level);
$action->on($callback);
return $action;
}
public function getLevel()
{
return $this->level;

View File

@ -12,11 +12,13 @@ use ZM\Annotation\AnnotationHandler;
use ZM\Annotation\AnnotationMap;
use ZM\Annotation\AnnotationParser;
use ZM\Annotation\Framework\Init;
use ZM\Exception\PluginException;
use ZM\Exception\ZMKnownException;
use ZM\Framework;
use ZM\Plugin\CommandManual\CommandManualPlugin;
use ZM\Plugin\OneBot12Adapter;
use ZM\Plugin\PluginManager;
use ZM\Plugin\PluginMeta;
use ZM\Process\ProcessStateManager;
use ZM\Store\Database\DBException;
use ZM\Store\Database\DBPool;
@ -167,11 +169,15 @@ class WorkerEventListener
if (!$enable) {
continue;
}
match ($name) {
'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)]),
$plugin = match ($name) {
'onebot12' => new OneBot12Adapter(parser: $parser),
'onebot12-ban-other-ws' => new OneBot12Adapter(submodule: $name),
'command-manual' => new CommandManualPlugin($parser),
default => throw new PluginException('Unknown native plugin: ' . $name),
};
$meta = new PluginMeta(['name' => $name], ZM_PLUGIN_TYPE_NATIVE);
$meta->bindEntity($plugin);
PluginManager::addPlugin($meta);
}
// 然后加载插件目录的插件
@ -184,8 +190,19 @@ class WorkerEventListener
}
$load_dir = zm_dir($load_dir);
// 从 plugins 目录加载插件,包含 phar 和文件夹形式
$count = PluginManager::addPluginsFromDir($load_dir);
logger()->info('Loaded ' . $count . ' user plugins');
if ($count !== 0) {
logger()->info('Loaded ' . $count . ' user plugins from plugin dir');
}
// 从 composer 依赖加载插件
if (config('global.plugin.composer_plugin_enable', true)) {
$count = PluginManager::addPluginsFromComposer();
if ($count !== 0) {
logger()->info('Loaded ' . $count . ' user plugins from composer');
}
}
// 启用并初始化插件
PluginManager::enablePlugins($parser);
@ -225,6 +242,7 @@ class WorkerEventListener
foreach (DBPool::getAllPools() as $name => $pool) {
DBPool::destroyPool($name);
}
// 清空 Redis 连接池
foreach (RedisPool::getAllPools() as $name => $pool) {
RedisPool::destroyPool($name);
}

View File

@ -8,31 +8,12 @@ use Jelix\Version\VersionComparator;
use ZM\Annotation\AnnotationMap;
use ZM\Annotation\AnnotationParser;
use ZM\Annotation\Framework\BindEvent;
use ZM\Annotation\OneBot\BotCommand;
use ZM\Annotation\OneBot\BotEvent;
use ZM\Exception\PluginException;
use ZM\Store\FileSystem;
class PluginManager
{
private const DEFAULT_META = [
'name' => '<anonymous>',
'version' => 'dev',
'dir' => '',
'object' => null,
'entry_file' => null,
'autoload' => null,
'dependencies' => [],
];
/** @var array|string[] 缺省的自动加载插件的入口文件 */
public static array $default_entries = [
'main.php',
'entry.php',
'index.php',
];
/** @var array 插件信息列表 */
/** @var array<string, PluginMeta> 插件信息列表 */
private static array $plugins = [];
/**
@ -51,117 +32,158 @@ class PluginManager
$list = FileSystem::scanDirFiles($dir, false, false, true);
$cnt = 0;
foreach ($list as $item) {
// 检查是不是 phar 格式的插件
if (is_file($item) && pathinfo($item, PATHINFO_EXTENSION) === 'phar') {
// 如果是PHP文件尝试添加插件
self::addPluginFromPhar($item);
++$cnt;
continue;
}
// 必须是目录形式的插件
if (!is_dir($item)) {
continue;
}
$plugin_meta = self::DEFAULT_META;
$plugin_meta['dir'] = $item;
// 看看有没有插件信息文件
$info_file = $item . '/zmplugin.json';
$main_file = '';
// 如果有的话,就从插件信息文件中找到插件信息
if (is_file($info_file)) {
$info = json_decode(file_get_contents($info_file), true);
if (json_last_error() !== JSON_ERROR_NONE) {
logger()->error('插件信息文件解析失败: ' . json_last_error_msg());
continue;
}
// 设置名称(如果有)
$plugin_meta['name'] = $info['name'] ?? ('<anonymous:' . pathinfo($item, PATHINFO_BASENAME) . '>');
// 设置版本(如果有)
if (isset($info['version'])) {
$plugin_meta['version'] = $info['version'];
}
// 设置了入口文件,则遵循这个入口文件
if (isset($info['main'])) {
$main_file = FileSystem::isRelativePath($info['main']) ? ($item . '/' . $info['main']) : $info['main'];
} else {
$main_file = self::matchDefaultEntry($item);
}
// 检查有没有 composer.json 和 vendor/autoload.php 自动加载,如果有的话,那就写上去
$composer_file = $item . '/composer.json';
if (is_file(zm_dir($composer_file))) {
// composer.json 存在,那么就加载这个插件
$composer = json_decode(file_get_contents($composer_file), true);
if (json_last_error() !== JSON_ERROR_NONE) {
logger()->error('插件 composer.json 文件解析失败: ' . json_last_error_msg());
continue;
}
if (isset($composer['autoload']['psr-4']) && is_assoc_array($composer['autoload']['psr-4'])) {
$plugin_meta['autoload'] = $composer['autoload']['psr-4'];
}
}
// 主文件存在,则加载
if (is_file(zm_dir($main_file))) {
// 如果入口文件存在,那么就加载这个插件
$plugin_meta['entry_file'] = $main_file;
}
// composer.json 不存在,那么就忽略这个插件,并报 warning
if (!is_file(zm_dir($composer_file)) && $plugin_meta['entry_file'] === null) {
logger()->warning('插件 ' . $item . ' 不存在入口文件,也没有自动加载文件和内建 Composer跳过加载');
continue;
}
} else {
$plugin_meta['name'] = '<unnamed:' . pathinfo($item, PATHINFO_BASENAME) . '>';
// 到这里,说明没有 zmplugin.json 这个文件,那么我们就直接匹配
$main_file = self::matchDefaultEntry($item);
if (is_file(zm_dir($main_file))) {
// 如果入口文件存在,那么就加载这个插件
$plugin_meta['entry_file'] = $main_file;
} else {
continue;
}
// 先看有没有 zmplugin.json没有则不是正常的插件发个 notice 然后跳过
$meta_file = $item . '/zmplugin.json';
if (!is_file($meta_file)) {
logger()->notice('插件目录 {dir} 没有插件元信息zmplugin.json跳过扫描。', ['dir' => $item]);
continue;
}
// 到这里,说明插件信息收集齐了,只需要加载就行了
self::addPlugin($plugin_meta);
// 检验元信息是否合法,不合法发个 notice 然后跳过
$json_meta = json_decode(file_get_contents($meta_file), true);
if (!is_array($json_meta)) {
logger()->notice('插件目录 {dir} 的插件元信息zmplugin.json不是有效的 JSON跳过扫描。', ['dir' => $item]);
continue;
}
// 构造一个元信息对象
$meta = new PluginMeta($json_meta, ZM_PLUGIN_TYPE_SOURCE, $item);
if ($meta->getEntryFile() === null && $meta->getAutoloadFile() === null) {
logger()->notice('插件 ' . $item . ' 不存在入口文件,也没有自动加载文件和内建 Composer跳过加载');
continue;
}
// 添加插件到全局列表
self::addPlugin($meta);
++$cnt;
}
return $cnt;
}
/**
* 添加插件到全局注册中
* 添加一个 Phar 文件形式的插件
*
* @throws PluginException
*/
public static function addPlugin(array $meta = []): void
public static function addPluginFromPhar(string $phar_path): void
{
if (!isset($meta['name'])) {
throw new PluginException('Plugin must have a name!');
}
logger()->debug('Adding plugin: ' . $meta['name']);
self::$plugins[$meta['name']] = $meta;
// 存在直接声明的对象,那么直接初始化
if (isset($meta['object']) && $meta['object'] instanceof ZMPlugin) {
return;
}
// 存在入口文件(单文件),从单文件加载
if (isset($meta['entry_file']) && is_file(zm_dir($meta['entry_file']))) {
$zmplugin = self::$plugins[$meta['name']]['object'] = require $meta['entry_file'];
if (!$zmplugin instanceof ZMPlugin) {
unset(self::$plugins[$meta['name']]);
throw new PluginException('插件 ' . $meta['name'] . ' 的入口文件 ' . $meta['entry_file'] . ' 必须返回一个 ZMPlugin 对象');
$meta = [];
try {
// 加载这个 Phar 文件
$phar = require $phar_path;
// 读取元信息
$plugin_file_path = zm_dir('phar://' . $phar_path . '/zmplugin.json');
if (!file_exists($plugin_file_path)) {
throw new PluginException('插件元信息 zmplugin.json 文件不存在');
}
return;
// 解析元信息的 JSON
$meta_json = json_decode(file_get_contents($plugin_file_path), true);
// 失败抛出异常
if (!is_array($meta_json)) {
throw new PluginException('插件信息文件解析失败');
}
// $phar 这时应该是一个 ZMPlugin 对象,写入元信息
$meta = new PluginMeta($meta_json, ZM_PLUGIN_TYPE_PHAR, zm_dir('phar://' . $phar_path));
// 如果已经返回了一个插件对象,那么直接塞进去实体
if ($phar instanceof ZMPlugin) {
$meta->bindEntity($phar);
}
// 添加到插件列表
self::addPlugin($meta);
} catch (\Throwable $e) {
throw new PluginException('Phar 插件 ' . $phar_path . ' 加载失败: ' . $e->getMessage(), previous: $e);
}
}
// 存在自动加载,检测 vendor/autoload.php 是否存在,如果存在,那么就加载
if (isset($meta['autoload'], $meta['dir']) && $meta['dir'] !== '' && is_file($meta['dir'] . '/vendor/autoload.php')) {
require_once $meta['dir'] . '/vendor/autoload.php';
return;
/**
* Composer 添加插件
* @throws PluginException
*/
public static function addPluginsFromComposer(): int
{
$installed_file = SOURCE_ROOT_DIR . '/vendor/composer/installed.json';
if (!file_exists($installed_file)) {
logger()->notice('找不到 Composer 的 installed.json 文件,跳过扫描 Composer 插件');
return 0;
}
// 如果都不存在,那是不可能的事情,抛出一个谁都没见过的异常
unset(self::$plugins[$meta['name']]);
throw new PluginException('插件 ' . $meta['name'] . ' 无法加载,因为没有入口文件,也没有自动加载文件和内建 Composer');
$json = json_decode(file_get_contents($installed_file), true);
if (!is_array($json)) {
logger()->notice('Composer 的 installed.json 文件解析失败,跳过扫描 Composer 插件');
return 0;
}
$cnt = 0;
foreach ($json['packages'] as $item) {
$root_dir = SOURCE_ROOT_DIR . '/vendor/' . $item['name'];
$meta_file = zm_dir($root_dir . '/zmplugin.json');
if (!file_exists($meta_file)) {
continue;
}
// 检验元信息是否合法,不合法发个 notice 然后跳过
$json_meta = json_decode(file_get_contents($meta_file), true);
if (!is_array($json_meta)) {
logger()->notice('插件目录 {dir} 的插件元信息zmplugin.json不是有效的 JSON跳过扫描。', ['dir' => $item]);
continue;
}
// 构造一个元信息对象
$meta = new PluginMeta($json_meta, ZM_PLUGIN_TYPE_COMPOSER, zm_dir($root_dir));
if ($meta->getEntryFile() === null && $meta->getAutoloadFile() === null) {
logger()->notice('插件 ' . $item . ' 不存在入口文件,也没有自动加载文件和内建 Composer跳过加载');
continue;
}
// 添加插件到全局列表
self::addPlugin($meta);
++$cnt;
}
return $cnt;
}
/**
* 根据插件元信息对象添加一个插件到框架的全局插件库中
*
* @throws PluginException
*/
public static function addPlugin(PluginMeta $meta): void
{
logger()->debug('Adding plugin: ' . $meta->getName());
// 首先看看有没有 entity如果还没有 entity且 entry_file 有东西,那么就从 entry_file 获取 ZMPlugin 对象
if ($meta->getEntity() === null) {
if (($entry_file = $meta->getEntryFile()) !== null) {
$entity = require $entry_file;
if ($entity instanceof ZMPlugin) {
$meta->bindEntity($entity);
}
}
}
// 如果设置了 ZMPlugin entity并且已设置了 PluginLoad 事件,那就回调
// 接下来看看有没有 autoload有的话 require_once 一下
if (($autoload = $meta->getAutoloadFile()) !== null) {
require_once $autoload;
}
// 如果既没有 entity也没有 autoload那就要抛出异常了
if ($meta->getEntity() === null && $meta->getAutoloadFile() === null) {
throw new PluginException('插件 ' . $meta->getName() . ' 既没有入口文件,也没有自动加载文件,无法加载');
}
// 检查同名插件,如果有同名插件,则抛出异常
if (isset(self::$plugins[$meta->getName()])) {
throw new PluginException('插件 ' . $meta->getName() . ' 已经存在,无法加载同名插件或重复加载!');
}
self::$plugins[$meta->getName()] = $meta;
}
/**
@ -172,59 +194,88 @@ class PluginManager
*/
public static function enablePlugins(AnnotationParser $parser): void
{
foreach (self::$plugins as $name => $plugin) {
if (!isset($plugin['internal'])) {
foreach (self::$plugins as $name => $meta) {
// 除了内建插件外,输出 log 告知启动插件
if ($meta->getPluginType() !== ZM_PLUGIN_TYPE_NATIVE) {
logger()->info('Enabling plugin: ' . $name);
}
// 先判断下依赖关系,如果声明了依赖,但依赖不合规直接报错崩溃
foreach (($plugin['dependencies'] ?? []) as $dep_name => $dep_version) {
// 先判断依赖关系,如果声明了依赖,但依赖不合规则报错崩溃
foreach ($meta->getDependencies() 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) {
// 依赖的插件版本不对,不行
if (VersionComparator::compareVersionRange(self::$plugins[$dep_name]->getVersion(), $dep_version) === false) {
throw new PluginException('插件 ' . $name . ' 依赖插件 ' . $dep_name . ',但是这个插件的版本不符合要求');
}
}
if (isset($plugin['object']) && $plugin['object'] instanceof ZMPlugin) {
$obj = $plugin['object'];
// 如果插件为单文件形式,且设置了 pluginLoad 事件,那就调用
$meta->getEntity()?->emitPluginLoad($parser);
if (($entity = $meta->getEntity()) instanceof ZMPlugin) {
// 将 BotAction 加入事件监听
foreach ($entity->getBotActions() as $action) {
AnnotationMap::addSingleAnnotation($action);
$parser->parseSpecial($action);
}
// 将 BotCommand 加入事件监听
foreach ($entity->getBotCommands() as $cmd) {
AnnotationMap::addSingleAnnotation($cmd);
$parser->parseSpecial($cmd);
}
// 将 Event 加入事件监听
foreach ($obj->getEvents() as $event) {
foreach ($entity->getEvents() as $event) {
$bind = new BindEvent($event[0], $event[2]);
$bind->on($event[1]);
AnnotationMap::$_list[BindEvent::class][] = $bind;
AnnotationMap::addSingleAnnotation($bind);
}
// 将 Routes 加入事件监听
foreach ($obj->getRoutes() as $route) {
foreach ($entity->getRoutes() as $route) {
$parser->parseSpecial($route);
}
// 将 BotEvents 加入事件监听
foreach ($obj->getBotEvents() as $event) {
AnnotationMap::$_list[BotEvent::class][] = $event;
foreach ($entity->getBotEvents() as $event) {
AnnotationMap::addSingleAnnotation($event);
}
// 将 BotCommand 加入事件监听
foreach ($obj->getBotCommands() as $cmd) {
AnnotationMap::$_list[BotCommand::class][] = $cmd;
$parser->parseSpecial($cmd);
// 将 Cron 加入注解
foreach ($entity->getCrons() as $cron) {
AnnotationMap::addSingleAnnotation($cron);
$parser->parseSpecial($cron);
}
} elseif (isset($plugin['autoload'], $plugin['dir'])) {
foreach ($plugin['autoload'] as $k => $v) {
$parser->addPsr4Path($plugin['dir'] . '/' . $v . '/', trim($k, '\\'));
// 设置 @Init 注解
foreach ($entity->getInits() as $init) {
AnnotationMap::addSingleAnnotation($init);
}
}
// 如果设置了 Autoload file那么将会把 psr-4 的加载路径丢进 parser
foreach ($meta->getAutoloadPsr4() as $namespace => $path) {
$parser->addPsr4Path($meta->getRootDir() . '/' . $path . '/', trim($namespace, '\\'));
}
}
}
private static function matchDefaultEntry(string $dir): string
/**
* 打包插件到 Phar
*
* @throws PluginException
*/
public static function packPlugin(string $name): string
{
$main = '';
// 没有设置入口文件,则遍历默认入口文件列表
foreach (self::$default_entries as $entry) {
$main_file = $dir . '/' . $entry;
if (is_file(zm_dir($main_file))) {
$main = $main_file;
break;
}
// 先遍历下插件目录下是否有这个插件,没有这个插件则不能打包
$plugin_dir = config('global.plugin.load_dir', SOURCE_ROOT_DIR . '/plugins');
// 模拟加载一遍插件
self::addPluginsFromDir($plugin_dir);
// 必须是源码模式才行
if (!isset(self::$plugins[$name]) || self::$plugins[$name]->getPluginType() !== ZM_PLUGIN_TYPE_SOURCE) {
throw new PluginException("没有找到名字为 {$name} 的插件(要打包的插件必须是源码模式)。");
}
return $main;
$plugin = self::$plugins[$name];
// 插件目录
$dir = $plugin->getRootDir();
// TODO: 写到这了
// 插件加载方式判断
return '';
}
}

View File

@ -0,0 +1,173 @@
<?php
declare(strict_types=1);
namespace ZM\Plugin;
use ZM\Exception\PluginException;
/**
* 插件的元信息对象,对应 zmplugin.json
*/
class PluginMeta implements \JsonSerializable
{
/** @var string 插件名称 */
private string $name;
/** @var string 插件版本 */
private string $version;
/** @var string 插件描述 */
private string $description;
/** @var array 插件的依赖列表 */
private array $dependencies;
/** @var null|string 插件的根目录 */
private ?string $root_dir = null;
/** @var int 插件类型 */
private int $plugin_type;
/** @var array 元信息原文 */
private array $metas;
private ?ZMPlugin $entity = null;
/**
* @param array $meta 元信息数组格式
* @param int $plugin_type 插件类型
* @param null|string $root_dir 插件根目录
*/
public function __construct(array $meta, int $plugin_type = ZM_PLUGIN_TYPE_NATIVE, ?string $root_dir = null)
{
// 设置名称
$this->name = $meta['name'] ?? '<anonymous>';
// 设置版本
$this->version = $meta['version'] ?? '1.0.0';
// 设置描述
$this->description = $meta['description'] ?? '';
// 设置依赖
$this->dependencies = $meta['dependencies'] ?? [];
$this->metas = $meta;
// 设置插件根目录
$this->plugin_type = $plugin_type;
// 设置插件类型
$this->root_dir = $root_dir;
}
public function bindEntity(ZMPlugin $plugin): void
{
$this->entity = $plugin;
}
/**
* 获取该插件的入口文件(如果存在的话)
*/
public function getEntryFile(): ?string
{
// 没传入插件目录的话,直接返回空
if ($this->root_dir === null) {
return null;
}
// 首先看看元信息中有没有 main 字段指定,没有就从 default 里找
$main = zm_dir($this->root_dir . '/' . ($this->metas['main'] ?? 'main.php'));
if (file_exists($main)) {
return $main;
}
return null;
}
/**
* 获取该插件的 Composer 自动加载文件(如果存在的话)
*/
public function getAutoloadFile(): ?string
{
// 没传入插件目录的话,直接返回空
if ($this->root_dir === null) {
return null;
}
return match ($this->plugin_type) {
ZM_PLUGIN_TYPE_PHAR, ZM_PLUGIN_TYPE_SOURCE => file_exists($dir = zm_dir($this->root_dir . '/vendor/autoload.php')) ? $dir : null,
ZM_PLUGIN_TYPE_NATIVE, ZM_PLUGIN_TYPE_COMPOSER => zm_dir(SOURCE_ROOT_DIR . '/vendor/autoload.php'),
default => null,
};
}
/**
* 获取该插件的 Composer 自动加载 PSR-4 列表
*
* @throws PluginException 无法加载 composer.json 时抛出异常
*/
public function getAutoloadPsr4(): array
{
// 没传入插件目录的话,直接返回空
if ($this->root_dir === null) {
return [];
}
// 先找有没有 composer.json没有的话就返回空列表
if (!file_exists(zm_dir($this->root_dir . '/composer.json'))) {
return [];
}
// 有,但是 composer.json 是坏的,抛出一个异常
if (($composer = json_decode(file_get_contents(zm_dir($this->root_dir . '/composer.json')), true)) === null) {
throw new PluginException("Bad composer.json in plugin {$this->name}");
}
return $composer['autoload']['psr-4'] ?? [];
}
public function getName(): string
{
return $this->name;
}
public function getVersion(): string
{
return $this->version;
}
public function getDescription(): string
{
return $this->description;
}
public function getDependencies(): array
{
return $this->dependencies;
}
public function getRootDir(): string
{
return $this->root_dir;
}
public function setRootDir(string $root_dir): void
{
$this->root_dir = $root_dir;
}
public function getPluginType(): int
{
return $this->plugin_type;
}
public function jsonSerialize(): array
{
return [
'name' => $this->name,
'version' => $this->version,
'description' => $this->description,
'dependencies' => $this->dependencies,
];
}
public function getMetas(): array
{
return $this->metas;
}
public function getEntity(): ?ZMPlugin
{
return $this->entity;
}
}

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace ZM\Plugin\Traits;
use ZM\Annotation\OneBot\BotCommand;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace ZM\Plugin\Traits;
use ZM\Annotation\Framework\Cron;
@ -13,7 +15,6 @@ trait CronTrait
* 添加一个计划任务
*
* @param Cron $cron 计划任务注解对象
* @return void
*/
public function addCron(Cron $cron): void
{
@ -22,7 +23,6 @@ trait CronTrait
/**
* @internal
* @return array
*/
public function getCrons(): array
{

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace ZM\Plugin\Traits;
trait EventTrait

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace ZM\Plugin\Traits;
use ZM\Annotation\Framework\Init;
trait InitTrait
{
/** @var Init[] 插件启动回调 */
protected array $on_init = [];
/**
* 设置当前插件的加载后的初始化回调
*
* @param callable $callback 回调函数
* @param int $worker_id 所在的 Worker 进程,默认在 #0
* @param int $level 优先级
*/
public function onInit(callable $callback, int $worker_id = 0, int $level = 20): void
{
$init = new Init($worker_id, $level);
$init->on($callback);
$this->on_init[] = $init;
}
/**
* 获取初始化注解事件回调
*
* @internal
*/
public function getInits(): array
{
return $this->on_init;
}
}

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace ZM\Plugin\Traits;
use ZM\Annotation\AnnotationParser;

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace ZM\Plugin\Traits;
use ZM\Annotation\Http\Route;
@ -19,7 +21,6 @@ trait RouteTrait
$this->routes[] = $route;
}
/**
* @internal
*/