zhamao-framework/src/ZM/Plugin/PluginManager.php

218 lines
8.6 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
namespace ZM\Plugin;
2022-12-19 01:45:27 +08:00
use ZM\Annotation\AnnotationMap;
use ZM\Annotation\AnnotationParser;
use ZM\Annotation\Framework\BindEvent;
use ZM\Annotation\OneBot\BotEvent;
use ZM\Exception\PluginException;
2022-12-19 01:45:27 +08:00
use ZM\Store\FileSystem;
class PluginManager
{
2022-12-19 01:45:27 +08:00
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 插件信息列表 */
private static array $plugins = [];
/**
2022-12-19 01:45:27 +08:00
* 传入插件父目录,扫描插件目录下的所有插件并注册添加
*
* @param string $dir 插件目录
* @return int 返回添加插件的数量
* @throws PluginException
*/
public static function addPluginsFromDir(string $dir): int
{
// 遍历插件目录
2022-12-20 20:10:40 +08:00
if (!is_dir($dir)) {
return 0;
}
2022-12-19 01:45:27 +08:00
$list = FileSystem::scanDirFiles($dir, false, false, true);
$cnt = 0;
foreach ($list as $item) {
// 必须是目录形式的插件
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;
}
}
// 到这里,说明插件信息收集齐了,只需要加载就行了
self::addPlugin($plugin_meta);
++$cnt;
}
return $cnt;
}
/**
* 添加插件到全局注册中
*
* @throws PluginException
*/
public static function addPlugin(array $meta = []): void
{
2022-12-19 01:45:27 +08:00
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;
}
2022-12-19 01:45:27 +08:00
// 存在入口文件(单文件),从单文件加载
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 对象');
}
return;
}
2022-12-19 01:45:27 +08:00
// 存在自动加载,检测 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;
}
// 如果都不存在,那是不可能的事情,抛出一个谁都没见过的异常
unset(self::$plugins[$meta['name']]);
throw new PluginException('插件 ' . $meta['name'] . ' 无法加载,因为没有入口文件,也没有自动加载文件和内建 Composer');
}
2022-12-20 20:10:40 +08:00
/**
* 启用所有插件
*
* @param AnnotationParser $parser 传入注解解析器,用于将插件中的事件注解解析出来
*/
2022-12-19 01:45:27 +08:00
public static function enablePlugins(AnnotationParser $parser): void
{
foreach (self::$plugins as $name => $plugin) {
if (!isset($plugin['internal'])) {
logger()->info('Enabling plugin: ' . $name);
}
if (isset($plugin['object']) && $plugin['object'] instanceof ZMPlugin) {
$obj = $plugin['object'];
// 将 Event 加入事件监听
foreach ($obj->getEvents() as $event) {
$bind = new BindEvent($event[0], $event[2]);
$bind->on($event[1]);
AnnotationMap::$_list[BindEvent::class][] = $bind;
}
// 将 Routes 加入事件监听
foreach ($obj->getRoutes() as $route) {
$parser->parseSpecial($route);
}
// 将 BotEvents 加入事件监听
foreach ($obj->getBotEvents() as $event) {
AnnotationMap::$_list[BotEvent::class][] = $event;
}
// 将 BotCommand 加入事件监听
foreach ($obj->getBotCommands() as $cmd) {
$parser->parseSpecial($cmd);
}
} elseif (isset($plugin['autoload'], $plugin['dir'])) {
foreach ($plugin['autoload'] as $k => $v) {
$parser->addRegisterPath($plugin['dir'] . '/' . $v . '/', trim($k, '\\'));
}
}
}
}
2022-12-19 01:45:27 +08:00
private static function matchDefaultEntry(string $dir): string
{
2022-12-19 01:45:27 +08:00
$main = '';
// 没有设置入口文件,则遍历默认入口文件列表
foreach (self::$default_entries as $entry) {
$main_file = $dir . '/' . $entry;
if (is_file(zm_dir($main_file))) {
$main = $main_file;
break;
}
}
return $main;
}
}