mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-07-03 23:05:37 +08:00
Compare commits
143 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ebcfa8e7e | ||
|
|
3edab143c3 | ||
|
|
9cd5f24d2f | ||
|
|
34919afb36 | ||
|
|
6282da4972 | ||
|
|
de4748860c | ||
|
|
c7736a7c3e | ||
|
|
3f9493ae7e | ||
|
|
dbda5dc299 | ||
|
|
c9ab695a9e | ||
|
|
bb57efc149 | ||
|
|
76543d5ec0 | ||
|
|
598f568b8c | ||
|
|
fc8c0b540a | ||
|
|
af75fc2f94 | ||
|
|
b94bbbca20 | ||
|
|
784fc1697b | ||
|
|
190eba585b | ||
|
|
f97b87d5e5 | ||
|
|
2c1a158d3e | ||
|
|
90de54c6a3 | ||
|
|
d596c315f2 | ||
|
|
52afaa3fb9 | ||
|
|
47cca37854 | ||
|
|
0fa49171b9 | ||
|
|
ea1d14ba00 | ||
|
|
a01791ddbe | ||
|
|
51a9c5c4e8 | ||
|
|
b1e37943df | ||
|
|
5459d1c5ff | ||
|
|
21373097bb | ||
|
|
f4f7327695 | ||
|
|
36fc2cb4cd | ||
|
|
b7469b9dd4 | ||
|
|
e7e77c99d0 | ||
|
|
6fbd35ed33 | ||
|
|
fba2339280 | ||
|
|
bd8270a4ae | ||
|
|
5299061c87 | ||
|
|
e843aeac55 | ||
|
|
19c792209b | ||
|
|
277d442634 | ||
|
|
51bec043e3 | ||
|
|
b7595f6f22 | ||
|
|
55181d583f | ||
|
|
b5a5a4f32e | ||
|
|
a746779efa | ||
|
|
736b6ea0a5 | ||
|
|
884f07baa6 | ||
|
|
b496c3136e | ||
|
|
bbfb40f973 | ||
|
|
a73e1fc164 | ||
|
|
ef7acb7fa0 | ||
|
|
fcd108adbe | ||
|
|
c9c76bfd10 | ||
|
|
d9302a3120 | ||
|
|
0cb3308dd1 | ||
|
|
583b9d7cf7 | ||
|
|
d7728ebc25 | ||
|
|
b6742526aa | ||
|
|
4defd8ac1b | ||
|
|
d28672e2ad | ||
|
|
94d6a51358 | ||
|
|
54eabc948f | ||
|
|
6c9e26ce83 | ||
|
|
a934037179 | ||
|
|
396444a089 | ||
|
|
699444abdd | ||
|
|
088031f807 | ||
|
|
8ce6e2c111 | ||
|
|
e7f36b9fcf | ||
|
|
dab91f526e | ||
|
|
35e23a7cbc | ||
|
|
7df05df48f | ||
|
|
707a1ae628 | ||
|
|
a65fc4076b | ||
|
|
f487897cf2 | ||
|
|
869d9ed543 | ||
|
|
8f61ed7561 | ||
|
|
8cb6a32341 | ||
|
|
687260e53c | ||
|
|
a919ce4554 | ||
|
|
d19d7acedd | ||
|
|
a393b3aff5 | ||
|
|
8c2036b6b4 | ||
|
|
dc081861ad | ||
|
|
ec76d2ba7d | ||
|
|
47ce0a8e9c | ||
|
|
f1c730962b | ||
|
|
d95a5131f6 | ||
|
|
69637cf482 | ||
|
|
25d6ec3a92 | ||
|
|
8bf5ff4e7f | ||
|
|
fd4e4be3a8 | ||
|
|
e0a01ea2ca | ||
|
|
256ccb96a6 | ||
|
|
bfffdc7d21 | ||
|
|
3bbe21268b | ||
|
|
1537e88a74 | ||
|
|
0d9711e62d | ||
|
|
0a7c5f06c6 | ||
|
|
e7a8a32b73 | ||
|
|
62b703f2e4 | ||
|
|
f44921d136 | ||
|
|
cd42545c7d | ||
|
|
0df3fd7bed | ||
|
|
56241da281 | ||
|
|
05a04104dd | ||
|
|
50d27ee186 | ||
|
|
371926b5a6 | ||
|
|
97face2406 | ||
|
|
ea64b40011 | ||
|
|
6d23e489aa | ||
|
|
f4d75e211f | ||
|
|
48f6cc644d | ||
|
|
33f517333f | ||
|
|
7253a309c7 | ||
|
|
f2aff5882d | ||
|
|
0dd61a7270 | ||
|
|
420b8e6d67 | ||
|
|
9e08919ce0 | ||
|
|
a7324f6c49 | ||
|
|
721c0834db | ||
|
|
e37629aaea | ||
|
|
a43a4a429e | ||
|
|
f5d34198e7 | ||
|
|
46604293e9 | ||
|
|
58e405cccd | ||
|
|
1deea5bebc | ||
|
|
cad5636f92 | ||
|
|
90290bee94 | ||
|
|
9f7ebceeb9 | ||
|
|
60ea2c7e12 | ||
|
|
441c866160 | ||
|
|
33d3341bb3 | ||
|
|
1d778fd78e | ||
|
|
0cd73a6304 | ||
|
|
2fedfe5b18 | ||
|
|
77c140bee6 | ||
|
|
c06e38d71f | ||
|
|
9b59cec22a | ||
|
|
306e992789 | ||
|
|
8335cfed69 |
12
.github/workflows/increment-build-number.yml
vendored
12
.github/workflows/increment-build-number.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
|
||||
- name: Setup PHP
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
|
||||
- name: Setup problem matchers for PHP
|
||||
run: echo "::add-matcher::${{ runner.tool_cache }}/php.json"
|
||||
|
||||
|
||||
- name: Get composer cache directory
|
||||
id: composer-cache
|
||||
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||
@@ -40,11 +40,15 @@ jobs:
|
||||
|
||||
- name: Install Composer Dependencies
|
||||
run: "composer install --prefer-dist --no-progress --optimize-autoloader"
|
||||
|
||||
|
||||
- name: Generate API Docs
|
||||
id: generate-api-docs
|
||||
continue-on-error: true
|
||||
run: bin/gendoc
|
||||
|
||||
|
||||
- name: Commit api docs
|
||||
if: steps.generate-api-docs.outcome == 'success'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
git config --global user.name 'Github Build Bot'
|
||||
git config --global user.email 'noreply@github.com'
|
||||
|
||||
11
.github/workflows/vuepress-deploy.yml
vendored
11
.github/workflows/vuepress-deploy.yml
vendored
@@ -1,10 +1,11 @@
|
||||
name: VuePress Auto Deploy
|
||||
name: Docs and Script Auto Deploy
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'docs/**'
|
||||
- 'ext/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -32,3 +33,11 @@ jobs:
|
||||
REMOTE_HOST: ${{ secrets.ZHAMAO_XIN_HOST }}
|
||||
REMOTE_USER: ${{ secrets.ZHAMAO_XIN_USER }}
|
||||
TARGET: ${{ secrets.ZHAMAO_XIN_TARGET }}
|
||||
- name: deploy script file
|
||||
uses: wlixcc/SFTP-Deploy-Action@v1.0
|
||||
with:
|
||||
username: ${{ secrets.ZHAMAO_XIN_USER }}
|
||||
server: ${{ secrets.ZHAMAO_XIN_HOST }}
|
||||
ssh_private_key: ${{ secrets.ZHAMAO_XIN_PRIVATE_KEY }}
|
||||
local_path: './ext/go.sh'
|
||||
remote_path: ${{ secrets.ZHAMAO_XIN_MAIN_TARGET }}
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -8,6 +8,9 @@
|
||||
/temp/
|
||||
/site/
|
||||
|
||||
# 框架审计文件
|
||||
audit.log
|
||||
|
||||
# 进程锁文件
|
||||
.daemon_pid
|
||||
.zm_worker_*.pid
|
||||
|
||||
32
README.md
32
README.md
@@ -74,18 +74,32 @@ public function index() {
|
||||
|
||||
## 开始
|
||||
|
||||
如果你是初学者,可以直接使用以下脚本部署 PHP 环境和安装框架的脚手架:
|
||||
框架目前支持 Linux、WSL、macOS 环境直接运行,上述环境可直接使用下方一键安装脚本。
|
||||
|
||||
> 如果你想在其他环境安装部署,可使用 Docker、Cygwin,详见文档。
|
||||
|
||||
```bash
|
||||
# 新建一个自己喜欢名字的文件夹,运行一键安装脚本 (仅限 x86-64(AMD64) 和 AArch64(ARM64) 平台)
|
||||
mkdir zhamao-app/
|
||||
cd zhamao-app/
|
||||
# 默认安装的 PHP 版本为 7.4,如需使用其他版本,请设置环境变量 ZM_DOWN_PHP_VERSION 为对应的 PHP 版本,例如:
|
||||
# export ZM_DOWN_PHP_VERSION=8.1
|
||||
bash -c "$(curl -fsSL https://api.zhamao.xin/go.sh)"
|
||||
# 检测PHP环境、安装框架
|
||||
bash <(curl -fsSL https://zhamao.xin/go.sh)
|
||||
|
||||
# 启动
|
||||
./zhamao server:start
|
||||
# 启动框架
|
||||
cd zhamao-app
|
||||
./zhamao server
|
||||
```
|
||||
|
||||
一键安装脚本还有可以自定义参数的方式,比如:
|
||||
|
||||
```bash
|
||||
# 脚本默认会检测系统的PHP,如果想直接跳过检测,安装独立的PHP版本,则添加此环境变量
|
||||
export ZM_NO_LOCAL_PHP="yes"
|
||||
# 脚本如果安装独立版本PHP,默认版本为8.0,如果想使用其他版本,则添加此环境变量指定版本
|
||||
export ZM_DOWN_PHP_VERSION="8.1"
|
||||
# 脚本默认会将框架在当前目录下的 `zhamao-app` 目录进行安装,如果想使用其他目录,则添加此环境变量
|
||||
export ZM_CUSTOM_DIR="my-custom-app"
|
||||
# 脚本默认会对本项目使用阿里云国内加速镜像,如果想使用packagist源,则添加此环境变量
|
||||
export ZM_COMPOSER_PACKAGIST="yes"
|
||||
# 执行完前面的环境变量再执行一键安装脚本,就可以实现自定义参数!
|
||||
bash <(curl -fsSL https://zhamao.xin/go.sh)
|
||||
```
|
||||
|
||||
关于其他安装方式,请参阅[文档](https://framework.zhamao.xin/guide/installation.html) 。
|
||||
|
||||
13
bin/gendoc
13
bin/gendoc
@@ -91,14 +91,15 @@ use Jasny\PhpdocParser\Set\PhpDocumentor;
|
||||
use Jasny\PhpdocParser\Tag\Summery;
|
||||
use ZM\Console\Console;
|
||||
|
||||
$opts = getopt('', ['show-warnings']);
|
||||
$opts = getopt('', ['show-warnings', 'show-debug']);
|
||||
|
||||
if (array_key_exists('show-warnings', $opts)) {
|
||||
$show_warnings = true;
|
||||
} else {
|
||||
$show_warnings = false;
|
||||
}
|
||||
|
||||
Console::init(2);
|
||||
Console::init(array_key_exists('show-debug', $opts) ? 4 : 2);
|
||||
|
||||
$errors = [];
|
||||
$warnings = [];
|
||||
@@ -296,12 +297,16 @@ foreach (new \RecursiveIteratorIterator($fs) as $file) {
|
||||
// 过滤不包含类的文件
|
||||
$tokens = token_get_all(file_get_contents($path));
|
||||
$found = false;
|
||||
foreach ($tokens as $token) {
|
||||
foreach ($tokens as $k => $token) {
|
||||
if (!is_array($token)) {
|
||||
continue;
|
||||
}
|
||||
if ($token[0] === T_CLASS) {
|
||||
if ($token[0] === T_CLASS && $tokens[$k - 2][0] !== T_NEW) {
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
if ($k >= 100) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$found) {
|
||||
|
||||
@@ -2,12 +2,24 @@
|
||||
<?php
|
||||
/** For Swoole coroutine tests */
|
||||
|
||||
// 如果改成 true,则会在终端显示所有炸毛框架的 Log
|
||||
const ZM_TEST_LOG_DEBUG = false;
|
||||
|
||||
use PHPUnit\Runner\Version;
|
||||
use PHPUnit\TextUI\TestRunner;
|
||||
use Swoole\Coroutine;
|
||||
use ZM\Annotation\Swoole\OnStart;
|
||||
use ZM\Command\RunServerCommand;
|
||||
use ZM\Console\Console;
|
||||
use ZM\ConsoleApplication;
|
||||
use ZM\Event\EventManager;
|
||||
use ZM\Framework;
|
||||
use ZM\Store\ZMAtomic;
|
||||
use ZM\Utils\ZMUtil;
|
||||
|
||||
$root = dirname(__DIR__);
|
||||
|
||||
co::set([
|
||||
Coroutine::set([
|
||||
'log_level' => SWOOLE_LOG_INFO,
|
||||
'trace_flags' => 0,
|
||||
]);
|
||||
@@ -41,8 +53,8 @@ if (!ini_get('date.timezone')) {
|
||||
|
||||
require $root . '/vendor/autoload.php';
|
||||
|
||||
const ZM_VERSION_ID = \ZM\ConsoleApplication::VERSION_ID;
|
||||
const ZM_VERSION = \ZM\ConsoleApplication::VERSION;
|
||||
const ZM_VERSION_ID = ConsoleApplication::VERSION_ID;
|
||||
const ZM_VERSION = ConsoleApplication::VERSION;
|
||||
|
||||
// 模拟define
|
||||
const ZM_PROCESS_MASTER = 1;
|
||||
@@ -64,19 +76,32 @@ $options['disable-safe-exit'] = true;
|
||||
$options['worker-num'] = 1;
|
||||
$options['private-mode'] = true;
|
||||
$options['log-error'] = true;
|
||||
\ZM\Console\Console::setLevel(0);
|
||||
$framework = new \ZM\Framework($options);
|
||||
$start = new \ZM\Annotation\Swoole\OnStart();
|
||||
|
||||
spl_autoload_register(static function ($class) {
|
||||
$map = [
|
||||
TestRunner::class => 'vendor/phpunit/phpunit/src/TextUI/TestRunner.php',
|
||||
Version::class => 'vendor/phpunit/phpunit/src/Runner/Version.php',
|
||||
];
|
||||
if (isset($map[$class])) {
|
||||
$source = file_get_contents(SOURCE_ROOT_DIR . '/' . $map[$class]);
|
||||
$source = str_replace(['#StandWith', 'Ukraine', 'declare(strict_types=1);'], '', $source);
|
||||
eval('?>' . $source);
|
||||
}
|
||||
}, true, true);
|
||||
|
||||
Console::setLevel(0);
|
||||
$framework = new Framework($options);
|
||||
$start = new OnStart();
|
||||
$start->method = function () {
|
||||
try {
|
||||
\ZM\Console\Console::setLevel(4);
|
||||
Console::setLevel(ZM_TEST_LOG_DEBUG ? 4 : 0);
|
||||
$retcode = PHPUnit\TextUI\Command::main(false);
|
||||
\ZM\Console\Console::setLevel(0);
|
||||
} finally {
|
||||
\ZM\Utils\ZMUtil::stop(($retcode ?? 1) !== 0);
|
||||
Console::setLevel(0);
|
||||
ZMUtil::stop(($retcode ?? 1) !== 0);
|
||||
}
|
||||
};
|
||||
\ZM\Event\EventManager::addEvent(\ZM\Annotation\Swoole\OnStart::class, $start);
|
||||
EventManager::addEvent(OnStart::class, $start);
|
||||
|
||||
$framework->start();
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
{
|
||||
"name": "jerry",
|
||||
"email": "admin@zhamao.me"
|
||||
},
|
||||
{
|
||||
"name": "sunxyw",
|
||||
"email": "dev@sunxyw.xyz"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
@@ -16,12 +20,13 @@
|
||||
"dragonmantank/cron-expression": "^3.3",
|
||||
"jelix/version": "^2.0",
|
||||
"koriym/attributes": "^1.0",
|
||||
"psr/container": "^2.0",
|
||||
"psy/psysh": "^0.11.2",
|
||||
"symfony/console": "~5.0 || ~4.0 || ~3.0",
|
||||
"symfony/console": "~6.0 || ~5.0 || ~4.0 || ~3.0",
|
||||
"symfony/polyfill-ctype": "^1.19",
|
||||
"symfony/polyfill-mbstring": "^1.19",
|
||||
"symfony/polyfill-php80": "^1.16",
|
||||
"symfony/routing": "~5.0 || ~4.0 || ~3.0",
|
||||
"symfony/routing": "~6.0 || ~5.0 || ~4.0 || ~3.0",
|
||||
"zhamao/config": "^1.0",
|
||||
"zhamao/connection-manager": "^1.0",
|
||||
"zhamao/console": "^1.0",
|
||||
@@ -30,6 +35,7 @@
|
||||
"require-dev": {
|
||||
"brainmaestro/composer-git-hooks": "^2.8",
|
||||
"friendsofphp/php-cs-fixer": "^3.2 != 3.7.0",
|
||||
"jetbrains/phpstorm-attributes": "^1.0",
|
||||
"phpstan/phpstan": "^1.1",
|
||||
"phpunit/phpunit": "^8.5 || ^9.0",
|
||||
"roave/security-advisories": "dev-latest",
|
||||
@@ -66,7 +72,10 @@
|
||||
],
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"sort-packages": true
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"phpstan/extension-installer": true
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"hooks": {
|
||||
|
||||
@@ -1 +1 @@
|
||||
module.exports = [{"title":"ZM","collapsable":true,"children":["ZM/Framework","ZM/ConsoleApplication","ZM/ZMServer"]},{"title":"ZM/API","collapsable":true,"children":["ZM/API/TuringAPI","ZM/API/GoCqhttpAPIV11","ZM/API/CQ","ZM/API/OneBotV11"]},{"title":"ZM/Annotation","collapsable":true,"children":["ZM/Annotation/AnnotationParser"]},{"title":"ZM/Annotation/Swoole","collapsable":true,"children":["ZM/Annotation/Swoole/OnSwooleEventBase"]},{"title":"ZM/Command","collapsable":true,"children":["ZM/Command/CheckConfigCommand"]},{"title":"ZM/Command/Module","collapsable":true,"children":["ZM/Command/Module/ModuleListCommand","ZM/Command/Module/ModulePackCommand"]},{"title":"ZM/Context","collapsable":true,"children":["ZM/Context/Context"]},{"title":"ZM/DB","collapsable":true,"children":["ZM/DB/InsertBody","ZM/DB/DB","ZM/DB/DeleteBody","ZM/DB/UpdateBody","ZM/DB/Table","ZM/DB/SelectBody"]},{"title":"ZM/Event","collapsable":true,"children":["ZM/Event/EventManager","ZM/Event/EventTracer","ZM/Event/EventDispatcher"]},{"title":"ZM/Event/SwooleEvent","collapsable":true,"children":["ZM/Event/SwooleEvent/OnWorkerStart","ZM/Event/SwooleEvent/OnTask"]},{"title":"ZM/Http","collapsable":true,"children":["ZM/Http/Response"]},{"title":"ZM/Module","collapsable":true,"children":["ZM/Module/ModuleBase","ZM/Module/ModuleUnpacker","ZM/Module/QQBot","ZM/Module/ModulePacker"]},{"title":"ZM/MySQL","collapsable":true,"children":["ZM/MySQL/MySQLConnection","ZM/MySQL/MySQLQueryBuilder","ZM/MySQL/MySQLStatement","ZM/MySQL/MySQLPool","ZM/MySQL/MySQLStatementWrapper","ZM/MySQL/MySQLWrapper"]},{"title":"ZM/Store","collapsable":true,"children":["ZM/Store/LightCacheInside","ZM/Store/LightCache","ZM/Store/ZMAtomic"]},{"title":"ZM/Store/Redis","collapsable":true,"children":["ZM/Store/Redis/ZMRedis"]},{"title":"ZM/Utils","collapsable":true,"children":["ZM/Utils/ZMUtil","ZM/Utils/DataProvider","ZM/Utils/HttpUtil","ZM/Utils/SignalListener","ZM/Utils/MessageUtil","ZM/Utils/Terminal","ZM/Utils/CoMessage"]},{"title":"ZM/Utils/Manager","collapsable":true,"children":["ZM/Utils/Manager/CronManager","ZM/Utils/Manager/ModuleManager","ZM/Utils/Manager/WorkerManager"]}]
|
||||
module.exports = [{"title":"ZM","collapsable":true,"children":["ZM/ZMServer"]},{"title":"ZM/API","collapsable":true,"children":["ZM/API/CQ","ZM/API/OneBotV11","ZM/API/GoCqhttpAPIV11","ZM/API/ZMRobot","ZM/API/TuringAPI"]},{"title":"ZM/API/Proxies/Bot","collapsable":true,"children":["ZM/API/Proxies/Bot/AllGroupsProxy","ZM/API/Proxies/Bot/AbstractBotProxy","ZM/API/Proxies/Bot/AllBotsProxy"]},{"title":"ZM/Annotation/Swoole","collapsable":true,"children":["ZM/Annotation/Swoole/OnSwooleEventBase"]},{"title":"ZM/Command","collapsable":true,"children":["ZM/Command/CheckConfigCommand"]},{"title":"ZM/Command/Module","collapsable":true,"children":["ZM/Command/Module/ModulePackCommand","ZM/Command/Module/ModuleListCommand"]},{"title":"ZM/Container","collapsable":true,"children":["ZM/Container/Container","ZM/Container/WorkerContainer","ZM/Container/BoundMethod"]},{"title":"ZM/DB","collapsable":true,"children":["ZM/DB/Table","ZM/DB/DB","ZM/DB/SelectBody","ZM/DB/InsertBody","ZM/DB/DeleteBody","ZM/DB/UpdateBody"]},{"title":"ZM/Event","collapsable":true,"children":["ZM/Event/EventTracer","ZM/Event/EventManager","ZM/Event/EventDispatcher"]},{"title":"ZM/Event/SwooleEvent","collapsable":true,"children":["ZM/Event/SwooleEvent/OnMessage","ZM/Event/SwooleEvent/OnTask"]},{"title":"ZM/Http","collapsable":true,"children":["ZM/Http/Response"]},{"title":"ZM/Module","collapsable":true,"children":["ZM/Module/ModuleUnpacker","ZM/Module/ModulePacker","ZM/Module/ModuleBase"]},{"title":"ZM/MySQL","collapsable":true,"children":["ZM/MySQL/MySQLConnection","ZM/MySQL/MySQLWrapper","ZM/MySQL/MySQLQueryBuilder","ZM/MySQL/MySQLStatementWrapper","ZM/MySQL/MySQLPool","ZM/MySQL/MySQLStatement"]},{"title":"ZM/Store","collapsable":true,"children":["ZM/Store/LightCache","ZM/Store/ZMAtomic","ZM/Store/LightCacheInside"]},{"title":"ZM/Store/Redis","collapsable":true,"children":["ZM/Store/Redis/ZMRedis"]},{"title":"ZM/Utils","collapsable":true,"children":["ZM/Utils/CoMessage","ZM/Utils/ReflectionUtil","ZM/Utils/Terminal","ZM/Utils/HttpUtil","ZM/Utils/DataProvider","ZM/Utils/ZMUtil","ZM/Utils/SignalListener","ZM/Utils/CommandInfoUtil","ZM/Utils/MessageUtil"]},{"title":"ZM/Utils/Manager","collapsable":true,"children":["ZM/Utils/Manager/WorkerManager","ZM/Utils/Manager/ModuleManager","ZM/Utils/Manager/CronManager"]}]
|
||||
@@ -21,17 +21,16 @@ footer: |
|
||||
|
||||
## 安装框架和环境
|
||||
|
||||
此命令可一键以模板安装 `zhamao-framework` 和 `PHP` 独立环境到目录下,无需 root!(仅限 Linux)
|
||||
此命令可一键以模板安装框架!(仅限 Linux 和 macOS)
|
||||
|
||||
```bash
|
||||
mkdir my-app
|
||||
cd my-app
|
||||
bash -c "$(curl -fsSL https://api.zhamao.xin/go.sh)"
|
||||
bash <(curl -fsSL https://zhamao.xin/go.sh)
|
||||
```
|
||||
|
||||
## 运行框架
|
||||
|
||||
```bash
|
||||
cd zhamao-app/
|
||||
./zhamao server
|
||||
```
|
||||
|
||||
|
||||
@@ -254,7 +254,7 @@ public function share(string $url, string $title, null|string $content, null|str
|
||||
## contact
|
||||
|
||||
```php
|
||||
public function contact(string $type, int|string $id): string
|
||||
public function contact(int|string $type, int|string $id): string
|
||||
```
|
||||
|
||||
### 描述
|
||||
@@ -265,7 +265,7 @@ public function contact(string $type, int|string $id): string
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| type | string | 名片类型 |
|
||||
| type | int|string | 名片类型 |
|
||||
| id | int|string | 好友或群ID |
|
||||
|
||||
### 返回
|
||||
@@ -455,7 +455,7 @@ public function _custom(string $type_name, array $params): string
|
||||
## decode
|
||||
|
||||
```php
|
||||
public function decode(string $msg, bool $is_content): string
|
||||
public function decode(int|string|Stringable $msg, bool $is_content): string
|
||||
```
|
||||
|
||||
### 描述
|
||||
@@ -466,7 +466,7 @@ public function decode(string $msg, bool $is_content): string
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| msg | string | 字符串 |
|
||||
| msg | int|string|Stringable | 字符串 |
|
||||
| is_content | bool | 如果是解码CQ码本体内容,则为false(默认),如果是参数内的字符串,则为true |
|
||||
|
||||
### 返回
|
||||
@@ -479,7 +479,7 @@ public function decode(string $msg, bool $is_content): string
|
||||
## replace
|
||||
|
||||
```php
|
||||
public function replace(string $str): string
|
||||
public function replace(int|string|Stringable $str): string
|
||||
```
|
||||
|
||||
### 描述
|
||||
@@ -490,7 +490,7 @@ public function replace(string $str): string
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| str | string | 字符串 |
|
||||
| str | int|string|Stringable | 字符串 |
|
||||
|
||||
### 返回
|
||||
|
||||
@@ -502,7 +502,7 @@ public function replace(string $str): string
|
||||
## escape
|
||||
|
||||
```php
|
||||
public function escape(string $msg, bool $is_content): string
|
||||
public function escape(int|string|Stringable $msg, bool $is_content): string
|
||||
```
|
||||
|
||||
### 描述
|
||||
@@ -513,7 +513,7 @@ public function escape(string $msg, bool $is_content): string
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| msg | string | 字符串 |
|
||||
| msg | int|string|Stringable | 字符串 |
|
||||
| is_content | bool | 如果是转义CQ码本体内容,则为false(默认),如果是参数内的字符串,则为true |
|
||||
|
||||
### 返回
|
||||
@@ -526,7 +526,7 @@ public function escape(string $msg, bool $is_content): string
|
||||
## encode
|
||||
|
||||
```php
|
||||
public function encode(string $msg, bool $is_content): string
|
||||
public function encode(int|string|Stringable $msg, bool $is_content): string
|
||||
```
|
||||
|
||||
### 描述
|
||||
@@ -537,7 +537,7 @@ public function encode(string $msg, bool $is_content): string
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| msg | string | 字符串 |
|
||||
| msg | int|string|Stringable | 字符串 |
|
||||
| is_content | bool | 如果是转义CQ码本体内容,则为false(默认),如果是参数内的字符串,则为true |
|
||||
|
||||
### 返回
|
||||
|
||||
117
docs/api/ZM/API/Proxies/Bot/AbstractBotProxy.md
Normal file
117
docs/api/ZM/API/Proxies/Bot/AbstractBotProxy.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# ZM\API\Proxies\Bot\AbstractBotProxy
|
||||
|
||||
## __construct
|
||||
|
||||
```php
|
||||
public function __construct(AbstractBotProxy|ZMRobot $bot): mixed
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
构造函数
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| bot | AbstractBotProxy|ZMRobot | 调用此代理的机器人实例 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| mixed | |
|
||||
|
||||
|
||||
## __call
|
||||
|
||||
```php
|
||||
public function __call(string $name, array $arguments): mixed
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
在传入的机器人实例上调用方法
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| name | string | 方法名 |
|
||||
| arguments | array | 参数 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| mixed | |
|
||||
|
||||
|
||||
## __get
|
||||
|
||||
```php
|
||||
public function __get(string $name): mixed
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取传入的机器人实例的属性
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| name | string | 属性名 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| mixed | |
|
||||
|
||||
|
||||
## __set
|
||||
|
||||
```php
|
||||
public function __set(string $name, mixed $value): mixed
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
设置传入的机器人实例的属性
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| name | string | 属性名 |
|
||||
| value | mixed | 属性值 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| mixed | |
|
||||
|
||||
|
||||
## __isset
|
||||
|
||||
```php
|
||||
public function __isset(string $name): bool
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
判断传入的机器人实例的属性是否存在
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| name | string | 属性名 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| bool | |
|
||||
24
docs/api/ZM/API/Proxies/Bot/AllBotsProxy.md
Normal file
24
docs/api/ZM/API/Proxies/Bot/AllBotsProxy.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# ZM\API\Proxies\Bot\AllBotsProxy
|
||||
|
||||
## __call
|
||||
|
||||
```php
|
||||
public function __call(string $name, array $arguments): array<mixed>
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
在所有机器人实例上调用方法
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| name | string | 方法名 |
|
||||
| arguments | array | 参数 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| array<mixed> | 返回一个包含所有执行结果的数组,键名为机器人ID |
|
||||
24
docs/api/ZM/API/Proxies/Bot/AllGroupsProxy.md
Normal file
24
docs/api/ZM/API/Proxies/Bot/AllGroupsProxy.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# ZM\API\Proxies\Bot\AllGroupsProxy
|
||||
|
||||
## __call
|
||||
|
||||
```php
|
||||
public function __call(string $name, array $arguments): array<mixed>
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
在传入的机器人实例上调用方法
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| name | string | 方法名 |
|
||||
| arguments | array | 参数 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| array<mixed> | 返回一个包含所有执行结果的数组,键名为群号 |
|
||||
@@ -1 +1,46 @@
|
||||
# ZM\API\ZMRobot
|
||||
# ZM\API\ZMRobot
|
||||
|
||||
## all
|
||||
|
||||
```php
|
||||
public function all(ZM\API\Proxies\Bot\AbstractBotProxy $proxy): ZM\API\Proxies\Bot\AllBotsProxy
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取一个会在所有机器人实例上执行的代理
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| proxy | ZM\API\Proxies\Bot\AbstractBotProxy | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| ZM\API\Proxies\Bot\AllBotsProxy | |
|
||||
|
||||
|
||||
## allGroups
|
||||
|
||||
```php
|
||||
public function allGroups(ZM\API\Proxies\Bot\AbstractBotProxy $proxy): ZM\API\Proxies\Bot\AllGroupsProxy
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取一个会在所有群上执行的代理
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| proxy | ZM\API\Proxies\Bot\AbstractBotProxy | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| ZM\API\Proxies\Bot\AllGroupsProxy | |
|
||||
|
||||
77
docs/api/ZM/Container/BoundMethod.md
Normal file
77
docs/api/ZM/Container/BoundMethod.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# ZM\Container\BoundMethod
|
||||
|
||||
## call
|
||||
|
||||
```php
|
||||
public function call(Container $container, callable|string $callback, array $parameters, string $default_method): mixed
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
调用指定闭包、类方法并注入依赖
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| container | Container | |
|
||||
| callback | callable|string | |
|
||||
| parameters | array | |
|
||||
| default_method | string | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| mixed | |
|
||||
|
||||
|
||||
## getMethodDependencies
|
||||
|
||||
```php
|
||||
public function getMethodDependencies(callable|string $callback, ZM\Container\ContainerInterface $container, array $parameters): array
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
Get all dependencies for a given method.
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| callback | callable|string | |
|
||||
| container | ZM\Container\ContainerInterface | |
|
||||
| parameters | array | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| array | |
|
||||
|
||||
|
||||
## addDependencyForCallParameter
|
||||
|
||||
```php
|
||||
public function addDependencyForCallParameter(ZM\Container\ContainerInterface $container, ReflectionParameter $parameter, array $parameters, array $dependencies): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
Get the dependency for the given call parameter.
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| container | ZM\Container\ContainerInterface | |
|
||||
| parameter | ReflectionParameter | |
|
||||
| parameters | array | |
|
||||
| dependencies | array | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
882
docs/api/ZM/Container/Container.md
Normal file
882
docs/api/ZM/Container/Container.md
Normal file
@@ -0,0 +1,882 @@
|
||||
# ZM\Container\Container
|
||||
|
||||
## getParent
|
||||
|
||||
```php
|
||||
public function getParent(): ZM\Container\ContainerInterface
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取父容器
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| ZM\Container\ContainerInterface | |
|
||||
|
||||
|
||||
## has
|
||||
|
||||
```php
|
||||
public function has(string $id): bool
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
Returns true if the container can return an entry for the given identifier.
|
||||
Returns false otherwise.
|
||||
`has($id)` returning true does not mean that `get($id)` will not throw an exception.
|
||||
It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| id | string | identifier of the entry to look for |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| bool | |
|
||||
|
||||
|
||||
## make
|
||||
|
||||
```php
|
||||
public function make(class-string<T> $abstract, array $parameters): Closure|mixed|T
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取一个绑定的实例
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | class-string<T> | 类或接口名 |
|
||||
| parameters | array | 参数 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| Closure|mixed|T | 实例 |
|
||||
|
||||
|
||||
## getInstance
|
||||
|
||||
```php
|
||||
public function getInstance(): static
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取类实例
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| static | |
|
||||
|
||||
|
||||
## bound
|
||||
|
||||
```php
|
||||
public function bound(string $abstract): bool
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
判断对应的类或接口是否已经注册
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| bool | |
|
||||
|
||||
|
||||
## getAlias
|
||||
|
||||
```php
|
||||
public function getAlias(string $abstract): string
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取类别名(如存在)
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| string | 别名,不存在时返回传入的类或接口名 |
|
||||
|
||||
|
||||
## alias
|
||||
|
||||
```php
|
||||
public function alias(string $abstract, string $alias): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
注册一个类别名
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
| alias | string | 别名 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
|
||||
|
||||
## bind
|
||||
|
||||
```php
|
||||
public function bind(string $abstract, null|Closure|string $concrete, bool $shared): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
注册绑定
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
| concrete | null|Closure|string | 返回类实例的闭包,或是类名 |
|
||||
| shared | bool | 是否共享 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
|
||||
|
||||
## bindIf
|
||||
|
||||
```php
|
||||
public function bindIf(string $abstract, null|Closure|string $concrete, bool $shared): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
注册绑定
|
||||
在已经绑定时不会重复注册
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
| concrete | null|Closure|string | 返回类实例的闭包,或是类名 |
|
||||
| shared | bool | 是否共享 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
|
||||
|
||||
## singleton
|
||||
|
||||
```php
|
||||
public function singleton(string $abstract, null|Closure|string $concrete): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
注册一个单例绑定
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
| concrete | null|Closure|string | 返回类实例的闭包,或是类名 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
|
||||
|
||||
## singletonIf
|
||||
|
||||
```php
|
||||
public function singletonIf(string $abstract, null|Closure|string $concrete): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
注册一个单例绑定
|
||||
在已经绑定时不会重复注册
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
| concrete | null|Closure|string | 返回类实例的闭包,或是类名 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
|
||||
|
||||
## instance
|
||||
|
||||
```php
|
||||
public function instance(string $abstract, mixed $instance): mixed
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
注册一个已有的实例,效果等同于单例绑定
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
| instance | mixed | 实例 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| mixed | |
|
||||
|
||||
|
||||
## factory
|
||||
|
||||
```php
|
||||
public function factory(string $abstract): Closure
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取一个解析对应类实例的闭包
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| Closure | |
|
||||
|
||||
|
||||
## flush
|
||||
|
||||
```php
|
||||
public function flush(): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
清除所有绑定和实例
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
|
||||
|
||||
## traitMake
|
||||
|
||||
```php
|
||||
public function traitMake(class-string<T> $abstract, array $parameters): Closure|mixed|T
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取一个绑定的实例
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | class-string<T> | 类或接口名 |
|
||||
| parameters | array | 参数 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| Closure|mixed|T | 实例 |
|
||||
|
||||
|
||||
## build
|
||||
|
||||
```php
|
||||
public function build(Closure|string $concrete): mixed
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
实例化具体的类实例
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| concrete | Closure|string | 类名或对应的闭包 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| mixed | |
|
||||
|
||||
|
||||
## call
|
||||
|
||||
```php
|
||||
public function call(callable|string $callback, array $parameters, null|string $default_method): mixed
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
调用对应的方法,并自动注入依赖
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| callback | callable|string | 对应的方法 |
|
||||
| parameters | array | 参数 |
|
||||
| default_method | null|string | 默认方法 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| mixed | |
|
||||
|
||||
|
||||
## get
|
||||
|
||||
```php
|
||||
public function get(string $id): mixed
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
Finds an entry of the container by its identifier and returns it.
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| id | string | identifier of the entry to look for * |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| mixed | entry |
|
||||
|
||||
|
||||
## extend
|
||||
|
||||
```php
|
||||
public function extend(string $abstract, Closure $closure): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
扩展一个类或接口
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
| closure | Closure | 扩展闭包 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
|
||||
|
||||
## getLogPrefix
|
||||
|
||||
```php
|
||||
public function getLogPrefix(): string
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取日志前缀
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| string | |
|
||||
|
||||
|
||||
## setLogPrefix
|
||||
|
||||
```php
|
||||
public function setLogPrefix(string $prefix): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
设置日志前缀
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| prefix | string | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
|
||||
|
||||
## getExtenders
|
||||
|
||||
```php
|
||||
public function getExtenders(string $abstract): Closure[]
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取对应类型的所有扩展器
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| Closure[] | |
|
||||
|
||||
|
||||
## isAlias
|
||||
|
||||
```php
|
||||
public function isAlias(string $name): bool
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
判断传入的是否为别名
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| name | string | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| bool | |
|
||||
|
||||
|
||||
## dropStaleInstances
|
||||
|
||||
```php
|
||||
public function dropStaleInstances(string $abstract): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
抛弃所有过时的实例和别名
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
|
||||
|
||||
## getClosure
|
||||
|
||||
```php
|
||||
public function getClosure(string $abstract, string $concrete): Closure
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取一个解析对应类的闭包
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
| concrete | string | 实际类名 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| Closure | |
|
||||
|
||||
|
||||
## getLastParameterOverride
|
||||
|
||||
```php
|
||||
public function getLastParameterOverride(): array
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取最后一次的覆盖参数
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| array | |
|
||||
|
||||
|
||||
## notInstantiable
|
||||
|
||||
```php
|
||||
public function notInstantiable(string $concrete, string $reason): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
抛出实例化异常
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| concrete | string | |
|
||||
| reason | string | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
|
||||
|
||||
## resolveDependencies
|
||||
|
||||
```php
|
||||
public function resolveDependencies(ReflectionParameter[] $dependencies): array
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
解析依赖
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| dependencies | ReflectionParameter[] | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| array | |
|
||||
|
||||
|
||||
## hasParameterOverride
|
||||
|
||||
```php
|
||||
public function hasParameterOverride(ReflectionParameter $parameter): bool
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
判断传入的参数是否存在覆盖参数
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| parameter | ReflectionParameter | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| bool | |
|
||||
|
||||
|
||||
## getParameterOverride
|
||||
|
||||
```php
|
||||
public function getParameterOverride(ReflectionParameter $parameter): mixed
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取覆盖参数
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| parameter | ReflectionParameter | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| mixed | |
|
||||
|
||||
|
||||
## hasParameterTypeOverride
|
||||
|
||||
```php
|
||||
public function hasParameterTypeOverride(ReflectionParameter $parameter): bool
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
判断传入的参数是否存在临时注入的参数
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| parameter | ReflectionParameter | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| bool | |
|
||||
|
||||
|
||||
## getParameterTypeOverride
|
||||
|
||||
```php
|
||||
public function getParameterTypeOverride(ReflectionParameter $parameter): mixed
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取临时注入的参数
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| parameter | ReflectionParameter | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| mixed | |
|
||||
|
||||
|
||||
## resolvePrimitive
|
||||
|
||||
```php
|
||||
public function resolvePrimitive(ReflectionParameter $parameter): mixed
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
解析基本类型
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| parameter | ReflectionParameter | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| mixed | 对应类型的默认值 |
|
||||
|
||||
|
||||
## resolveClass
|
||||
|
||||
```php
|
||||
public function resolveClass(ReflectionParameter $parameter): mixed
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
解析类
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| parameter | ReflectionParameter | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| mixed | |
|
||||
|
||||
|
||||
## getConcrete
|
||||
|
||||
```php
|
||||
public function getConcrete(string $abstract): Closure|string
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取类名的实际类型
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| Closure|string | |
|
||||
|
||||
|
||||
## isBuildable
|
||||
|
||||
```php
|
||||
public function isBuildable(mixed $concrete, string $abstract): bool
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
判断传入的实际类型是否可以构造
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| concrete | mixed | 实际类型 |
|
||||
| abstract | string | 类或接口名 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| bool | |
|
||||
|
||||
|
||||
## isShared
|
||||
|
||||
```php
|
||||
public function isShared(string $abstract): bool
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
判断传入的类型是否为共享实例
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| bool | |
|
||||
|
||||
|
||||
## shouldLog
|
||||
|
||||
```php
|
||||
public function shouldLog(): bool
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
判断是否输出日志
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| bool | |
|
||||
|
||||
|
||||
## log
|
||||
|
||||
```php
|
||||
public function log(string $message): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
记录日志(自动附加容器日志前缀)
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| message | string | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
841
docs/api/ZM/Container/WorkerContainer.md
Normal file
841
docs/api/ZM/Container/WorkerContainer.md
Normal file
@@ -0,0 +1,841 @@
|
||||
# ZM\Container\WorkerContainer
|
||||
|
||||
## getInstance
|
||||
|
||||
```php
|
||||
public function getInstance(): static
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取类实例
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| static | |
|
||||
|
||||
|
||||
## bound
|
||||
|
||||
```php
|
||||
public function bound(string $abstract): bool
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
判断对应的类或接口是否已经注册
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| bool | |
|
||||
|
||||
|
||||
## getAlias
|
||||
|
||||
```php
|
||||
public function getAlias(string $abstract): string
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取类别名(如存在)
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| string | 别名,不存在时返回传入的类或接口名 |
|
||||
|
||||
|
||||
## alias
|
||||
|
||||
```php
|
||||
public function alias(string $abstract, string $alias): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
注册一个类别名
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
| alias | string | 别名 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
|
||||
|
||||
## bind
|
||||
|
||||
```php
|
||||
public function bind(string $abstract, null|Closure|string $concrete, bool $shared): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
注册绑定
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
| concrete | null|Closure|string | 返回类实例的闭包,或是类名 |
|
||||
| shared | bool | 是否共享 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
|
||||
|
||||
## bindIf
|
||||
|
||||
```php
|
||||
public function bindIf(string $abstract, null|Closure|string $concrete, bool $shared): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
注册绑定
|
||||
在已经绑定时不会重复注册
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
| concrete | null|Closure|string | 返回类实例的闭包,或是类名 |
|
||||
| shared | bool | 是否共享 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
|
||||
|
||||
## singleton
|
||||
|
||||
```php
|
||||
public function singleton(string $abstract, null|Closure|string $concrete): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
注册一个单例绑定
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
| concrete | null|Closure|string | 返回类实例的闭包,或是类名 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
|
||||
|
||||
## singletonIf
|
||||
|
||||
```php
|
||||
public function singletonIf(string $abstract, null|Closure|string $concrete): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
注册一个单例绑定
|
||||
在已经绑定时不会重复注册
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
| concrete | null|Closure|string | 返回类实例的闭包,或是类名 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
|
||||
|
||||
## instance
|
||||
|
||||
```php
|
||||
public function instance(string $abstract, mixed $instance): mixed
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
注册一个已有的实例,效果等同于单例绑定
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
| instance | mixed | 实例 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| mixed | |
|
||||
|
||||
|
||||
## factory
|
||||
|
||||
```php
|
||||
public function factory(string $abstract): Closure
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取一个解析对应类实例的闭包
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| Closure | |
|
||||
|
||||
|
||||
## flush
|
||||
|
||||
```php
|
||||
public function flush(): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
清除所有绑定和实例
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
|
||||
|
||||
## make
|
||||
|
||||
```php
|
||||
public function make(class-string<T> $abstract, array $parameters): Closure|mixed|T
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取一个绑定的实例
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | class-string<T> | 类或接口名 |
|
||||
| parameters | array | 参数 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| Closure|mixed|T | 实例 |
|
||||
|
||||
|
||||
## build
|
||||
|
||||
```php
|
||||
public function build(Closure|string $concrete): mixed
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
实例化具体的类实例
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| concrete | Closure|string | 类名或对应的闭包 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| mixed | |
|
||||
|
||||
|
||||
## call
|
||||
|
||||
```php
|
||||
public function call(callable|string $callback, array $parameters, null|string $default_method): mixed
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
调用对应的方法,并自动注入依赖
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| callback | callable|string | 对应的方法 |
|
||||
| parameters | array | 参数 |
|
||||
| default_method | null|string | 默认方法 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| mixed | |
|
||||
|
||||
|
||||
## get
|
||||
|
||||
```php
|
||||
public function get(string $id): mixed
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
Finds an entry of the container by its identifier and returns it.
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| id | string | identifier of the entry to look for * |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| mixed | entry |
|
||||
|
||||
|
||||
## has
|
||||
|
||||
```php
|
||||
public function has(string $id): bool
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
Returns true if the container can return an entry for the given identifier.
|
||||
Returns false otherwise.
|
||||
`has($id)` returning true does not mean that `get($id)` will not throw an exception.
|
||||
It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| id | string | identifier of the entry to look for |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| bool | |
|
||||
|
||||
|
||||
## extend
|
||||
|
||||
```php
|
||||
public function extend(string $abstract, Closure $closure): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
扩展一个类或接口
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
| closure | Closure | 扩展闭包 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
|
||||
|
||||
## getLogPrefix
|
||||
|
||||
```php
|
||||
public function getLogPrefix(): string
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取日志前缀
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| string | |
|
||||
|
||||
|
||||
## setLogPrefix
|
||||
|
||||
```php
|
||||
public function setLogPrefix(string $prefix): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
设置日志前缀
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| prefix | string | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
|
||||
|
||||
## getExtenders
|
||||
|
||||
```php
|
||||
public function getExtenders(string $abstract): Closure[]
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取对应类型的所有扩展器
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| Closure[] | |
|
||||
|
||||
|
||||
## isAlias
|
||||
|
||||
```php
|
||||
public function isAlias(string $name): bool
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
判断传入的是否为别名
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| name | string | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| bool | |
|
||||
|
||||
|
||||
## dropStaleInstances
|
||||
|
||||
```php
|
||||
public function dropStaleInstances(string $abstract): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
抛弃所有过时的实例和别名
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
|
||||
|
||||
## getClosure
|
||||
|
||||
```php
|
||||
public function getClosure(string $abstract, string $concrete): Closure
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取一个解析对应类的闭包
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
| concrete | string | 实际类名 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| Closure | |
|
||||
|
||||
|
||||
## getLastParameterOverride
|
||||
|
||||
```php
|
||||
public function getLastParameterOverride(): array
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取最后一次的覆盖参数
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| array | |
|
||||
|
||||
|
||||
## notInstantiable
|
||||
|
||||
```php
|
||||
public function notInstantiable(string $concrete, string $reason): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
抛出实例化异常
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| concrete | string | |
|
||||
| reason | string | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
|
||||
|
||||
## resolveDependencies
|
||||
|
||||
```php
|
||||
public function resolveDependencies(ReflectionParameter[] $dependencies): array
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
解析依赖
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| dependencies | ReflectionParameter[] | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| array | |
|
||||
|
||||
|
||||
## hasParameterOverride
|
||||
|
||||
```php
|
||||
public function hasParameterOverride(ReflectionParameter $parameter): bool
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
判断传入的参数是否存在覆盖参数
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| parameter | ReflectionParameter | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| bool | |
|
||||
|
||||
|
||||
## getParameterOverride
|
||||
|
||||
```php
|
||||
public function getParameterOverride(ReflectionParameter $parameter): mixed
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取覆盖参数
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| parameter | ReflectionParameter | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| mixed | |
|
||||
|
||||
|
||||
## hasParameterTypeOverride
|
||||
|
||||
```php
|
||||
public function hasParameterTypeOverride(ReflectionParameter $parameter): bool
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
判断传入的参数是否存在临时注入的参数
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| parameter | ReflectionParameter | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| bool | |
|
||||
|
||||
|
||||
## getParameterTypeOverride
|
||||
|
||||
```php
|
||||
public function getParameterTypeOverride(ReflectionParameter $parameter): mixed
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取临时注入的参数
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| parameter | ReflectionParameter | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| mixed | |
|
||||
|
||||
|
||||
## resolvePrimitive
|
||||
|
||||
```php
|
||||
public function resolvePrimitive(ReflectionParameter $parameter): mixed
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
解析基本类型
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| parameter | ReflectionParameter | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| mixed | 对应类型的默认值 |
|
||||
|
||||
|
||||
## resolveClass
|
||||
|
||||
```php
|
||||
public function resolveClass(ReflectionParameter $parameter): mixed
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
解析类
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| parameter | ReflectionParameter | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| mixed | |
|
||||
|
||||
|
||||
## getConcrete
|
||||
|
||||
```php
|
||||
public function getConcrete(string $abstract): Closure|string
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取类名的实际类型
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| Closure|string | |
|
||||
|
||||
|
||||
## isBuildable
|
||||
|
||||
```php
|
||||
public function isBuildable(mixed $concrete, string $abstract): bool
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
判断传入的实际类型是否可以构造
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| concrete | mixed | 实际类型 |
|
||||
| abstract | string | 类或接口名 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| bool | |
|
||||
|
||||
|
||||
## isShared
|
||||
|
||||
```php
|
||||
public function isShared(string $abstract): bool
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
判断传入的类型是否为共享实例
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| abstract | string | 类或接口名 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| bool | |
|
||||
|
||||
|
||||
## shouldLog
|
||||
|
||||
```php
|
||||
public function shouldLog(): bool
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
判断是否输出日志
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| bool | |
|
||||
|
||||
|
||||
## log
|
||||
|
||||
```php
|
||||
public function log(string $message): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
记录日志(自动附加容器日志前缀)
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| message | string | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
@@ -1 +1,24 @@
|
||||
# ZM\Event\SwooleEvent\OnMessage
|
||||
# ZM\Event\SwooleEvent\OnMessage
|
||||
|
||||
## registerRequestContainerBindings
|
||||
|
||||
```php
|
||||
public function registerRequestContainerBindings(Swoole\WebSocket\Frame $frame, ZM\ConnectionManager\ConnectionObject $conn): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
注册请求容器绑定
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| frame | Swoole\WebSocket\Frame | |
|
||||
| conn | ZM\ConnectionManager\ConnectionObject | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
|
||||
@@ -15,3 +15,26 @@ public function loadAnnotations(): mixed
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| mixed | |
|
||||
|
||||
|
||||
## registerWorkerContainerBindings
|
||||
|
||||
```php
|
||||
public function registerWorkerContainerBindings(Swoole\WebSocket\Server $server): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
注册进程容器绑定
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| server | Swoole\WebSocket\Server | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# ZM\Framework
|
||||
|
||||
## __construct
|
||||
|
||||
```php
|
||||
public function __construct(array $args, bool $instant_mode): mixed
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
创建一个新的框架实例
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| args | array | 运行参数 |
|
||||
| instant_mode | bool | 是否为单文件模式 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| mixed | |
|
||||
|
||||
|
||||
## saveProcessState
|
||||
|
||||
```php
|
||||
|
||||
@@ -1,17 +1,57 @@
|
||||
# ZM\Module\ModuleBase
|
||||
|
||||
## getModuleName
|
||||
## __construct
|
||||
|
||||
```php
|
||||
public function getModuleName(): mixed
|
||||
public function __construct(string $module_name): mixed
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
作者很懒,什么也没有说
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| module_name | string | 模块名称 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| mixed | |
|
||||
|
||||
|
||||
## getModuleName
|
||||
|
||||
```php
|
||||
public function getModuleName(): string
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取模块名称
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| string | |
|
||||
|
||||
|
||||
## getEvents
|
||||
|
||||
```php
|
||||
public function getEvents(): array
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取事件列表
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| array | |
|
||||
|
||||
@@ -137,15 +137,15 @@ public function dispatchAPIResponse(mixed $req): mixed
|
||||
## getInstance
|
||||
|
||||
```php
|
||||
public function getInstance(): self
|
||||
public function getInstance(): static
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
作者很懒,什么也没有说
|
||||
获取类实例
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| self | |
|
||||
| static | |
|
||||
|
||||
138
docs/api/ZM/Utils/CommandInfoUtil.md
Normal file
138
docs/api/ZM/Utils/CommandInfoUtil.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# ZM\Utils\CommandInfoUtil
|
||||
|
||||
## exists
|
||||
|
||||
```php
|
||||
public function exists(): bool
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
判断命令信息是否已生成并缓存
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| bool | |
|
||||
|
||||
|
||||
## get
|
||||
|
||||
```php
|
||||
public function get(): array
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取命令信息
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| array | |
|
||||
|
||||
|
||||
## regenerate
|
||||
|
||||
```php
|
||||
public function regenerate(): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
重新生成命令信息
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
|
||||
|
||||
## getHelp
|
||||
|
||||
```php
|
||||
public function getHelp(string $command_id, bool $simple): string
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取命令帮助
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| command_id | string | 命令ID,为 `class@method` 格式 |
|
||||
| simple | bool | 是否仅输出简易信息(只有命令触发条件和描述) |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| string | |
|
||||
|
||||
|
||||
## save
|
||||
|
||||
```php
|
||||
public function save(array $helps): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
缓存命令信息
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| helps | array | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
|
||||
|
||||
## generateCommandList
|
||||
|
||||
```php
|
||||
public function generateCommandList(): array
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
根据注解树生成命令信息(内部)
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| array | |
|
||||
|
||||
|
||||
## generateCommandArgumentList
|
||||
|
||||
```php
|
||||
public function generateCommandArgumentList(string $id): array
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
生成指定命令的参数列表
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| id | string | 命令 ID |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| array | |
|
||||
@@ -158,7 +158,7 @@ public function loadFromJson(string $filename): null|mixed
|
||||
## scanDirFiles
|
||||
|
||||
```php
|
||||
public function scanDirFiles(string $dir, bool $recursive, bool|string $relative): array|false
|
||||
public function scanDirFiles(string $dir, bool $recursive, bool|mixed|string $relative, bool $include_dir): array|false
|
||||
```
|
||||
|
||||
### 描述
|
||||
@@ -171,7 +171,8 @@ public function scanDirFiles(string $dir, bool $recursive, bool|string $relative
|
||||
| -------- | ---- | ----------- |
|
||||
| dir | string | 目录 |
|
||||
| recursive | bool | 是否递归扫描子目录 |
|
||||
| relative | bool|string | 是否返回相对目录,如果为true则返回相对目录,如果为false则返回绝对目录 |
|
||||
| relative | bool|mixed|string | 是否返回相对目录,如果为true则返回相对目录,如果为false则返回绝对目录 |
|
||||
| include_dir | bool | 非递归模式下,是否包含目录 |
|
||||
|
||||
### 返回
|
||||
|
||||
@@ -201,3 +202,26 @@ public function isRelativePath(string $path): bool
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| bool | 返回结果 |
|
||||
|
||||
|
||||
## createIfNotExists
|
||||
|
||||
```php
|
||||
public function createIfNotExists(string $path): void
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
创建目录(如果不存在)
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| path | string | 目录路径 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| void | |
|
||||
|
||||
@@ -194,18 +194,26 @@ public function arrayToStr(array $array): string
|
||||
| string | |
|
||||
|
||||
|
||||
## generateCommandHelp
|
||||
## checkArguments
|
||||
|
||||
```php
|
||||
public function generateCommandHelp(): array
|
||||
public function checkArguments(string $class, string $method, array $match): array
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
根据注解树生成命令列表、帮助
|
||||
作者很懒,什么也没有说
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| class | string | |
|
||||
| method | string | |
|
||||
| match | array | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| array | 帮助信息,每个元素对应一个命令的帮助信息,格式为:命令名(其他触发条件):命令描述 |
|
||||
| array | |
|
||||
|
||||
95
docs/api/ZM/Utils/ReflectionUtil.md
Normal file
95
docs/api/ZM/Utils/ReflectionUtil.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# ZM\Utils\ReflectionUtil
|
||||
|
||||
## getParameterClassName
|
||||
|
||||
```php
|
||||
public function getParameterClassName(ReflectionParameter $parameter): null|string
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取参数的类名(如有)
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| parameter | ReflectionParameter | 参数 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| null|string | 类名,如果参数不是类,返回 null |
|
||||
|
||||
|
||||
## variableToString
|
||||
|
||||
```php
|
||||
public function variableToString(mixed $var): string
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
将传入变量转换为字符串
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| var | mixed | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| string | |
|
||||
|
||||
|
||||
## isNonStaticMethod
|
||||
|
||||
```php
|
||||
public function isNonStaticMethod(callable|string $callback): bool
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
判断传入的回调是否为任意类的非静态方法
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| callback | callable|string | 回调 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| bool | |
|
||||
|
||||
|
||||
## getCallReflector
|
||||
|
||||
```php
|
||||
public function getCallReflector(callable|string $callback): ReflectionFunctionAbstract
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
获取传入的回调的反射实例
|
||||
如果传入的是类方法,则会返回 {@link ReflectionMethod} 实例
|
||||
否则将返回 {@link ReflectionFunction} 实例
|
||||
可传入实现了 __invoke 的类
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| callback | callable|string | 回调 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| ReflectionFunctionAbstract | |
|
||||
@@ -1,9 +1,55 @@
|
||||
# ZM\ZMServer
|
||||
|
||||
## getAppName
|
||||
## __construct
|
||||
|
||||
```php
|
||||
public function getAppName(): mixed
|
||||
public function __construct(string $app_name): mixed
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
作者很懒,什么也没有说
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| app_name | string | App名称 |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| mixed | |
|
||||
|
||||
|
||||
## addModule
|
||||
|
||||
```php
|
||||
public function addModule(mixed $module_class): mixed
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
作者很懒,什么也没有说
|
||||
|
||||
### 参数
|
||||
|
||||
| 名称 | 类型 | 描述 |
|
||||
| -------- | ---- | ----------- |
|
||||
| module_class | mixed | |
|
||||
|
||||
### 返回
|
||||
|
||||
| 类型 | 描述 |
|
||||
| ---- | ----------- |
|
||||
| mixed | |
|
||||
|
||||
|
||||
## run
|
||||
|
||||
```php
|
||||
public function run(): mixed
|
||||
```
|
||||
|
||||
### 描述
|
||||
|
||||
@@ -54,10 +54,12 @@ DataProvider::getDataFolder("TestModule"); // 例如返回 /root/zhamao-framewor
|
||||
|
||||
递归或非递归扫描目录,返回相对目录的文件列表或绝对目录的文件列表。(非常好用)
|
||||
|
||||
定义:`scanDirFiles($dir, $recursive = true, $relative = false)`
|
||||
定义:`scanDirFiles($dir, $recursive = true, $relative = false, $include_dir = false)`
|
||||
|
||||
`$dir` 为要扫描的目录,`$recursive` 为是否递归,`$relative` 为是否返回相对目录的文件列表。
|
||||
|
||||
`$include_dir` 只有在 `$recursive === false` 的情况才生效,当 `$include_dir` 为 `true` 时,返回的非递归文件列表会包含目录,否则只包含文件。
|
||||
|
||||
从给定的目录下开始遍历整个目录,如果将 `$recursive` 设置为 `true`,则会递归扫描子目录,否则将返回包含目录的文件列表。
|
||||
|
||||
如果将 `$relative` 设置为 `true`,则会返回文件列表的相对路径,否则返回绝对路径。
|
||||
|
||||
@@ -262,6 +262,22 @@
|
||||
|
||||
无。
|
||||
|
||||
## OnSave()
|
||||
|
||||
框架退出和每 15 分钟在 Worker #0 执行的代码。建议这里用来编写内存数据持久化的代码,如将 WorkerCache、内存全局变量存到文件。
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ------------------------------ |
|
||||
| 名称 | `@OnSetup` |
|
||||
| 触发前提 | 每 15 分钟或 Ctrl+C、`server:stop` 等方式退出框架时 |
|
||||
| 命名空间 | `ZM\Annotation\Swoole\OnSave` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无 |
|
||||
|
||||
### 注解参数
|
||||
|
||||
无。
|
||||
|
||||
## TerminalCommand()
|
||||
|
||||
添加一个远程终端的自定义命令。(2.4.0 版本起可用)
|
||||
@@ -430,4 +446,4 @@ public function onCrawl() {
|
||||
## 示例6(创建一个远程终端命令并调试框架)
|
||||
|
||||
> 开个坑,以后填。(__填坑标记__)
|
||||
>
|
||||
>
|
||||
|
||||
@@ -177,7 +177,13 @@ public function testUrl() {
|
||||
|
||||
如果想要设置允许请求控制器的 HTTP 请求方式,可以使用方法在控制器中的 `@RequestMapping` 注解配置 `method` 参数,可以是 `GET`,`POST`,`PUT`, `PATCH`,`DELETE`,`OPTIONS`,`HEAD` 中的一个或多个。
|
||||
|
||||
- 限定 HTTP 方法:`@RequestMapping(method="GET")`,`@RequestMapping(method={"GET","POST"})`
|
||||
- 限定 HTTP 方法:`@RequestMapping(request_method="GET")`,`@RequestMapping(request_method={"GET","POST"})`
|
||||
|
||||
::: warning 注意
|
||||
|
||||
此处是 `request_method`,而不是 `method`,因为炸毛框架的注解 `method` 是保留属性,写错会出现问题。
|
||||
|
||||
:::
|
||||
|
||||
## 静态文件服务器
|
||||
|
||||
|
||||
@@ -78,4 +78,5 @@
|
||||
| E00074 | 参数非法 | 检查调用的参数是否正常(此处可能有多处问题,请看具体调用栈炸掉的地方) |
|
||||
| E00075 | Cron表达式非法 | 检查 @Cron 中的表达式是否书写格式正确 |
|
||||
| E00076 | Cron检查间隔非法 | 检查 @Cron 中的检查间隔(毫秒)是否在1000-60000之间 |
|
||||
| E00077 | 输入了非法的消息匹配参数类型 | 检查传入 `@CommandArgument` 或 `checkArguments()` 方法有没有传入非法的 `type` 参数 |
|
||||
| E99999 | 未知错误 | |
|
||||
|
||||
@@ -6,21 +6,15 @@
|
||||
|
||||
## 一键下载静态 PHP 环境和框架脚手架
|
||||
|
||||
从 2.4.4 版本起,炸毛框架支持一键拉取一个静态的 PHP 运行时和脚手架,只需运行下面的脚本即可。(开发环境推荐此方法)
|
||||
从 2.4.4 版本起,炸毛框架支持一键拉取一个静态的 PHP 运行时和脚手架(如果本机内安装的 PHP 已符合要求,则不安装),只需运行下面的脚本即可。
|
||||
|
||||
```bash
|
||||
# 将会把 PHP、框架都安装在此目录下
|
||||
mkdir zhamao-app/ # 这里可以取自己的项目名字
|
||||
cd zhamao-app/
|
||||
bash -c "$(curl -fsSL https://api.zhamao.xin/go.sh)"
|
||||
bash <(curl -fsSL https://zhamao.xin/go.sh)
|
||||
|
||||
# 安装完成后的启动框架命令(2.5.0 版本后可省略掉 runtime/php 前缀)
|
||||
vendor/bin/start server
|
||||
|
||||
# 扩展用法:使用静态 PHP 版本的 Composer update
|
||||
runtime/composer update
|
||||
# 扩展用法:使用静态 PHP 运行别的 CLI 脚本
|
||||
runtime/php path/to/your/script.php
|
||||
cd zhamao-app
|
||||
./zhamao server
|
||||
```
|
||||
|
||||
> 有关静态 PHP 的多种用法(如 Composer),见 [进阶 - PHP 环境高级](/advanced/php-env)
|
||||
@@ -80,3 +74,15 @@ $ vendor/bin/start server
|
||||
|
||||
## 进阶环境部署和开发
|
||||
炸毛框架还支持更多种启动方式,如源码模式、守护进程模式,具体后续有关环境和部署的进阶教程,请查看 [进阶开发](/advanced/) 部分!
|
||||
|
||||
## Windows 注意事项
|
||||
|
||||
由于 Swoole 扩展目前无法原生支持 Windows 环境的 PHP,所以以上方式都是默认在 Linux、macOS 系统下的命令。
|
||||
|
||||
如果需要在 Windows 上开发和运行,可以使用 WSL(1 和 2 均可)、Linux 虚拟机、Docker 或 cygwin。
|
||||
|
||||
如果使用 WSL、虚拟机或 Docker,方式可以直接参考上方相关命令。如果使用 cygwin,可先从 [Swoole 官方仓库](https://github.com/swoole/swoole-src) 下载 swoole 的 cygwin 构建版本,然后下载 Composer 后安装依赖,直接运行框架即可。
|
||||
|
||||
## macOS 注意事项
|
||||
|
||||
macOS 理论上运行环境和 Linux 无异,但 macOS 由于不能静态编译,所以不能使用静态编译的 PHP 直接运行,需自行从 `Homebrew` 下载最新版 PHP(命令 `brew install php`),然后使用命令 `pecl install swoole` 来安装 Swoole 后运行框架。
|
||||
|
||||
@@ -4,6 +4,90 @@
|
||||
|
||||
同时此处将只使用 build 版本号进行区分。
|
||||
|
||||
## build 478 (2022-8-7)
|
||||
|
||||
- 修复 `zm_sleep()` 无法传入小数的 Bug (#141)
|
||||
|
||||
## build 477 (2022-8-5)
|
||||
|
||||
- 修复了 `@CQNotice`、`@CQRequest` 注解无法正常激活的 Bug (#140)
|
||||
|
||||
## build 476 (2022-8-3)
|
||||
|
||||
- `DataProvider::scanDirFiles()` 新增参数 `$include_dir`,用于控制非递归模式下是否包含目录
|
||||
|
||||
## build 475 (2022-7-3)
|
||||
|
||||
- 修复 `match_args` 全局方法对于 `0` 字符串处理的 Bug(#136)
|
||||
|
||||
## build 474 (2022-5-21)
|
||||
|
||||
- 修复 WebSocket 连接时报错的 Bug
|
||||
|
||||
## build 473 (2022-5-7)
|
||||
|
||||
- 修复 `server:stop` 命令下部分情况报错的问题
|
||||
|
||||
## build 472 (2022-5-6)
|
||||
|
||||
- 修复 Container 环境继承全局变量的问题
|
||||
|
||||
## build 471 (2022-5-5)
|
||||
|
||||
- 修复 `CQ::encode()` 无法传入 `int` 的强类型解析问题(#113)
|
||||
- (内部)重构 CQ 类
|
||||
- 新增启动命令参数 `--audit-mode`,用于单次审计模式
|
||||
- 修复 `EventMapIterator` 对 PHP 8.1 的兼容性问题
|
||||
- 修复 #95 中提到的无输入流时报错的问题
|
||||
- 新增部分不可执行脚本的防呆退出功能
|
||||
- 修复 `ZMServer` 中的 typo
|
||||
|
||||
## build 470 (2022-5-4)
|
||||
|
||||
- 重构帮助生成器,将帮助生成器重构为 `CommandInfoUtil` 类
|
||||
|
||||
## build 469 (2022-5-3)
|
||||
|
||||
- 新增 `@CommandArgument` 注解,可直接通过注解添加聊天机器人命令参数
|
||||
- 修改默认 Hello 模块下随机数功能为采用 `@CommandArgument` 注解模式
|
||||
- (内部)新增 `EventManager::$event_map`,用于补充对事件对象遍历的方式
|
||||
- 新增 `EventMapIterator` 类,用于遍历注解事件对象
|
||||
- 新增 `MessageUtil::checkArguments()` 方法,用于检查 `@CommandArgument` 注解
|
||||
|
||||
## build 468 (2022-4-30)
|
||||
|
||||
- 优化单元测试流程
|
||||
- 优化上下文对象,在非协程环境下不再会抛出异常或返回 null
|
||||
|
||||
## build 467 (2022-4-29)
|
||||
|
||||
- 优化 `@RequestMapping` 注解事件的方法返回值处理,支持数组和字符串(数组自动转为 JSON 格式)
|
||||
|
||||
## build 466 (2022-4-29)
|
||||
|
||||
- 优化容器支持无名顺序参数的调用
|
||||
- 优化静态路由,支持 64 以上长度的路由
|
||||
|
||||
## build 464 (2022-4-28)
|
||||
|
||||
- 重构全局方法 `match_pattern()`,优化性能以及解决部分字符串不能匹配的 Bug
|
||||
|
||||
## build 463 (2022-4-16)
|
||||
|
||||
- 新增链式调用全局方法 `chain()`
|
||||
- 新增函数执行时间工具全局函数 `stopwatch()`
|
||||
|
||||
## build 462 (2022-4-15)
|
||||
|
||||
- 新增依赖注入、容器支持,目前对 Swoole 事件、机器人事件均支持使用依赖注入
|
||||
- 新增全局容器方法 `container()`、`resolve()`、`app()`,用于获取容器参数等
|
||||
- 新增相关容器测试
|
||||
|
||||
## build 461 (2022-4-11)
|
||||
|
||||
- 新增 AllBotsProxy、AllGroupsProxy 代理类,支持批量发送机器人动作
|
||||
- 新增全局函数 `implode_when_necessary()`,用于将可能为数组的参数转换为字符串
|
||||
|
||||
## build 460 (2022-4-3)
|
||||
|
||||
- 优化代码到 phpstan-level-4
|
||||
|
||||
@@ -1,5 +1,63 @@
|
||||
# 更新日志(v2 版本)
|
||||
|
||||
## v2.8.4(build 478)
|
||||
|
||||
> 更新时间:2022.8.7
|
||||
|
||||
- 修复 `zm_sleep()` 无法传入小数的 Bug (#141)
|
||||
|
||||
## v2.8.3(build 477)
|
||||
|
||||
> 更新时间:2022.8.5
|
||||
|
||||
- 修复了 `@CQNotice`、`@CQRequest` 注解无法正常激活的 Bug (#140)
|
||||
|
||||
## v2.8.2(build 476)
|
||||
|
||||
> 更新时间:2022.8.3
|
||||
|
||||
- `DataProvider::scanDirFiles()` 新增参数 `$include_dir`,用于控制非递归模式下是否包含目录
|
||||
|
||||
## v2.8.1(build 475)
|
||||
|
||||
> 更新时间:2022.7.3
|
||||
|
||||
- 修复 WebSocket 连接时报错的 Bug
|
||||
- 修复 `match_args` 全局方法对于 `0` 字符串处理的 Bug(#136)
|
||||
|
||||
## v2.8.0(build 473)
|
||||
|
||||
> 更新时间:2022.5.7
|
||||
|
||||
- 新增 AllBotsProxy、AllGroupsProxy 代理类,支持批量发送机器人动作
|
||||
- 新增全局函数 `implode_when_necessary()`,用于将可能为数组的参数转换为字符串
|
||||
- 新增依赖注入、容器支持,目前对 Swoole 事件、机器人事件均支持使用依赖注入
|
||||
- 新增全局容器方法 `container()`、`resolve()`、`app()`,用于获取容器参数等
|
||||
- 新增相关容器测试
|
||||
- 新增链式调用全局方法 `chain()`
|
||||
- 新增函数执行时间工具全局函数 `stopwatch()`
|
||||
- 新增 `@CommandArgument` 注解,可直接通过注解添加聊天机器人命令参数
|
||||
- (内部)新增 `EventManager::$event_map`,用于补充对事件对象遍历的方式
|
||||
- 新增 `EventMapIterator` 类,用于遍历注解事件对象
|
||||
- 新增 `MessageUtil::checkArguments()` 方法,用于检查 `@CommandArgument` 注解
|
||||
- 新增启动命令参数 `--audit-mode`,用于单次审计模式
|
||||
- 新增部分不可执行脚本的防呆退出功能
|
||||
- 重构全局方法 `match_pattern()`,优化性能以及解决部分字符串不能匹配的 Bug
|
||||
- 优化容器支持无名顺序参数的调用
|
||||
- 优化静态路由,支持 64 以上长度的路由
|
||||
- 优化 `@RequestMapping` 注解事件的方法返回值处理,支持数组和字符串(数组自动转为 JSON 格式)
|
||||
- 优化单元测试流程
|
||||
- 优化上下文对象,在非协程环境下不再会抛出异常或返回 null
|
||||
- 修改默认 Hello 模块下随机数功能为采用 `@CommandArgument` 注解模式
|
||||
- 重构帮助生成器,将帮助生成器重构为 `CommandInfoUtil` 类
|
||||
- 修复 `CQ::encode()` 无法传入 `int` 的强类型解析问题(#113)
|
||||
- (内部)重构 CQ 类
|
||||
- 修复 `EventMapIterator` 对 PHP 8.1 的兼容性问题
|
||||
- 修复 #95 中提到的无输入流时报错的问题
|
||||
- 修复 `ZMServer` 中的 typo
|
||||
- 修复 Container 环境继承全局变量的问题
|
||||
- 修复 `server:stop` 命令下部分情况报错的问题
|
||||
|
||||
## v2.7.6(build 460)
|
||||
|
||||
> 更新时间:2022.4.3
|
||||
|
||||
264
ext/go.sh
Executable file
264
ext/go.sh
Executable file
@@ -0,0 +1,264 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# 支持的环境变量:
|
||||
# ZM_DOWN_PHP_VERSION php版本,默认为8.0
|
||||
# ZM_NO_LOCAL_PHP 如果填入任意内容,则不检查本地PHP,直接安装内建PHP(仅限Linux)
|
||||
# ZM_TEMP_DIR 脚本下载内建PHP和Composer的临时目录,默认为/tmp/.zm-runtime
|
||||
# ZM_CUSTOM_DIR 自定义新建目录名称,默认为zhamao-app
|
||||
# ZM_COMPOSER_PACKAGIST 是否使用Composer的国外源,默认使用国内阿里云,如果设置并填写了内容,则自动使用Composer的国外源
|
||||
|
||||
ZM_PWD=$(pwd)
|
||||
_cyan="\033[0;36m"
|
||||
_reset="\033[0m"
|
||||
|
||||
# 彩头
|
||||
function nhead() {
|
||||
if [ "$1" = "red" ]; then
|
||||
echo -ne "\033[0;31m[!]"
|
||||
elif [ "$1" = "yellow" ]; then
|
||||
echo -ne "\033[0;33m[?]"
|
||||
else
|
||||
echo -ne "\033[0;32m[*]"
|
||||
fi
|
||||
echo -e "\033[0m"
|
||||
}
|
||||
|
||||
# 下载文件 $1 到目录 $2,自动选择使用 curl 或者 wget
|
||||
function download_file() {
|
||||
downloader="wget"
|
||||
type wget >/dev/null 2>&1 || { downloader="curl"; }
|
||||
if [ "$downloader" = "wget" ]; then
|
||||
_down_prefix="O"
|
||||
else
|
||||
_down_prefix="o"
|
||||
fi
|
||||
_down_symbol=0
|
||||
if [ ! -f "$2" ]; then
|
||||
echo -ne "$(nhead) 正在下载 $1 ... "
|
||||
$downloader "$1" -$_down_prefix "$2" >/dev/null 2>&1 && echo "完成!" && _down_symbol=1
|
||||
else
|
||||
echo "已存在!" && _down_symbol=1
|
||||
fi
|
||||
if [ $_down_symbol == 0 ]; then
|
||||
echo "$(nhead red) 下载失败!请检查网络连接!"
|
||||
rm -rf "$2"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# 安装下载内建PHP
|
||||
function install_native_php() {
|
||||
ZM_PHP_VERSION="8.1"
|
||||
if [ "$ZM_DOWN_PHP_VERSION" != "" ]; then
|
||||
ZM_PHP_VERSION="$ZM_DOWN_PHP_VERSION"
|
||||
fi
|
||||
echo "$(nhead) 使用的内建 PHP 版本: $ZM_PHP_VERSION"
|
||||
|
||||
rm -rf "$ZM_TEMP_DIR"
|
||||
mkdir "$ZM_TEMP_DIR" >/dev/null 2>&1
|
||||
if [ ! -f "$ZM_TEMP_DIR/php" ]; then
|
||||
download_file "https://dl.zhamao.xin/php-bin/down.php?php_ver=$ZM_PHP_VERSION&arch=$(uname -m)" "$ZM_TEMP_DIR/php.tgz" || return 1
|
||||
tar -xf "$ZM_TEMP_DIR/php.tgz" -C "$ZM_TEMP_DIR/" && rm -rf "$ZM_TEMP_DIR/php.tgz"
|
||||
fi
|
||||
echo "$(nhead) 安装内建 PHP 完成!"
|
||||
php_executable="$ZM_TEMP_DIR/php"
|
||||
return 0
|
||||
}
|
||||
|
||||
# 安装下载Composer
|
||||
function install_native_composer() {
|
||||
if [ ! -f "$ZM_TEMP_DIR/composer.phar" ]; then
|
||||
# 下载 composer.phar
|
||||
download_file "https://mirrors.aliyun.com/composer/composer.phar" "$ZM_TEMP_DIR/composer.phar" || return 1
|
||||
if [ "$php_executable" = "$ZM_TEMP_DIR/php" ]; then
|
||||
# shellcheck disable=SC2016
|
||||
txt='#!/usr/bin/env sh
|
||||
if [ -f "$(dirname $0)/php" ]; then
|
||||
"$(dirname $0)/php" "$(dirname $0)/composer.phar" $*
|
||||
else
|
||||
php "$(dirname $0)/composer.phar" $*
|
||||
fi'
|
||||
echo "$txt" >"$ZM_TEMP_DIR/composer"
|
||||
chmod +x "$ZM_TEMP_DIR/composer"
|
||||
else
|
||||
mv "$ZM_TEMP_DIR/composer.phar" "$ZM_TEMP_DIR/composer" && chmod +x "$ZM_TEMP_DIR/composer"
|
||||
fi
|
||||
fi
|
||||
echo "$(nhead) 安装内建 Composer 完成!"
|
||||
composer_executable="$ZM_TEMP_DIR/composer"
|
||||
return 0
|
||||
}
|
||||
|
||||
# 检查Composer可用性
|
||||
function composer_check() {
|
||||
# 顺带检查一下
|
||||
echo "$(nhead) 正在检查 Git、unzip、7z 能否正常使用 ... "
|
||||
type git >/dev/null 2>&1 || {
|
||||
echo "$(nhead red) 检测到系统不存在 git 命令,可能无法正常使用 Composer 下载 GitHub 等仓库项目!"
|
||||
}
|
||||
zip_check_symbol=0
|
||||
if type unzip >/dev/null 2>&1; then
|
||||
zip_check_symbol=1
|
||||
fi
|
||||
if type 7z >/dev/null 2>&1; then
|
||||
zip_check_symbol=1
|
||||
fi
|
||||
if [ $zip_check_symbol -eq 0 ]; then
|
||||
if [ "$($php_executable -m | grep zip)" = "" ]; then
|
||||
echo "$(nhead red) 检测到系统不存在 unzip 或 7z 命令,PHP 不存在 zip 扩展,可能无法正常使用 Composer 下载压缩包项目!"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 测试 Composer 和 PHP 是否能正常使用
|
||||
if [ "$("$composer_executable" -n about | grep Manager)" = "" ]; then
|
||||
echo "$(nhead red) Download PHP binary and composer failed!"
|
||||
return 1
|
||||
fi
|
||||
echo "$(nhead) 环境检查完成!"
|
||||
return 0
|
||||
}
|
||||
|
||||
# 环境检查
|
||||
function darwin_env_check() {
|
||||
echo -ne "$(nhead) 检查是否存在 PHP ... "
|
||||
if type php >/dev/null 2>&1; then
|
||||
php_executable=$(which php)
|
||||
echo "位置:$php_executable"
|
||||
if [ "$($php_executable -m | grep swoole)" = "" ]; then
|
||||
echo "$(nhead red) PHP 不存在 swoole 扩展,可能无法正常使用 Swoole 框架!" && return 1
|
||||
fi
|
||||
else
|
||||
echo "不存在"
|
||||
if type brew >/dev/null 2>&1; then
|
||||
echo -n "$(nhead yellow) 是否使用 Homebrew 安装 PHP?[y/N] "
|
||||
read -r y
|
||||
if [ "$y" = "" ]; then y="N"; fi
|
||||
if [ "$y" = "y" ]; then
|
||||
brew install php || echo "$(nhead red) 安装 PHP 失败!" && return 1
|
||||
else
|
||||
echo "$(nhead red) 跳过安装 PHP!" && return 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo -ne "$(nhead) 检查是否存在 Composer ... "
|
||||
if type composer >/dev/null 2>&1; then
|
||||
composer_executable=$(which composer)
|
||||
echo "位置:$composer_executable"
|
||||
else
|
||||
echo "不存在,正在下载 Composer ..."
|
||||
install_native_composer || return 1
|
||||
fi
|
||||
|
||||
composer_check || return 1
|
||||
}
|
||||
|
||||
# 询问是否安装 native php
|
||||
function prompt_install_native_php() {
|
||||
echo -ne "$(nhead yellow) 检测到系统的 PHP 不存在 swoole 扩展,是否下载安装独立的内建 PHP 和 Composer?[Y/n] "
|
||||
read -r y
|
||||
case $y in
|
||||
Y|y|"") return 0 ;;
|
||||
*) echo "$(nhead red) 跳过安装内建 PHP!" && return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# 环境检查
|
||||
function linux_env_check() {
|
||||
if [ "$ZM_NO_LOCAL_PHP" != "" ]; then # 如果指定了不使用本地 php,则不检查,直接下载
|
||||
install_native_php && install_native_composer && composer_check && return 0 || return 1
|
||||
else
|
||||
echo -ne "$(nhead) 检查是否存在 PHP ... "
|
||||
if type php >/dev/null 2>&1; then
|
||||
php_executable=$(which php)
|
||||
echo "位置:$php_executable"
|
||||
if [ "$($php_executable -m | grep swoole)" = "" ]; then
|
||||
echo "$(nhead red) PHP 不存在 swoole 扩展,可能无法正常使用 Swoole 框架!" && \
|
||||
prompt_install_native_php && \
|
||||
install_native_php && \
|
||||
install_native_composer && composer_check && return 0 || return 1
|
||||
fi
|
||||
else
|
||||
echo "不存在,将下载内建 PHP"
|
||||
install_native_php || return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo -ne "$(nhead) 检查是否存在 Composer ... "
|
||||
if type composer >/dev/null 2>&1; then
|
||||
composer_executable=$(which composer)
|
||||
echo "位置:$composer_executable"
|
||||
else
|
||||
echo "不存在,将下载内建 Composer"
|
||||
install_native_composer || return 1
|
||||
fi
|
||||
|
||||
composer_check || return 1
|
||||
}
|
||||
|
||||
function if_use_aliyun() {
|
||||
if [ "$ZM_COMPOSER_PACKAGIST" = "" ]; then
|
||||
$composer_executable -n config repos.packagist composer https://mirrors.aliyun.com/composer
|
||||
fi
|
||||
}
|
||||
|
||||
function if_restore_native_runtime() {
|
||||
ZM_RUNTIME_DIR="$ZM_PWD/$ZM_CUSTOM_DIR/runtime/"
|
||||
if [ "$php_executable" = "$ZM_TEMP_DIR/php" ]; then
|
||||
echo "$(nhead) 移动内建 PHP 到框架目录 $ZM_RUNTIME_DIR ..." && \
|
||||
mkdir -p "$ZM_RUNTIME_DIR" && \
|
||||
mv "$ZM_TEMP_DIR/php" "$ZM_RUNTIME_DIR" || {
|
||||
echo "$(nhead red) 移动内建 PHP 到框架目录失败!" && return 1
|
||||
}
|
||||
php_executable="$ZM_RUNTIME_DIR/php"
|
||||
fi
|
||||
if [ "$composer_executable" = "$ZM_TEMP_DIR/composer" ]; then
|
||||
echo "$(nhead) 移动内建 Composer 到框架目录 $ZM_RUNTIME_DIR ..." && \
|
||||
mkdir -p "$ZM_CUSTOM_DIR/runtime" && \
|
||||
mv "$ZM_TEMP_DIR/composer" "$ZM_RUNTIME_DIR" && \
|
||||
mv "$ZM_TEMP_DIR/composer.phar" "$ZM_RUNTIME_DIR" || {
|
||||
echo "$(nhead red) 移动内建 Composer 到框架目录失败!" && return 1
|
||||
}
|
||||
composer_executable="$ZM_RUNTIME_DIR/composer"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
function install_framework() {
|
||||
echo "$(nhead) 开始安装框架到目录 $ZM_CUSTOM_DIR ..."
|
||||
mkdir -p "$ZM_PWD/$ZM_CUSTOM_DIR" && \
|
||||
cd "$ZM_PWD/$ZM_CUSTOM_DIR" && \
|
||||
$composer_executable init --name="zhamao/framework-starter" -n -q && \
|
||||
if_use_aliyun && \
|
||||
echo "$(nhead) 从 Composer 拉取框架 ..." && \
|
||||
$composer_executable require -n -q zhamao/framework:^2.7 && \
|
||||
$composer_executable require -n -q --dev swoole/ide-helper:^4.5 && \
|
||||
if_restore_native_runtime && \
|
||||
echo "$(nhead) 初始化框架脚手架文件 ..." && \
|
||||
vendor/bin/start init >/dev/null 2>&1 && \
|
||||
$composer_executable dump-autoload -n -q && \
|
||||
echo "$(nhead) 安装框架完成,已安装到目录 $ZM_CUSTOM_DIR" && \
|
||||
echo -e "$(nhead) 启动框架命令:""$_cyan""cd $ZM_CUSTOM_DIR && ./zhamao server""$_reset" || {
|
||||
echo "$(nhead red) 安装框架失败!" && return 1
|
||||
}
|
||||
}
|
||||
|
||||
# 环境变量设置
|
||||
test "$ZM_TEMP_DIR" = "" && ZM_TEMP_DIR="/tmp/.zm-runtime"
|
||||
test "$ZM_CUSTOM_DIR" = "" && ZM_CUSTOM_DIR="zhamao-app"
|
||||
|
||||
if [ -d "$ZM_PWD/$ZM_CUSTOM_DIR" ]; then
|
||||
echo "$(nhead red) 检测到目录 $ZM_CUSTOM_DIR/ 已安装过框架,请更换文件夹名称或删除旧文件夹再试!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查系统环境,目前只支持 Linux
|
||||
case $(uname -s) in
|
||||
Linux) linux_env_check || exit 1 ;;
|
||||
Darwin) darwin_env_check || exit 1 ;;
|
||||
*) echo "$(nhead red) Only support Linux and macOS!" && exit 1 ;;
|
||||
esac
|
||||
|
||||
# 安装框架
|
||||
install_framework
|
||||
@@ -6,5 +6,9 @@ parameters:
|
||||
ignoreErrors:
|
||||
- '#Used constant OS_TYPE_(LINUX|WINDOWS) not found#'
|
||||
- '#Constant .* not found#'
|
||||
- '#PHPDoc tag @throws with type Psr\\Container\\ContainerExceptionInterface is not subtype of Throwable#'
|
||||
- '#Unsafe usage of new static#'
|
||||
dynamicConstantNames:
|
||||
- SWOOLE_VERSION
|
||||
- ZM_TEST_LOG_DEBUG
|
||||
- _PHAR_STUB_ID
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Module\Example;
|
||||
|
||||
use ZM\Annotation\CQ\CommandArgument;
|
||||
use ZM\Annotation\CQ\CQBefore;
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
use ZM\Annotation\CQ\CQMessage;
|
||||
@@ -12,21 +13,25 @@ use ZM\Annotation\Http\RequestMapping;
|
||||
use ZM\Annotation\Swoole\OnCloseEvent;
|
||||
use ZM\Annotation\Swoole\OnOpenEvent;
|
||||
use ZM\Annotation\Swoole\OnRequestEvent;
|
||||
use ZM\Annotation\Swoole\OnStart;
|
||||
use ZM\API\CQ;
|
||||
use ZM\API\OneBotV11;
|
||||
use ZM\API\TuringAPI;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\ConnectionManager\ConnectionObject;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Context\Context;
|
||||
use ZM\Event\EventDispatcher;
|
||||
use ZM\Exception\InterruptException;
|
||||
use ZM\Module\QQBot;
|
||||
use ZM\Requests\ZMRequest;
|
||||
use ZM\Utils\CommandInfoUtil;
|
||||
use ZM\Utils\MessageUtil;
|
||||
use ZM\Utils\ZMUtil;
|
||||
|
||||
/**
|
||||
* Class Hello
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
class Hello
|
||||
@@ -134,18 +139,18 @@ class Hello
|
||||
* 问法2:从1到20的随机数
|
||||
* @CQCommand("随机数")
|
||||
* @CQCommand(pattern="*从*到*的随机数")
|
||||
* @CommandArgument(name="num1",type="int",required=true)
|
||||
* @CommandArgument(name="num2",type="int",required=true)
|
||||
* @param mixed $num1
|
||||
* @param mixed $num2
|
||||
* @return string
|
||||
*/
|
||||
public function randNum()
|
||||
public function randNum($num1, $num2)
|
||||
{
|
||||
// 获取第一个数字类型的参数
|
||||
$num1 = ctx()->getNumArg('请输入第一个数字');
|
||||
// 获取第二个数字类型的参数
|
||||
$num2 = ctx()->getNumArg('请输入第二个数字');
|
||||
$a = min(intval($num1), intval($num2));
|
||||
$b = max(intval($num1), intval($num2));
|
||||
$a = min($num1, $num2);
|
||||
$b = max($num1, $num2);
|
||||
// 回复用户结果
|
||||
return '随机数是:' . mt_rand($a, $b);
|
||||
return '从' . $a . '到' . $b . '的随机数是:' . mt_rand($a, $b);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,6 +176,7 @@ class Hello
|
||||
/**
|
||||
* 使用自定义参数的路由参数
|
||||
* @RequestMapping("/whoami/{name}")
|
||||
*
|
||||
* @param array $param 参数
|
||||
* @return string 返回的 HTML Body
|
||||
*/
|
||||
@@ -182,6 +188,7 @@ class Hello
|
||||
/**
|
||||
* 在机器人连接后向终端输出信息
|
||||
* @OnOpenEvent("qq")
|
||||
*
|
||||
* @param ConnectionObject $conn WebSocket 连接对象
|
||||
*/
|
||||
public function onConnect(ConnectionObject $conn)
|
||||
@@ -201,6 +208,7 @@ class Hello
|
||||
/**
|
||||
* 阻止 Chrome 自动请求 /favicon.ico 导致的多条请求并发和干扰
|
||||
* @OnRequestEvent(rule="ctx()->getRequest()->server['request_uri'] == '/favicon.ico'",level=200)
|
||||
*
|
||||
* @throws InterruptException
|
||||
*/
|
||||
public function onRequest()
|
||||
@@ -226,8 +234,33 @@ class Hello
|
||||
#[CQCommand('帮助')]
|
||||
public function help(): string
|
||||
{
|
||||
$helps = MessageUtil::generateCommandHelp();
|
||||
$util = resolve(CommandInfoUtil::class);
|
||||
$commands = $util->get();
|
||||
$helps = array_map(static function ($command) use ($util) {
|
||||
return $util->getHelp($command['id']);
|
||||
}, $commands);
|
||||
array_unshift($helps, '帮助:');
|
||||
return implode("\n", $helps);
|
||||
}
|
||||
|
||||
/**
|
||||
* @CQCommand("proxy")
|
||||
*/
|
||||
#[CQCommand('proxy')]
|
||||
public function proxy()
|
||||
{
|
||||
bot()->all()->allGroups()->sendGroupMsg(0, ctx()->getMessage());
|
||||
}
|
||||
|
||||
/*
|
||||
* 欢迎来到容器时代
|
||||
*
|
||||
* @param Context $context 通过依赖注入实现的
|
||||
*
|
||||
* @CQCommand("容器你好")
|
||||
*/
|
||||
public function welcomeToContainerAge(Context $context)
|
||||
{
|
||||
$context->reply('欢迎来到容器时代');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace ZM\API;
|
||||
|
||||
use Stringable;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Entity\CQObject;
|
||||
|
||||
@@ -16,11 +17,7 @@ class CQ
|
||||
*/
|
||||
public static function at($qq): string
|
||||
{
|
||||
if (is_numeric($qq) || $qq === 'all') {
|
||||
return '[CQ:at,qq=' . $qq . ']';
|
||||
}
|
||||
Console::warning(zm_internal_errcode('E00035') . "传入的QQ号码({$qq})错误!");
|
||||
return ' ';
|
||||
return self::buildCQ('at', ['qq' => $qq]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,11 +27,7 @@ class CQ
|
||||
*/
|
||||
public static function face($id): string
|
||||
{
|
||||
if (is_numeric($id)) {
|
||||
return '[CQ:face,id=' . $id . ']';
|
||||
}
|
||||
Console::warning(zm_internal_errcode('E00035') . "传入的face id({$id})错误!");
|
||||
return ' ';
|
||||
return self::buildCQ('face', ['id' => $id]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,13 +41,13 @@ class CQ
|
||||
*/
|
||||
public static function image(string $file, bool $cache = true, bool $flash = false, bool $proxy = true, int $timeout = -1): string
|
||||
{
|
||||
return
|
||||
'[CQ:image,file=' . self::encode($file, true) .
|
||||
(!$cache ? ',cache=0' : '') .
|
||||
($flash ? ',type=flash' : '') .
|
||||
(!$proxy ? ',proxy=false' : '') .
|
||||
($timeout != -1 ? (',timeout=' . $timeout) : '') .
|
||||
']';
|
||||
$optional_values = [
|
||||
'cache' => !$cache ? 'cache=0' : '',
|
||||
'flash' => $flash ? 'type=flash' : '',
|
||||
'proxy' => !$proxy ? 'proxy=false' : '',
|
||||
'timeout' => $timeout != -1 ? 'timeout=' . $timeout : '',
|
||||
];
|
||||
return self::buildCQ('image', ['file' => $file], $optional_values);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,13 +61,13 @@ class CQ
|
||||
*/
|
||||
public static function record(string $file, bool $magic = false, bool $cache = true, bool $proxy = true, int $timeout = -1): string
|
||||
{
|
||||
return
|
||||
'[CQ:record,file=' . self::encode($file, true) .
|
||||
($magic ? ',magic=1' : '') .
|
||||
(!$cache ? ',cache=0' : '') .
|
||||
(!$proxy ? ',proxy=false' : '') .
|
||||
($timeout != -1 ? (',timeout=' . $timeout) : '') .
|
||||
']';
|
||||
$optional_values = [
|
||||
'magic' => $magic ? 'magic=true' : '',
|
||||
'cache' => !$cache ? 'cache=0' : '',
|
||||
'proxy' => !$proxy ? 'proxy=false' : '',
|
||||
'timeout' => $timeout != -1 ? 'timeout=' . $timeout : '',
|
||||
];
|
||||
return self::buildCQ('record', ['file' => $file], $optional_values);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,12 +80,12 @@ class CQ
|
||||
*/
|
||||
public static function video(string $file, bool $cache = true, bool $proxy = true, int $timeout = -1): string
|
||||
{
|
||||
return
|
||||
'[CQ:video,file=' . self::encode($file, true) .
|
||||
(!$cache ? ',cache=0' : '') .
|
||||
(!$proxy ? ',proxy=false' : '') .
|
||||
($timeout != -1 ? (',timeout=' . $timeout) : '') .
|
||||
']';
|
||||
$optional_values = [
|
||||
'cache' => !$cache ? 'cache=0' : '',
|
||||
'proxy' => !$proxy ? 'proxy=false' : '',
|
||||
'timeout' => $timeout != -1 ? 'timeout=' . $timeout : '',
|
||||
];
|
||||
return self::buildCQ('video', ['file' => $file], $optional_values);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,7 +124,10 @@ class CQ
|
||||
*/
|
||||
public static function poke($type, $id, string $name = ''): string
|
||||
{
|
||||
return "[CQ:poke,type={$type},id={$id}" . ($name != '' ? (',name=' . self::encode($name, true)) : '') . ']';
|
||||
$optional_values = [
|
||||
'name' => $name ? 'name=' . $name : '',
|
||||
];
|
||||
return self::buildCQ('poke', ['type' => $type, 'id' => $id], $optional_values);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,7 +137,7 @@ class CQ
|
||||
*/
|
||||
public static function anonymous(int $ignore = 1): string
|
||||
{
|
||||
return '[CQ:anonymous' . ($ignore != 1 ? ',ignore=0' : '') . ']';
|
||||
return self::buildCQ('anonymous', [], ['ignore' => $ignore != 1 ? 'ignore=0' : '']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,28 +150,22 @@ class CQ
|
||||
*/
|
||||
public static function share(string $url, string $title, ?string $content = null, ?string $image = null): string
|
||||
{
|
||||
if ($content === null) {
|
||||
$c = '';
|
||||
} else {
|
||||
$c = ',content=' . self::encode($content, true);
|
||||
}
|
||||
if ($image === null) {
|
||||
$i = '';
|
||||
} else {
|
||||
$i = ',image=' . self::encode($image, true);
|
||||
}
|
||||
return '[CQ:share,url=' . self::encode($url, true) . ',title=' . self::encode($title, true) . $c . $i . ']';
|
||||
$optional_values = [
|
||||
'content' => $content ? 'content=' . self::encode($content, true) : '',
|
||||
'image' => $image ? 'image=' . self::encode($image, true) : '',
|
||||
];
|
||||
return self::buildCQ('share', ['url' => $url, 'title' => $title], $optional_values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送好友或群推荐名片
|
||||
* @param string $type 名片类型
|
||||
* @param int|string $type 名片类型
|
||||
* @param int|string $id 好友或群ID
|
||||
* @return string CQ码
|
||||
*/
|
||||
public static function contact(string $type, $id): string
|
||||
public static function contact($type, $id): string
|
||||
{
|
||||
return "[CQ:contact,type={$type},id={$id}]";
|
||||
return self::buildCQ('contact', ['type' => $type, 'id' => $id]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,12 +178,11 @@ class CQ
|
||||
*/
|
||||
public static function location($lat, $lon, string $title = '', string $content = ''): string
|
||||
{
|
||||
return '[CQ:location' .
|
||||
',lat=' . self::encode((string) $lat, true) .
|
||||
',lon=' . self::encode((string) $lon, true) .
|
||||
($title != '' ? (',title=' . self::encode($title, true)) : '') .
|
||||
($content != '' ? (',content=' . self::encode($content, true)) : '') .
|
||||
']';
|
||||
$optional_values = [
|
||||
'title' => $title ? 'title=' . self::encode($title, true) : '',
|
||||
'content' => $content ? 'content=' . self::encode($content, true) : '',
|
||||
];
|
||||
return self::buildCQ('location', ['lat' => $lat, 'lon' => $lon], $optional_values);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -215,26 +204,17 @@ class CQ
|
||||
case 'qq':
|
||||
case '163':
|
||||
case 'xiami':
|
||||
return "[CQ:music,type={$type},id={$id_or_url}]";
|
||||
return self::buildCQ('music', ['type' => $type, 'id' => $id_or_url]);
|
||||
case 'custom':
|
||||
if ($title === null || $audio === null) {
|
||||
Console::warning(zm_internal_errcode('E00035') . '传入CQ码实例的标题和音频链接不能为空!');
|
||||
return ' ';
|
||||
}
|
||||
if ($content === null) {
|
||||
$c = '';
|
||||
} else {
|
||||
$c = ',content=' . self::encode($content, true);
|
||||
}
|
||||
if ($image === null) {
|
||||
$i = '';
|
||||
} else {
|
||||
$i = ',image=' . self::encode($image, true);
|
||||
}
|
||||
return '[CQ:music,type=custom,url=' .
|
||||
self::encode($id_or_url, true) .
|
||||
',audio=' . self::encode($audio, true) . ',title=' . self::encode($title, true) . $c . $i .
|
||||
']';
|
||||
$optional_values = [
|
||||
'content' => $content ? 'content=' . self::encode($content, true) : '',
|
||||
'image' => $image ? 'image=' . self::encode($image, true) : '',
|
||||
];
|
||||
return self::buildCQ('music', ['type' => 'custom', 'url' => $id_or_url, 'audio' => $audio, 'title' => $title], $optional_values);
|
||||
default:
|
||||
Console::warning(zm_internal_errcode('E00035') . "传入的music type({$type})错误!");
|
||||
return ' ';
|
||||
@@ -248,7 +228,7 @@ class CQ
|
||||
*/
|
||||
public static function forward($id): string
|
||||
{
|
||||
return '[CQ:forward,id=' . self::encode((string) $id) . ']';
|
||||
return self::buildCQ('forward', ['id' => $id]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -256,15 +236,15 @@ class CQ
|
||||
* 特殊说明: 需要使用单独的API /send_group_forward_msg 发送, 并且由于消息段较为复杂, 仅支持Array形式入参。
|
||||
* 如果引用消息和自定义消息同时出现, 实际查看顺序将取消息段顺序。
|
||||
* 另外按 CQHTTP 文档说明, data 应全为字符串, 但由于需要接收message 类型的消息, 所以 仅限此Type的content字段 支持Array套娃
|
||||
* @deprecated 这个不推荐使用,因为 go-cqhttp 官方没有对其提供CQ码模式相关支持,仅支持Array模式发送
|
||||
* @param int|string $user_id 转发消息id
|
||||
* @param string $nickname 发送者显示名字
|
||||
* @param string $content 具体消息
|
||||
* @return string CQ码
|
||||
* @deprecated 这个不推荐使用,因为 go-cqhttp 官方没有对其提供CQ码模式相关支持,仅支持Array模式发送
|
||||
*/
|
||||
public static function node($user_id, string $nickname, string $content): string
|
||||
{
|
||||
return "[CQ:node,user_id={$user_id},nickname=" . self::encode($nickname, true) . ',content=' . self::encode($content, true) . ']';
|
||||
return self::buildCQ('node', ['user_id' => $user_id, 'nickname' => $nickname, 'content' => $content]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,7 +254,7 @@ class CQ
|
||||
*/
|
||||
public static function xml(string $data): string
|
||||
{
|
||||
return '[CQ:xml,data=' . self::encode($data, true) . ']';
|
||||
return self::buildCQ('xml', ['data' => $data]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -285,7 +265,7 @@ class CQ
|
||||
*/
|
||||
public static function json(string $data, int $resid = 0): string
|
||||
{
|
||||
return '[CQ:json,data=' . self::encode($data, true) . ',resid=' . $resid . ']';
|
||||
return self::buildCQ('json', ['data' => $data, 'resid' => $resid]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -296,23 +276,18 @@ class CQ
|
||||
*/
|
||||
public static function _custom(string $type_name, array $params): string
|
||||
{
|
||||
$code = '[CQ:' . $type_name;
|
||||
foreach ($params as $k => $v) {
|
||||
$code .= ',' . $k . '=' . self::escape($v, true);
|
||||
}
|
||||
$code .= ']';
|
||||
return $code;
|
||||
return self::buildCQ($type_name, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 反转义字符串中的CQ码敏感符号
|
||||
* @param string $msg 字符串
|
||||
* @param bool $is_content 如果是解码CQ码本体内容,则为false(默认),如果是参数内的字符串,则为true
|
||||
* @return string 转义后的CQ码
|
||||
* @param int|string|Stringable $msg 字符串
|
||||
* @param bool $is_content 如果是解码CQ码本体内容,则为false(默认),如果是参数内的字符串,则为true
|
||||
* @return string 转义后的CQ码
|
||||
*/
|
||||
public static function decode(string $msg, bool $is_content = false): string
|
||||
public static function decode($msg, bool $is_content = false): string
|
||||
{
|
||||
$msg = str_replace(['&', '[', ']'], ['&', '[', ']'], $msg);
|
||||
$msg = str_replace(['&', '[', ']'], ['&', '[', ']'], (string) $msg);
|
||||
if ($is_content) {
|
||||
$msg = str_replace(',', ',', $msg);
|
||||
}
|
||||
@@ -321,24 +296,24 @@ class CQ
|
||||
|
||||
/**
|
||||
* 简单反转义替换CQ码的方括号
|
||||
* @param string $str 字符串
|
||||
* @return string 字符串
|
||||
* @param int|string|Stringable $str 字符串
|
||||
* @return string 字符串
|
||||
*/
|
||||
public static function replace(string $str): string
|
||||
public static function replace($str): string
|
||||
{
|
||||
$str = str_replace('{{', '[', $str);
|
||||
$str = str_replace('{{', '[', (string) $str);
|
||||
return str_replace('}}', ']', $str);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转义CQ码的特殊字符,同encode
|
||||
* @param string $msg 字符串
|
||||
* @param bool $is_content 如果是转义CQ码本体内容,则为false(默认),如果是参数内的字符串,则为true
|
||||
* @return string 转义后的CQ码
|
||||
* @param int|string|Stringable $msg 字符串
|
||||
* @param bool $is_content 如果是转义CQ码本体内容,则为false(默认),如果是参数内的字符串,则为true
|
||||
* @return string 转义后的CQ码
|
||||
*/
|
||||
public static function escape(string $msg, bool $is_content = false): string
|
||||
public static function escape($msg, bool $is_content = false): string
|
||||
{
|
||||
$msg = str_replace(['&', '[', ']'], ['&', '[', ']'], $msg);
|
||||
$msg = str_replace(['&', '[', ']'], ['&', '[', ']'], (string) $msg);
|
||||
if ($is_content) {
|
||||
$msg = str_replace(',', ',', $msg);
|
||||
}
|
||||
@@ -347,13 +322,13 @@ class CQ
|
||||
|
||||
/**
|
||||
* 转义CQ码的特殊字符
|
||||
* @param string $msg 字符串
|
||||
* @param bool $is_content 如果是转义CQ码本体内容,则为false(默认),如果是参数内的字符串,则为true
|
||||
* @return string 转义后的CQ码
|
||||
* @param int|string|Stringable $msg 字符串
|
||||
* @param bool $is_content 如果是转义CQ码本体内容,则为false(默认),如果是参数内的字符串,则为true
|
||||
* @return string 转义后的CQ码
|
||||
*/
|
||||
public static function encode(string $msg, bool $is_content = false): string
|
||||
public static function encode($msg, bool $is_content = false): string
|
||||
{
|
||||
$msg = str_replace(['&', '[', ']'], ['&', '[', ']'], $msg);
|
||||
$msg = str_replace(['&', '[', ']'], ['&', '[', ']'], (string) $msg);
|
||||
if ($is_content) {
|
||||
$msg = str_replace(',', ',', $msg);
|
||||
}
|
||||
@@ -416,7 +391,7 @@ class CQ
|
||||
{
|
||||
$cqs = [];
|
||||
$offset = 0;
|
||||
while (($head = mb_strpos(($submsg = mb_substr($msg, $offset)), '[CQ:')) !== false) {
|
||||
while (($head = mb_strpos($submsg = mb_substr($msg, $offset), '[CQ:')) !== false) {
|
||||
$key_offset = mb_substr($submsg, $head);
|
||||
$tmpmsg = mb_strpos($key_offset, ']');
|
||||
if ($tmpmsg === false) {
|
||||
@@ -438,4 +413,22 @@ class CQ
|
||||
}
|
||||
return $cqs;
|
||||
}
|
||||
|
||||
private static function buildCQ(string $cq, array $array, array $optional_values = []): string
|
||||
{
|
||||
$str = '[CQ:' . $cq;
|
||||
foreach ($array as $k => $v) {
|
||||
if ($v === null) {
|
||||
Console::warning('param ' . $k . ' cannot be set with null, empty CQ will returned!');
|
||||
return ' ';
|
||||
}
|
||||
$str .= ',' . $k . '=' . self::encode($v);
|
||||
}
|
||||
foreach ($optional_values as $v) {
|
||||
if ($v !== '') {
|
||||
$str .= ',' . $v;
|
||||
}
|
||||
}
|
||||
return $str . ']';
|
||||
}
|
||||
}
|
||||
|
||||
74
src/ZM/API/Proxies/Bot/AbstractBotProxy.php
Normal file
74
src/ZM/API/Proxies/Bot/AbstractBotProxy.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\API\Proxies\Bot;
|
||||
|
||||
use ZM\API\ZMRobot;
|
||||
|
||||
/**
|
||||
* @mixin ZMRobot
|
||||
*/
|
||||
abstract class AbstractBotProxy
|
||||
{
|
||||
/**
|
||||
* 传入的机器人实例
|
||||
*
|
||||
* @var ZMRobot
|
||||
*/
|
||||
protected $bot;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param AbstractBotProxy|ZMRobot $bot 调用此代理的机器人实例
|
||||
*/
|
||||
public function __construct($bot)
|
||||
{
|
||||
$this->bot = $bot;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在传入的机器人实例上调用方法
|
||||
*
|
||||
* @param string $name 方法名
|
||||
* @param array $arguments 参数
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call(string $name, array $arguments)
|
||||
{
|
||||
return $this->bot->{$name}(...$arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取传入的机器人实例的属性
|
||||
*
|
||||
* @param string $name 属性名
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get(string $name)
|
||||
{
|
||||
return $this->bot->{$name};
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置传入的机器人实例的属性
|
||||
*
|
||||
* @param string $name 属性名
|
||||
* @param mixed $value 属性值
|
||||
*/
|
||||
public function __set(string $name, $value)
|
||||
{
|
||||
$this->bot->{$name} = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断传入的机器人实例的属性是否存在
|
||||
*
|
||||
* @param string $name 属性名
|
||||
*/
|
||||
public function __isset(string $name): bool
|
||||
{
|
||||
return isset($this->bot->{$name});
|
||||
}
|
||||
}
|
||||
43
src/ZM/API/Proxies/Bot/AllBotsProxy.php
Normal file
43
src/ZM/API/Proxies/Bot/AllBotsProxy.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\API\Proxies\Bot;
|
||||
|
||||
use ReflectionException;
|
||||
use ReflectionMethod;
|
||||
use ReflectionNamedType;
|
||||
use ZM\API\ZMRobot;
|
||||
use ZM\Console\Console;
|
||||
|
||||
class AllBotsProxy extends AbstractBotProxy
|
||||
{
|
||||
/**
|
||||
* 在所有机器人实例上调用方法
|
||||
*
|
||||
* @param string $name 方法名
|
||||
* @param array $arguments 参数
|
||||
* @throws ReflectionException
|
||||
* @return array<mixed> 返回一个包含所有执行结果的数组,键名为机器人ID
|
||||
*/
|
||||
public function __call(string $name, array $arguments)
|
||||
{
|
||||
// 如果调用的方法为代理方法,则传入当前代理作为参数
|
||||
// 一般此情况代表用户进行嵌套代理,即 `->all()->allGroups()` 等情况
|
||||
$reflection = new ReflectionMethod(ZMRobot::class, $name);
|
||||
if (($return = $reflection->getReturnType()) && $return instanceof ReflectionNamedType && str_contains($return->getName(), 'Proxy')) {
|
||||
Console::debug("Trying to construct proxy {$name} inside proxy, returning nested proxy.");
|
||||
// 插入当前代理作为第一个参数
|
||||
array_unshift($arguments, $this);
|
||||
return $this->bot->{$name}(...$arguments);
|
||||
}
|
||||
|
||||
$result = [];
|
||||
// 遍历所有机器人实例
|
||||
foreach ($this->bot::getAllRobot() as $bot) {
|
||||
Console::debug("Calling {$name} on bot {$bot->getSelfId()}.");
|
||||
$result[$bot->getSelfId()] = $bot->{$name}(...$arguments);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
43
src/ZM/API/Proxies/Bot/AllGroupsProxy.php
Normal file
43
src/ZM/API/Proxies/Bot/AllGroupsProxy.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\API\Proxies\Bot;
|
||||
|
||||
use ReflectionException;
|
||||
use ZM\API\ZMRobot;
|
||||
use ZM\Console\Console;
|
||||
|
||||
class AllGroupsProxy extends AbstractBotProxy
|
||||
{
|
||||
/**
|
||||
* 在传入的机器人实例上调用方法
|
||||
*
|
||||
* @param string $name 方法名
|
||||
* @param array $arguments 参数
|
||||
* @throws ReflectionException
|
||||
* @return array<mixed> 返回一个包含所有执行结果的数组,键名为群号
|
||||
*/
|
||||
public function __call(string $name, array $arguments)
|
||||
{
|
||||
// 如果调用的方法并非群组方法,则直接返回输出
|
||||
// 因为目前所有群组方法都是以 `group_id` 作为第一个参数,故以此来判断
|
||||
$reflection = new \ReflectionMethod(ZMRobot::class, $name);
|
||||
if (!$reflection->getNumberOfParameters() || $reflection->getParameters()[0]->getName() !== 'group_id') {
|
||||
Console::warning("Trying to call non-group method {$name} on AllGroupsProxy, skipped.");
|
||||
return $this->bot->{$name}(...$arguments);
|
||||
}
|
||||
|
||||
$result = [];
|
||||
// 获取并遍历所有群组
|
||||
$groups = $this->bot->getGroupList()['data'];
|
||||
foreach ($groups as $group) {
|
||||
$arguments[0] = $group['group_id'];
|
||||
$bot_id = implode_when_necessary($this->bot->getSelfId());
|
||||
Console::debug("Calling {$name} on group {$group['group_id']} on bot {$bot_id}.");
|
||||
// 在群组上调用方法
|
||||
$result[$group['group_id']] = $this->bot->{$name}(...$arguments);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace ZM\API;
|
||||
|
||||
use ZM\API\Proxies\Bot as Proxies;
|
||||
use ZM\Console\Console;
|
||||
|
||||
/**
|
||||
* Class ZMRobot
|
||||
* @since 1.2
|
||||
@@ -11,4 +14,25 @@ namespace ZM\API;
|
||||
*/
|
||||
class ZMRobot extends OneBotV11
|
||||
{
|
||||
/**
|
||||
* 获取一个会在所有机器人实例上执行的代理
|
||||
*/
|
||||
public function all(Proxies\AbstractBotProxy $proxy = null): Proxies\AllBotsProxy
|
||||
{
|
||||
$bot = $proxy ?: $this;
|
||||
$bot_id = implode_when_necessary($bot->getSelfId());
|
||||
Console::debug("Constructing AllBotsProxy for ZMRobot({$bot_id})");
|
||||
return new Proxies\AllBotsProxy($bot);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个会在所有群上执行的代理
|
||||
*/
|
||||
public function allGroups(Proxies\AbstractBotProxy $proxy = null): Proxies\AllGroupsProxy
|
||||
{
|
||||
$bot = $proxy ?: $this;
|
||||
$bot_id = implode_when_necessary($bot->getSelfId());
|
||||
Console::debug("Constructing AllGroupsProxy for ZMRobot({$bot_id})");
|
||||
return new Proxies\AllGroupsProxy($bot);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,10 +21,10 @@ use ZM\Annotation\Interfaces\Level;
|
||||
use ZM\Annotation\Module\Closed;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Event\EventManager;
|
||||
use ZM\Exception\AnnotationException;
|
||||
use ZM\Utils\Manager\RouteManager;
|
||||
use ZM\Utils\ZMUtil;
|
||||
use function server;
|
||||
|
||||
class AnnotationParser
|
||||
{
|
||||
@@ -156,11 +156,12 @@ class AnnotationParser
|
||||
}
|
||||
$inserted[$v][$method_name] = true;
|
||||
}
|
||||
}
|
||||
if ($method_anno instanceof RequestMapping) {
|
||||
} elseif ($method_anno instanceof RequestMapping) {
|
||||
RouteManager::importRouteByAnnotation($method_anno, $method_name, $v, $methods_annotations);
|
||||
} elseif ($method_anno instanceof Middleware) {
|
||||
$this->middleware_map[$method_anno->class][$method_anno->method][] = $method_anno;
|
||||
} else {
|
||||
EventManager::$event_map[$method_anno->class][$method_anno->method][] = $method_anno;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
146
src/ZM/Annotation/CQ/CommandArgument.php
Normal file
146
src/ZM/Annotation/CQ/CommandArgument.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Annotation\CQ;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
use Doctrine\Common\Annotations\Annotation\Required;
|
||||
use ZM\Annotation\AnnotationBase;
|
||||
use ZM\Annotation\Interfaces\ErgodicAnnotation;
|
||||
use ZM\Exception\InvalidArgumentException;
|
||||
use ZM\Exception\ZMKnownException;
|
||||
|
||||
/**
|
||||
* Class CommandArgument
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target("ALL")
|
||||
*/
|
||||
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_ALL)]
|
||||
class CommandArgument extends AnnotationBase implements ErgodicAnnotation
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
* @Required()
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $description = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $type = 'string';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $required = false;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $prompt = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $default = '';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $timeout = 60;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $error_prompt_policy = 1;
|
||||
|
||||
/**
|
||||
* @param string $name 参数名称(可以是中文)
|
||||
* @param string $description 参数描述(默认为空)
|
||||
* @param bool $required 参数是否必需,如果是必需,为true(默认为false)
|
||||
* @param string $prompt 当参数为必需时,返回给用户的提示输入的消息(默认为"请输入$name")
|
||||
* @param string $default 当required为false时,未匹配到参数将自动使用default值(默认为空)
|
||||
* @param int $timeout prompt超时时间(默认为60秒)
|
||||
* @throws InvalidArgumentException|ZMKnownException
|
||||
*/
|
||||
public function __construct(
|
||||
string $name,
|
||||
string $description = '',
|
||||
string $type = 'string',
|
||||
bool $required = false,
|
||||
string $prompt = '',
|
||||
string $default = '',
|
||||
int $timeout = 60,
|
||||
int $error_prompt_policy = 1
|
||||
) {
|
||||
$this->name = $name;
|
||||
$this->description = $description;
|
||||
$this->type = $this->fixTypeName($type);
|
||||
$this->required = $required;
|
||||
$this->prompt = $prompt;
|
||||
$this->default = $default;
|
||||
$this->timeout = $timeout;
|
||||
$this->error_prompt_policy = $error_prompt_policy;
|
||||
if ($this->type === 'bool') {
|
||||
if ($this->default === '') {
|
||||
$this->default = 'yes';
|
||||
}
|
||||
if (!in_array($this->default, array_merge(TRUE_LIST, FALSE_LIST))) {
|
||||
throw new InvalidArgumentException('CommandArgument参数 ' . $name . ' 类型传入类型应为布尔型,检测到非法的默认值 ' . $this->default);
|
||||
}
|
||||
} elseif ($this->type === 'number') {
|
||||
if ($this->default === '') {
|
||||
$this->default = '0';
|
||||
}
|
||||
if (!is_numeric($this->default)) {
|
||||
throw new InvalidArgumentException('CommandArgument参数 ' . $name . ' 类型传入类型应为数字型,检测到非法的默认值 ' . $this->default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getTypeErrorPrompt(): string
|
||||
{
|
||||
return '参数类型错误,请重新输入!';
|
||||
}
|
||||
|
||||
public function getErrorQuitPrompt(): string
|
||||
{
|
||||
return '参数类型错误,停止输入!';
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZMKnownException
|
||||
*/
|
||||
protected function fixTypeName(string $type): string
|
||||
{
|
||||
$table = [
|
||||
'str' => 'string',
|
||||
'string' => 'string',
|
||||
'strings' => 'string',
|
||||
'byte' => 'string',
|
||||
'num' => 'number',
|
||||
'number' => 'number',
|
||||
'int' => 'number',
|
||||
'float' => 'number',
|
||||
'double' => 'number',
|
||||
'boolean' => 'bool',
|
||||
'bool' => 'bool',
|
||||
'true' => 'bool',
|
||||
'any' => 'any',
|
||||
'all' => 'any',
|
||||
'*' => 'any',
|
||||
];
|
||||
if (array_key_exists($type, $table)) {
|
||||
return $table[$type];
|
||||
}
|
||||
throw new ZMKnownException(zm_internal_errcode('E00077') . 'Invalid argument type: ' . $type . ', only support any, string, number and bool !');
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ class BuildCommand extends Command
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$this->output = $output;
|
||||
$target_dir = $input->getOption('target') ?? (WORKING_DIR);
|
||||
$target_dir = $input->getOption('target') ?? WORKING_DIR;
|
||||
if (mb_strpos($target_dir, '../')) {
|
||||
$target_dir = realpath($target_dir);
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ class InitCommand extends Command
|
||||
$info = pathinfo($file);
|
||||
@mkdir($base_path . $info['dirname'], 0777, true);
|
||||
echo 'Copying ' . $file . PHP_EOL;
|
||||
$package_name = (json_decode(file_get_contents(__DIR__ . '/../../../composer.json'), true)['name']);
|
||||
$package_name = json_decode(file_get_contents(__DIR__ . '/../../../composer.json'), true)['name'];
|
||||
copy($base_path . '/vendor/' . $package_name . $file, $base_path . $file);
|
||||
} else {
|
||||
echo 'Skipping ' . $file . ' , file exists.' . PHP_EOL;
|
||||
|
||||
@@ -48,6 +48,7 @@ class RunServerCommand extends Command
|
||||
new InputOption('polling-watch', null, null, '强制启用轮询模式监听'),
|
||||
new InputOption('no-state-check', null, null, '关闭启动前框架运行状态检查'),
|
||||
new InputOption('private-mode', null, null, '启动时隐藏MOTD和敏感信息'),
|
||||
new InputOption('audit-mode', null, null, '启动时开启审计模式,独立将所有日志输出到文件供开发人员审计'),
|
||||
]);
|
||||
$this->setDescription('Run zhamao-framework | 启动框架');
|
||||
$this->setHelp('直接运行可以启动');
|
||||
|
||||
@@ -43,7 +43,9 @@ class ServerStopCommand extends DaemonCommand
|
||||
} else {
|
||||
parent::execute($input, $output);
|
||||
}
|
||||
Process::kill(intval($this->daemon_file['pid']), SIGTERM);
|
||||
if ($this->daemon_file !== null) {
|
||||
Process::kill(intval($this->daemon_file['pid']), SIGTERM);
|
||||
}
|
||||
$i = 10;
|
||||
while (Framework::getProcessState(ZM_PROCESS_MASTER) !== false && $i > 0) {
|
||||
sleep(1);
|
||||
|
||||
@@ -28,9 +28,9 @@ use ZM\Exception\InitException;
|
||||
|
||||
class ConsoleApplication extends Application
|
||||
{
|
||||
public const VERSION_ID = 460;
|
||||
public const VERSION_ID = 478;
|
||||
|
||||
public const VERSION = '2.7.5';
|
||||
public const VERSION = '2.8.4';
|
||||
|
||||
private static $obj;
|
||||
|
||||
|
||||
104
src/ZM/Container/BoundMethod.php
Normal file
104
src/ZM/Container/BoundMethod.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Container;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use ReflectionException;
|
||||
use ReflectionParameter;
|
||||
use ZM\Utils\ReflectionUtil;
|
||||
|
||||
class BoundMethod
|
||||
{
|
||||
/**
|
||||
* 调用指定闭包、类方法并注入依赖
|
||||
*
|
||||
* @param Container $container
|
||||
* @param callable|string $callback
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ReflectionException
|
||||
* @return mixed
|
||||
*/
|
||||
public static function call(ContainerInterface $container, $callback, array $parameters = [], string $default_method = null)
|
||||
{
|
||||
if (is_string($callback) && !$default_method && method_exists($callback, '__invoke')) {
|
||||
$default_method = '__invoke';
|
||||
}
|
||||
|
||||
if (is_string($callback) && $default_method) {
|
||||
$callback = [$callback, $default_method];
|
||||
}
|
||||
|
||||
if (ReflectionUtil::isNonStaticMethod($callback)) {
|
||||
$callback[0] = $container->make($callback[0]);
|
||||
}
|
||||
|
||||
if (!is_callable($callback)) {
|
||||
throw new InvalidArgumentException('Callback is not callable.');
|
||||
}
|
||||
|
||||
return call_user_func_array($callback, self::getMethodDependencies($container, $callback, $parameters));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all dependencies for a given method.
|
||||
*
|
||||
* @param callable|string $callback
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
protected static function getMethodDependencies(ContainerInterface $container, $callback, array $parameters = []): array
|
||||
{
|
||||
$dependencies = [];
|
||||
|
||||
foreach (ReflectionUtil::getCallReflector($callback)->getParameters() as $i => $parameter) {
|
||||
if (isset($parameters[$i]) && $parameter->hasType() && ($type = $parameter->getType())) {
|
||||
if ($type instanceof \ReflectionNamedType && gettype($parameters[$i]) === $type->getName()) {
|
||||
$dependencies[] = $parameters[$i];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
static::addDependencyForCallParameter($container, $parameter, $parameters, $dependencies);
|
||||
}
|
||||
|
||||
return array_merge($dependencies, array_values($parameters));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the dependency for the given call parameter.
|
||||
*
|
||||
* @throws EntryResolutionException
|
||||
*/
|
||||
protected static function addDependencyForCallParameter(
|
||||
ContainerInterface $container,
|
||||
ReflectionParameter $parameter,
|
||||
array &$parameters,
|
||||
array &$dependencies
|
||||
): void {
|
||||
if (array_key_exists($param_name = $parameter->getName(), $parameters)) {
|
||||
$dependencies[] = $parameters[$param_name];
|
||||
|
||||
unset($parameters[$param_name]);
|
||||
} elseif (!is_null($class_name = ReflectionUtil::getParameterClassName($parameter))) {
|
||||
if (array_key_exists($class_name, $parameters)) {
|
||||
$dependencies[] = $parameters[$class_name];
|
||||
|
||||
unset($parameters[$class_name]);
|
||||
} elseif ($parameter->isVariadic()) {
|
||||
$variadic_dependencies = $container->make($class_name);
|
||||
|
||||
$dependencies = array_merge($dependencies, is_array($variadic_dependencies)
|
||||
? $variadic_dependencies
|
||||
: [$variadic_dependencies]);
|
||||
} else {
|
||||
$dependencies[] = $container->make($class_name);
|
||||
}
|
||||
} elseif ($parameter->isDefaultValueAvailable()) {
|
||||
$dependencies[] = $parameter->getDefaultValue();
|
||||
} elseif (!array_key_exists($param_name, $parameters) && !$parameter->isOptional()) {
|
||||
$message = "无法解析类 {$parameter->getDeclaringClass()->getName()} 的依赖 {$parameter}";
|
||||
|
||||
throw new EntryResolutionException($message);
|
||||
}
|
||||
}
|
||||
}
|
||||
61
src/ZM/Container/Container.php
Normal file
61
src/ZM/Container/Container.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Container;
|
||||
|
||||
use ZM\Utils\SingletonTrait;
|
||||
|
||||
class Container implements ContainerInterface
|
||||
{
|
||||
use SingletonTrait;
|
||||
use ContainerTrait {
|
||||
make as protected traitMake;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取父容器
|
||||
*/
|
||||
public function getParent(): ContainerInterface
|
||||
{
|
||||
return WorkerContainer::getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the container can return an entry for the given identifier.
|
||||
* Returns false otherwise.
|
||||
*
|
||||
* `has($id)` returning true does not mean that `get($id)` will not throw an exception.
|
||||
* It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
|
||||
*
|
||||
* @param string $id identifier of the entry to look for
|
||||
*/
|
||||
public function has(string $id): bool
|
||||
{
|
||||
return $this->bound($id) || $this->getParent()->has($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个绑定的实例
|
||||
*
|
||||
* @template T
|
||||
* @param class-string<T> $abstract 类或接口名
|
||||
* @param array $parameters 参数
|
||||
* @throws EntryResolutionException
|
||||
* @return Closure|mixed|T 实例
|
||||
*/
|
||||
public function make(string $abstract, array $parameters = [])
|
||||
{
|
||||
if (isset($this->shared[$abstract])) {
|
||||
return $this->shared[$abstract];
|
||||
}
|
||||
|
||||
// 此类没有,父类有,则从父类中获取
|
||||
if (!$this->bound($abstract) && $this->getParent()->bound($abstract)) {
|
||||
$this->log("{$abstract} is not bound, but in parent container, using parent container");
|
||||
return $this->getParent()->make($abstract, $parameters);
|
||||
}
|
||||
|
||||
return $this->traitMake($abstract, $parameters);
|
||||
}
|
||||
}
|
||||
110
src/ZM/Container/ContainerInterface.php
Normal file
110
src/ZM/Container/ContainerInterface.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Container;
|
||||
|
||||
use Closure;
|
||||
use Psr\Container\ContainerInterface as PsrContainerInterface;
|
||||
|
||||
/**
|
||||
* Interface ContainerInterface
|
||||
*
|
||||
* 从 Illuminate WorkerContainer 简化而来,兼容 PSR-11
|
||||
*/
|
||||
interface ContainerInterface extends PsrContainerInterface
|
||||
{
|
||||
/**
|
||||
* 判断对应的类或接口是否已经注册
|
||||
*
|
||||
* @param string $abstract 类或接口名
|
||||
*/
|
||||
public function bound(string $abstract): bool;
|
||||
|
||||
/**
|
||||
* 注册一个类别名
|
||||
*
|
||||
* @param string $abstract 类或接口名
|
||||
* @param string $alias 别名
|
||||
*/
|
||||
public function alias(string $abstract, string $alias): void;
|
||||
|
||||
/**
|
||||
* 注册绑定
|
||||
*
|
||||
* @param string $abstract 类或接口名
|
||||
* @param null|Closure|string $concrete 返回类实例的闭包,或是类名
|
||||
* @param bool $shared 是否共享
|
||||
*/
|
||||
public function bind(string $abstract, $concrete = null, bool $shared = false): void;
|
||||
|
||||
/**
|
||||
* 注册绑定
|
||||
*
|
||||
* 在已经绑定时不会重复注册
|
||||
*
|
||||
* @param string $abstract 类或接口名
|
||||
* @param null|Closure|string $concrete 返回类实例的闭包,或是类名
|
||||
* @param bool $shared 是否共享
|
||||
*/
|
||||
public function bindIf(string $abstract, $concrete = null, bool $shared = false): void;
|
||||
|
||||
/**
|
||||
* 注册一个单例绑定
|
||||
*
|
||||
* @param string $abstract 类或接口名
|
||||
* @param null|Closure|string $concrete 返回类实例的闭包,或是类名
|
||||
*/
|
||||
public function singleton(string $abstract, $concrete = null): void;
|
||||
|
||||
/**
|
||||
* 注册一个单例绑定
|
||||
*
|
||||
* 在已经绑定时不会重复注册
|
||||
*
|
||||
* @param string $abstract 类或接口名
|
||||
* @param null|Closure|string $concrete 返回类实例的闭包,或是类名
|
||||
*/
|
||||
public function singletonIf(string $abstract, $concrete = null): void;
|
||||
|
||||
/**
|
||||
* 注册一个已有的实例,效果等同于单例绑定
|
||||
*
|
||||
* @param string $abstract 类或接口名
|
||||
* @param mixed $instance 实例
|
||||
* @return mixed
|
||||
*/
|
||||
public function instance(string $abstract, $instance);
|
||||
|
||||
/**
|
||||
* 获取一个解析对应类实例的闭包
|
||||
*
|
||||
* @param string $abstract 类或接口名
|
||||
*/
|
||||
public function factory(string $abstract): Closure;
|
||||
|
||||
/**
|
||||
* 清除所有绑定和实例
|
||||
*/
|
||||
public function flush(): void;
|
||||
|
||||
/**
|
||||
* 获取一个绑定的实例
|
||||
*
|
||||
* @template T
|
||||
* @param class-string<T> $abstract 类或接口名
|
||||
* @param array $parameters 参数
|
||||
* @return Closure|mixed|T 实例
|
||||
*/
|
||||
public function make(string $abstract, array $parameters = []);
|
||||
|
||||
/**
|
||||
* 调用对应的方法,并自动注入依赖
|
||||
*
|
||||
* @param callable $callback 对应的方法
|
||||
* @param array $parameters 参数
|
||||
* @param null|string $default_method 默认方法
|
||||
* @return mixed
|
||||
*/
|
||||
public function call(callable $callback, array $parameters = [], string $default_method = null);
|
||||
}
|
||||
736
src/ZM/Container/ContainerTrait.php
Normal file
736
src/ZM/Container/ContainerTrait.php
Normal file
@@ -0,0 +1,736 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Container;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
use ReflectionNamedType;
|
||||
use ReflectionParameter;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Utils\ReflectionUtil;
|
||||
|
||||
trait ContainerTrait
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $shared = [];
|
||||
|
||||
/**
|
||||
* @var array[]
|
||||
*/
|
||||
protected $build_stack = [];
|
||||
|
||||
/**
|
||||
* @var array[]
|
||||
*/
|
||||
protected $with = [];
|
||||
|
||||
/**
|
||||
* 日志前缀
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $log_prefix;
|
||||
|
||||
/**
|
||||
* @var array[]
|
||||
*/
|
||||
private static $bindings = [];
|
||||
|
||||
/**
|
||||
* @var object[]
|
||||
*/
|
||||
private static $instances = [];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private static $aliases = [];
|
||||
|
||||
/**
|
||||
* @var Closure[][]
|
||||
*/
|
||||
private static $extenders = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if ($this->shouldLog()) {
|
||||
$this->log('Container created');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断对应的类或接口是否已经注册
|
||||
*
|
||||
* @param string $abstract 类或接口名
|
||||
*/
|
||||
public function bound(string $abstract): bool
|
||||
{
|
||||
return array_key_exists($abstract, self::$bindings)
|
||||
|| array_key_exists($abstract, self::$instances)
|
||||
|| array_key_exists($abstract, $this->shared)
|
||||
|| $this->isAlias($abstract);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类别名(如存在)
|
||||
*
|
||||
* @param string $abstract 类或接口名
|
||||
* @return string 别名,不存在时返回传入的类或接口名
|
||||
*/
|
||||
public function getAlias(string $abstract): string
|
||||
{
|
||||
if (!isset(self::$aliases[$abstract])) {
|
||||
return $abstract;
|
||||
}
|
||||
|
||||
return $this->getAlias(self::$aliases[$abstract]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册一个类别名
|
||||
*
|
||||
* @param string $abstract 类或接口名
|
||||
* @param string $alias 别名
|
||||
*/
|
||||
public function alias(string $abstract, string $alias): void
|
||||
{
|
||||
if ($alias === $abstract) {
|
||||
throw new InvalidArgumentException("[{$abstract}] is same as [{$alias}]");
|
||||
}
|
||||
|
||||
self::$aliases[$alias] = $abstract;
|
||||
|
||||
if ($this->shouldLog()) {
|
||||
$this->log("[{$abstract}] is aliased as [{$alias}]");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册绑定
|
||||
*
|
||||
* @param string $abstract 类或接口名
|
||||
* @param null|Closure|string $concrete 返回类实例的闭包,或是类名
|
||||
* @param bool $shared 是否共享
|
||||
*/
|
||||
public function bind(string $abstract, $concrete = null, bool $shared = false): void
|
||||
{
|
||||
$this->dropStaleInstances($abstract);
|
||||
|
||||
// 如果没有提供闭包,则默认为自动解析类名
|
||||
if (is_null($concrete)) {
|
||||
$concrete = $abstract;
|
||||
}
|
||||
|
||||
$concrete_name = '';
|
||||
if ($this->shouldLog()) {
|
||||
$concrete_name = ReflectionUtil::variableToString($concrete);
|
||||
}
|
||||
|
||||
// 如果不是闭包,则认为是类名,此时将其包装在一个闭包中,以方便后续处理
|
||||
if (!$concrete instanceof Closure) {
|
||||
$concrete = $this->getClosure($abstract, $concrete);
|
||||
}
|
||||
|
||||
self::$bindings[$abstract] = compact('concrete', 'shared');
|
||||
|
||||
if ($this->shouldLog()) {
|
||||
$this->log("[{$abstract}] is bound to [{$concrete_name}]" . ($shared ? ' (shared)' : ''));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册绑定
|
||||
*
|
||||
* 在已经绑定时不会重复注册
|
||||
*
|
||||
* @param string $abstract 类或接口名
|
||||
* @param null|Closure|string $concrete 返回类实例的闭包,或是类名
|
||||
* @param bool $shared 是否共享
|
||||
*/
|
||||
public function bindIf(string $abstract, $concrete = null, bool $shared = false): void
|
||||
{
|
||||
if (!$this->bound($abstract)) {
|
||||
$this->bind($abstract, $concrete, $shared);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册一个单例绑定
|
||||
*
|
||||
* @param string $abstract 类或接口名
|
||||
* @param null|Closure|string $concrete 返回类实例的闭包,或是类名
|
||||
*/
|
||||
public function singleton(string $abstract, $concrete = null): void
|
||||
{
|
||||
$this->bind($abstract, $concrete, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册一个单例绑定
|
||||
*
|
||||
* 在已经绑定时不会重复注册
|
||||
*
|
||||
* @param string $abstract 类或接口名
|
||||
* @param null|Closure|string $concrete 返回类实例的闭包,或是类名
|
||||
*/
|
||||
public function singletonIf(string $abstract, $concrete = null): void
|
||||
{
|
||||
if (!$this->bound($abstract)) {
|
||||
$this->singleton($abstract, $concrete);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册一个已有的实例,效果等同于单例绑定
|
||||
*
|
||||
* @param string $abstract 类或接口名
|
||||
* @param mixed $instance 实例
|
||||
* @return mixed
|
||||
*/
|
||||
public function instance(string $abstract, $instance)
|
||||
{
|
||||
if (isset(self::$instances[$abstract])) {
|
||||
return self::$instances[$abstract];
|
||||
}
|
||||
|
||||
self::$instances[$abstract] = $instance;
|
||||
|
||||
if ($this->shouldLog()) {
|
||||
$class_name = ReflectionUtil::variableToString($instance);
|
||||
$this->log("[{$abstract}] is bound to [{$class_name}] (instance)");
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个解析对应类实例的闭包
|
||||
*
|
||||
* @param string $abstract 类或接口名
|
||||
*/
|
||||
public function factory(string $abstract): Closure
|
||||
{
|
||||
return function () use ($abstract) {
|
||||
return $this->make($abstract);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有绑定和实例
|
||||
*/
|
||||
public function flush(): void
|
||||
{
|
||||
self::$aliases = [];
|
||||
self::$bindings = [];
|
||||
self::$instances = [];
|
||||
|
||||
$this->shared = [];
|
||||
$this->build_stack = [];
|
||||
$this->with = [];
|
||||
|
||||
if ($this->shouldLog()) {
|
||||
$this->log('Container flushed');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个绑定的实例
|
||||
*
|
||||
* @template T
|
||||
* @param class-string<T> $abstract 类或接口名
|
||||
* @param array $parameters 参数
|
||||
* @throws EntryResolutionException
|
||||
* @return Closure|mixed|T 实例
|
||||
*/
|
||||
public function make(string $abstract, array $parameters = [])
|
||||
{
|
||||
$abstract = $this->getAlias($abstract);
|
||||
|
||||
$needs_contextual_build = !empty($parameters);
|
||||
|
||||
if (isset($this->shared[$abstract])) {
|
||||
if ($this->shouldLog()) {
|
||||
$this->log(sprintf(
|
||||
'[%s] resolved (shared)%s',
|
||||
$abstract,
|
||||
$needs_contextual_build ? ' with ' . implode(', ', $parameters) : ''
|
||||
));
|
||||
}
|
||||
return $this->shared[$abstract];
|
||||
}
|
||||
|
||||
// 如果已经存在在实例池中(通常意味着单例绑定),则直接返回该实例
|
||||
if (isset(self::$instances[$abstract]) && !$needs_contextual_build) {
|
||||
if ($this->shouldLog()) {
|
||||
$this->log("[{$abstract}] resolved (instance)");
|
||||
}
|
||||
return self::$instances[$abstract];
|
||||
}
|
||||
|
||||
$this->with[] = $parameters;
|
||||
|
||||
$concrete = $this->getConcrete($abstract);
|
||||
|
||||
// 构造该类的实例,并递归解析所有依赖
|
||||
if ($this->isBuildable($concrete, $abstract)) {
|
||||
$object = $this->build($concrete);
|
||||
} else {
|
||||
$object = $this->make($concrete);
|
||||
}
|
||||
|
||||
// 如果该类存在扩展器(装饰器),则逐个应用到实例
|
||||
foreach ($this->getExtenders($abstract) as $extender) {
|
||||
$object = $extender($object, $this);
|
||||
}
|
||||
|
||||
// 如果该类被注册为单例,则需要将其存放在实例池中,方便后续取用同一实例
|
||||
if (!$needs_contextual_build && $this->isShared($abstract)) {
|
||||
$this->shared[$abstract] = $object;
|
||||
if ($this->shouldLog()) {
|
||||
$this->log("[{$abstract}] added to shared pool");
|
||||
}
|
||||
}
|
||||
|
||||
// 弹出本次构造的覆盖参数
|
||||
array_pop($this->with);
|
||||
|
||||
if ($this->shouldLog()) {
|
||||
$this->log(sprintf(
|
||||
'[%s] resolved%s',
|
||||
$abstract,
|
||||
$needs_contextual_build ? ' with ' . implode(', ', $parameters) : ''
|
||||
));
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实例化具体的类实例
|
||||
*
|
||||
* @param Closure|string $concrete 类名或对应的闭包
|
||||
* @throws EntryResolutionException
|
||||
* @return mixed
|
||||
*/
|
||||
public function build($concrete)
|
||||
{
|
||||
// 如果传入的是闭包,则直接执行并返回
|
||||
if ($concrete instanceof Closure) {
|
||||
return $concrete($this, $this->getLastParameterOverride());
|
||||
}
|
||||
|
||||
try {
|
||||
$reflection = new ReflectionClass($concrete);
|
||||
} catch (ReflectionException $e) {
|
||||
throw new EntryResolutionException("指定的类 {$concrete} 不存在", 0, $e);
|
||||
}
|
||||
|
||||
if (!$reflection->isInstantiable()) {
|
||||
$this->notInstantiable($concrete);
|
||||
}
|
||||
|
||||
$this->build_stack[] = $concrete;
|
||||
|
||||
$constructor = $reflection->getConstructor();
|
||||
|
||||
// 如果不存在构造函数,则代表不需要进一步解析,直接实例化即可
|
||||
if (is_null($constructor)) {
|
||||
array_pop($this->build_stack);
|
||||
return new $concrete();
|
||||
}
|
||||
|
||||
$dependencies = $constructor->getParameters();
|
||||
|
||||
// 获取所有依赖的实例
|
||||
try {
|
||||
$instances = $this->resolveDependencies($dependencies);
|
||||
} catch (EntryResolutionException $e) {
|
||||
array_pop($this->build_stack);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
array_pop($this->build_stack);
|
||||
|
||||
return $reflection->newInstanceArgs($instances);
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用对应的方法,并自动注入依赖
|
||||
*
|
||||
* @param callable|string $callback 对应的方法
|
||||
* @param array $parameters 参数
|
||||
* @param null|string $default_method 默认方法
|
||||
* @return mixed
|
||||
*/
|
||||
public function call($callback, array $parameters = [], string $default_method = null)
|
||||
{
|
||||
if ($this->shouldLog()) {
|
||||
if (count($parameters)) {
|
||||
$str_parameters = array_map([ReflectionUtil::class, 'variableToString'], $parameters);
|
||||
$str_parameters = implode(', ', $str_parameters);
|
||||
} else {
|
||||
$str_parameters = '';
|
||||
}
|
||||
$this->log(sprintf(
|
||||
'Called %s%s(%s)',
|
||||
ReflectionUtil::variableToString($callback),
|
||||
$default_method ? '@' . $default_method : '',
|
||||
$str_parameters
|
||||
));
|
||||
}
|
||||
return BoundMethod::call($this, $callback, $parameters, $default_method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds an entry of the container by its identifier and returns it.
|
||||
*
|
||||
* @param string $id identifier of the entry to look for
|
||||
*
|
||||
* @throws NotFoundExceptionInterface no entry was found for **this** identifier
|
||||
* @throws ContainerExceptionInterface error while retrieving the entry
|
||||
*
|
||||
* @return mixed entry
|
||||
*/
|
||||
public function get(string $id)
|
||||
{
|
||||
try {
|
||||
return $this->make($id);
|
||||
} catch (Exception $e) {
|
||||
if ($this->has($id)) {
|
||||
throw new EntryResolutionException('', 0, $e);
|
||||
}
|
||||
|
||||
throw new EntryNotFoundException('', 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the container can return an entry for the given identifier.
|
||||
* Returns false otherwise.
|
||||
*
|
||||
* `has($id)` returning true does not mean that `get($id)` will not throw an exception.
|
||||
* It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
|
||||
*
|
||||
* @param string $id identifier of the entry to look for
|
||||
*/
|
||||
public function has(string $id): bool
|
||||
{
|
||||
return $this->bound($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 扩展一个类或接口
|
||||
*
|
||||
* @param string $abstract 类或接口名
|
||||
* @param Closure $closure 扩展闭包
|
||||
*/
|
||||
public function extend(string $abstract, Closure $closure): void
|
||||
{
|
||||
$abstract = $this->getAlias($abstract);
|
||||
|
||||
// 如果该类已经被解析过,则直接将扩展器应用到该类的实例上
|
||||
// 否则,将扩展器存入扩展器池,等待解析
|
||||
if (isset(self::$instances[$abstract])) {
|
||||
self::$instances[$abstract] = $closure(self::$instances[$abstract], $this);
|
||||
} else {
|
||||
self::$extenders[$abstract][] = $closure;
|
||||
}
|
||||
|
||||
if ($this->shouldLog()) {
|
||||
$this->log("[{$abstract}] extended");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取日志前缀
|
||||
*/
|
||||
public function getLogPrefix(): string
|
||||
{
|
||||
return ($this->log_prefix ?: '[WorkerContainer(U)]') . ' ';
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置日志前缀
|
||||
*/
|
||||
public function setLogPrefix(string $prefix): void
|
||||
{
|
||||
$this->log_prefix = $prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对应类型的所有扩展器
|
||||
*
|
||||
* @param string $abstract 类或接口名
|
||||
* @return Closure[]
|
||||
*/
|
||||
protected function getExtenders(string $abstract): array
|
||||
{
|
||||
$abstract = $this->getAlias($abstract);
|
||||
|
||||
return self::$extenders[$abstract] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断传入的是否为别名
|
||||
*/
|
||||
protected function isAlias(string $name): bool
|
||||
{
|
||||
return array_key_exists($name, self::$aliases);
|
||||
}
|
||||
|
||||
/**
|
||||
* 抛弃所有过时的实例和别名
|
||||
*
|
||||
* @param string $abstract 类或接口名
|
||||
*/
|
||||
protected function dropStaleInstances(string $abstract): void
|
||||
{
|
||||
unset(
|
||||
self::$instances[$abstract],
|
||||
self::$aliases[$abstract],
|
||||
$this->shared[$abstract]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个解析对应类的闭包
|
||||
*
|
||||
* @param string $abstract 类或接口名
|
||||
* @param string $concrete 实际类名
|
||||
*/
|
||||
protected function getClosure(string $abstract, string $concrete): Closure
|
||||
{
|
||||
return static function ($container, $parameters = []) use ($abstract, $concrete) {
|
||||
$method = $abstract === $concrete ? 'build' : 'make';
|
||||
|
||||
return $container->{$method}($concrete, $parameters);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最后一次的覆盖参数
|
||||
*/
|
||||
protected function getLastParameterOverride(): array
|
||||
{
|
||||
return $this->with[count($this->with) - 1] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 抛出实例化异常
|
||||
*
|
||||
* @throws EntryResolutionException
|
||||
*/
|
||||
protected function notInstantiable(string $concrete, string $reason = ''): void
|
||||
{
|
||||
if (!empty($this->build_stack)) {
|
||||
$previous = implode(', ', $this->build_stack);
|
||||
$message = "类 {$concrete} 无法实例化,其被 {$previous} 依赖";
|
||||
} else {
|
||||
$message = "类 {$concrete} 无法实例化";
|
||||
}
|
||||
|
||||
throw new EntryResolutionException("{$message}:{$reason}");
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析依赖
|
||||
*
|
||||
* @param ReflectionParameter[] $dependencies
|
||||
* @throws EntryResolutionException
|
||||
*/
|
||||
protected function resolveDependencies(array $dependencies): array
|
||||
{
|
||||
$results = [];
|
||||
|
||||
foreach ($dependencies as $dependency) {
|
||||
// 如果此依赖存在覆盖参数,则使用覆盖参数
|
||||
// 否则,将尝试解析参数
|
||||
if ($this->hasParameterOverride($dependency)) {
|
||||
$results[] = $this->getParameterOverride($dependency);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 如果存在临时注入的依赖,则使用临时注入的依赖
|
||||
if ($this->hasParameterTypeOverride($dependency)) {
|
||||
$results[] = $this->getParameterTypeOverride($dependency);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 如果类名为空,则代表此依赖是基本类型,且无法对其进行依赖解析
|
||||
$class_name = ReflectionUtil::getParameterClassName($dependency);
|
||||
$results[] = is_null($class_name)
|
||||
? $this->resolvePrimitive($dependency)
|
||||
: $this->resolveClass($dependency);
|
||||
|
||||
if ($this->shouldLog()) {
|
||||
if (is_null($class_name)) {
|
||||
if ($dependency->hasType()) {
|
||||
$class_name = $dependency->getType();
|
||||
} else {
|
||||
$class_name = 'Primitive';
|
||||
}
|
||||
}
|
||||
$this->log("Dependency [{$class_name} {$dependency->name}] resolved");
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断传入的参数是否存在覆盖参数
|
||||
*/
|
||||
protected function hasParameterOverride(ReflectionParameter $parameter): bool
|
||||
{
|
||||
return array_key_exists($parameter->name, $this->getLastParameterOverride());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取覆盖参数
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getParameterOverride(ReflectionParameter $parameter)
|
||||
{
|
||||
return $this->getLastParameterOverride()[$parameter->name];
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断传入的参数是否存在临时注入的参数
|
||||
*/
|
||||
protected function hasParameterTypeOverride(ReflectionParameter $parameter): bool
|
||||
{
|
||||
if (!$parameter->hasType()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$type = $parameter->getType();
|
||||
|
||||
if (!$type instanceof ReflectionNamedType || $type->isBuiltin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return array_key_exists($type->getName(), $this->getLastParameterOverride());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取临时注入的参数
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getParameterTypeOverride(ReflectionParameter $parameter)
|
||||
{
|
||||
$type = $parameter->getType();
|
||||
|
||||
if (!$type instanceof ReflectionNamedType) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->getLastParameterOverride()[$type->getName()];
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析基本类型
|
||||
*
|
||||
* @throws EntryResolutionException 如参数不存在默认值,则抛出异常
|
||||
* @return mixed 对应类型的默认值
|
||||
*/
|
||||
protected function resolvePrimitive(ReflectionParameter $parameter)
|
||||
{
|
||||
if ($parameter->isDefaultValueAvailable()) {
|
||||
return $parameter->getDefaultValue();
|
||||
}
|
||||
|
||||
throw new EntryResolutionException("无法解析类 {$parameter->getDeclaringClass()->getName()} 的参数 {$parameter}");
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析类
|
||||
*
|
||||
* @throws EntryResolutionException 如果无法解析类,则抛出异常
|
||||
* @return mixed
|
||||
*/
|
||||
protected function resolveClass(ReflectionParameter $parameter)
|
||||
{
|
||||
try {
|
||||
// 尝试解析
|
||||
return $this->make($parameter->getClass()->name);
|
||||
} catch (EntryResolutionException $e) {
|
||||
// 如果参数是可选的,则返回默认值
|
||||
if ($parameter->isDefaultValueAvailable()) {
|
||||
array_pop($this->with);
|
||||
return $parameter->getDefaultValue();
|
||||
}
|
||||
|
||||
if ($parameter->isVariadic()) {
|
||||
array_pop($this->with);
|
||||
return [];
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类名的实际类型
|
||||
*
|
||||
* @param string $abstract 类或接口名
|
||||
* @return Closure|string
|
||||
*/
|
||||
protected function getConcrete(string $abstract)
|
||||
{
|
||||
if (isset(self::$bindings[$abstract])) {
|
||||
return self::$bindings[$abstract]['concrete'];
|
||||
}
|
||||
|
||||
return $abstract;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断传入的实际类型是否可以构造
|
||||
*
|
||||
* @param mixed $concrete 实际类型
|
||||
* @param string $abstract 类或接口名
|
||||
*/
|
||||
protected function isBuildable($concrete, string $abstract): bool
|
||||
{
|
||||
return $concrete === $abstract || $concrete instanceof Closure;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断传入的类型是否为共享实例
|
||||
*
|
||||
* @param string $abstract 类或接口名
|
||||
*/
|
||||
protected function isShared(string $abstract): bool
|
||||
{
|
||||
return isset($this->instances[$abstract])
|
||||
|| (isset($this->bindings[$abstract]['shared'])
|
||||
&& $this->bindings[$abstract]['shared'] === true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否输出日志
|
||||
*/
|
||||
protected function shouldLog(): bool
|
||||
{
|
||||
return Console::getLevel() >= 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录日志(自动附加容器日志前缀)
|
||||
*/
|
||||
protected function log(string $message): void
|
||||
{
|
||||
Console::debug($this->getLogPrefix() . $message);
|
||||
}
|
||||
}
|
||||
12
src/ZM/Container/EntryNotFoundException.php
Normal file
12
src/ZM/Container/EntryNotFoundException.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Container;
|
||||
|
||||
use Exception;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
class EntryNotFoundException extends Exception implements NotFoundExceptionInterface
|
||||
{
|
||||
}
|
||||
12
src/ZM/Container/EntryResolutionException.php
Normal file
12
src/ZM/Container/EntryResolutionException.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Container;
|
||||
|
||||
use Exception;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
|
||||
class EntryResolutionException extends Exception implements ContainerExceptionInterface
|
||||
{
|
||||
}
|
||||
13
src/ZM/Container/WorkerContainer.php
Normal file
13
src/ZM/Container/WorkerContainer.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Container;
|
||||
|
||||
use ZM\Utils\SingletonTrait;
|
||||
|
||||
class WorkerContainer implements ContainerInterface
|
||||
{
|
||||
use SingletonTrait;
|
||||
use ContainerTrait;
|
||||
}
|
||||
30
src/ZM/Entity/InputArguments.php
Normal file
30
src/ZM/Entity/InputArguments.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Entity;
|
||||
|
||||
class InputArguments
|
||||
{
|
||||
private $arguments;
|
||||
|
||||
public function __construct(array $arguments)
|
||||
{
|
||||
$this->arguments = $arguments;
|
||||
}
|
||||
|
||||
public function getArguments(): array
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
public function getArgument($name)
|
||||
{
|
||||
return $this->arguments[$name] ?? null;
|
||||
}
|
||||
|
||||
public function get($name)
|
||||
{
|
||||
return $this->getArgument($name);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ use Closure;
|
||||
use Doctrine\Common\Annotations\AnnotationException;
|
||||
use Error;
|
||||
use Exception;
|
||||
use Throwable;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Exception\InterruptException;
|
||||
@@ -110,7 +111,7 @@ class EventDispatcher
|
||||
|
||||
/**
|
||||
* @param mixed ...$params
|
||||
* @throws Exception
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function dispatchEvents(...$params)
|
||||
{
|
||||
@@ -137,7 +138,7 @@ class EventDispatcher
|
||||
} catch (InterruptException $e) {
|
||||
$this->store = $e->return_var;
|
||||
$this->status = self::STATUS_INTERRUPTED;
|
||||
} catch (Exception|Error $e) {
|
||||
} catch (Throwable $e) {
|
||||
$this->status = self::STATUS_EXCEPTION;
|
||||
throw $e;
|
||||
}
|
||||
@@ -188,7 +189,9 @@ class EventDispatcher
|
||||
if (isset(EventManager::$middleware_map[$q_c][$q_f])) {
|
||||
$middlewares = EventManager::$middleware_map[$q_c][$q_f];
|
||||
if ($this->log) {
|
||||
Console::verbose("[事件分发{$this->eid}] " . $q_c . '::' . $q_f . ' 方法还绑定了 Middleware:' . implode(', ', array_map(function ($x) { return $x->middleware; }, $middlewares)));
|
||||
Console::verbose("[事件分发{$this->eid}] " . $q_c . '::' . $q_f . ' 方法还绑定了 Middleware:' . implode(', ', array_map(function ($x) {
|
||||
return $x->middleware;
|
||||
}, $middlewares)));
|
||||
}
|
||||
$before_result = true;
|
||||
$r = [];
|
||||
@@ -231,7 +234,7 @@ class EventDispatcher
|
||||
if ($this->log) {
|
||||
Console::verbose("[事件分发{$this->eid}] 正在执行方法 " . $q_c . '::' . $q_f . ' ...');
|
||||
}
|
||||
$this->store = $q_o->{$q_f}(...$params);
|
||||
$this->store = container()->call([$q_o, $q_f], $params);
|
||||
} catch (Exception $e) {
|
||||
if ($e instanceof InterruptException) {
|
||||
if ($this->log) {
|
||||
@@ -283,7 +286,7 @@ class EventDispatcher
|
||||
if ($this->log) {
|
||||
Console::verbose("[事件分发{$this->eid}] 正在执行方法 " . $q_c . '::' . $q_f . ' ...');
|
||||
}
|
||||
$this->store = $q_o->{$q_f}(...$params);
|
||||
$this->store = container()->call([$q_o, $q_f], $params);
|
||||
$this->status = self::STATUS_NORMAL;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ class EventManager
|
||||
|
||||
public static $middleware_map = [];
|
||||
|
||||
public static $event_map = [];
|
||||
|
||||
public static $middlewares = [];
|
||||
|
||||
public static $req_mapping = [];
|
||||
@@ -32,7 +34,8 @@ class EventManager
|
||||
if ($event_obj->method instanceof Closure) {
|
||||
Console::debug("Adding event {$event_name} at @Anonymous");
|
||||
} else {
|
||||
Console::debug("Adding event {$event_name} at " . ($event_obj->class) . ':' . ($event_obj->method));
|
||||
Console::debug("Adding event {$event_name} at " . $event_obj->class . ':' . $event_obj->method);
|
||||
self::$event_map[$event_obj->class][$event_obj->method][] = $event_obj;
|
||||
}
|
||||
self::$events[$event_name][] = $event_obj;
|
||||
(new AnnotationParser())->sortByLevel(self::$events, $event_name);
|
||||
|
||||
78
src/ZM/Event/EventMapIterator.php
Normal file
78
src/ZM/Event/EventMapIterator.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Event;
|
||||
|
||||
use Iterator;
|
||||
use ReturnTypeWillChange;
|
||||
use ZM\Console\Console;
|
||||
|
||||
class EventMapIterator implements Iterator
|
||||
{
|
||||
private $offset = 0;
|
||||
|
||||
private $class;
|
||||
|
||||
private $method;
|
||||
|
||||
private $event_name;
|
||||
|
||||
public function __construct($class, $method, $event_name)
|
||||
{
|
||||
$this->class = $class;
|
||||
$this->method = $method;
|
||||
$this->event_name = $event_name;
|
||||
}
|
||||
|
||||
#[ReturnTypeWillChange]
|
||||
public function current()
|
||||
{
|
||||
Console::debug('从 [' . $this->offset . '] 开始获取');
|
||||
return EventManager::$event_map[$this->class][$this->method][$this->offset];
|
||||
}
|
||||
|
||||
public function next(): void
|
||||
{
|
||||
Console::debug('下一个offset为 [' . ++$this->offset . ']');
|
||||
$this->nextToValid();
|
||||
}
|
||||
|
||||
#[ReturnTypeWillChange]
|
||||
public function key()
|
||||
{
|
||||
Console::debug('返回key:' . $this->offset);
|
||||
return isset(EventManager::$event_map[$this->class][$this->method][$this->offset]) ? $this->offset : null;
|
||||
}
|
||||
|
||||
public function valid($s = false): bool
|
||||
{
|
||||
Console::debug(
|
||||
"[{$this->offset}] " .
|
||||
($s ? 'valid' : '') . '存在:' .
|
||||
(!isset(EventManager::$event_map[$this->class][$this->method][$this->offset]) ? Console::setColor('false', 'red') : ('true' .
|
||||
(is_a(EventManager::$event_map[$this->class][$this->method][$this->offset], $this->event_name, true) ? ',是目标对象' : ',不是目标对象')))
|
||||
);
|
||||
return
|
||||
isset(EventManager::$event_map[$this->class][$this->method][$this->offset])
|
||||
&& is_a(EventManager::$event_map[$this->class][$this->method][$this->offset], $this->event_name, true);
|
||||
}
|
||||
|
||||
public function rewind(): void
|
||||
{
|
||||
Console::debug('回到0');
|
||||
$this->offset = 0;
|
||||
$this->nextToValid();
|
||||
}
|
||||
|
||||
private function nextToValid()
|
||||
{
|
||||
while (
|
||||
isset(EventManager::$event_map[$this->class][$this->method][$this->offset])
|
||||
&& !is_a(EventManager::$event_map[$this->class][$this->method][$this->offset], $this->event_name, true)
|
||||
) {
|
||||
++$this->offset;
|
||||
}
|
||||
Console::debug('内部偏移offset为 [' . $this->offset . ']');
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ use ZM\Annotation\Swoole\OnCloseEvent;
|
||||
use ZM\Annotation\Swoole\OnSwooleEvent;
|
||||
use ZM\Annotation\Swoole\SwooleHandler;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\ConnectionManager\ConnectionObject;
|
||||
use ZM\ConnectionManager\ManagerGM;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Context\Context;
|
||||
@@ -34,6 +35,8 @@ class OnClose implements SwooleEvent
|
||||
}
|
||||
set_coroutine_params(['server' => $server, 'connection' => $conn, 'fd' => $fd]);
|
||||
|
||||
container()->instance(ConnectionObject::class, $conn);
|
||||
|
||||
$dispatcher1 = new EventDispatcher(OnCloseEvent::class);
|
||||
$dispatcher1->setRuleFunction(function ($v) {
|
||||
return $v->connect_type == ctx()->getConnection()->getName() && eval('return ' . $v->getRule() . ';');
|
||||
|
||||
@@ -70,7 +70,7 @@ class OnManagerStart implements SwooleEvent
|
||||
}
|
||||
if (Framework::$argv['polling-watch']) {
|
||||
self::$watch_tick_id = swoole_timer_tick(3000, function () use ($server) {
|
||||
$data = (DataProvider::scanDirFiles(DataProvider::getSourceRootDir() . '/src/'));
|
||||
$data = DataProvider::scanDirFiles(DataProvider::getSourceRootDir() . '/src/');
|
||||
$hash = md5('');
|
||||
foreach ($data as $file) {
|
||||
$hash = md5($hash . md5_file($file));
|
||||
|
||||
@@ -10,10 +10,13 @@ use Throwable;
|
||||
use ZM\Annotation\Swoole\OnMessageEvent;
|
||||
use ZM\Annotation\Swoole\OnSwooleEvent;
|
||||
use ZM\Annotation\Swoole\SwooleHandler;
|
||||
use ZM\ConnectionManager\ConnectionObject;
|
||||
use ZM\ConnectionManager\ManagerGM;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Console\TermColor;
|
||||
use ZM\Container\Container;
|
||||
use ZM\Context\Context;
|
||||
use ZM\Context\ContextInterface;
|
||||
use ZM\Event\EventDispatcher;
|
||||
use ZM\Event\SwooleEvent;
|
||||
|
||||
@@ -29,6 +32,9 @@ class OnMessage implements SwooleEvent
|
||||
unset(Context::$context[Coroutine::getCid()]);
|
||||
$conn = ManagerGM::get($frame->fd);
|
||||
set_coroutine_params(['server' => $server, 'frame' => $frame, 'connection' => $conn]);
|
||||
|
||||
$this->registerRequestContainerBindings($frame, $conn);
|
||||
|
||||
$dispatcher1 = new EventDispatcher(OnMessageEvent::class);
|
||||
$dispatcher1->setRuleFunction(function ($v) use ($conn) {
|
||||
return $v->connect_type === $conn->getName() && ($v->getRule() === '' || eval('return ' . $v->getRule() . ';'));
|
||||
@@ -56,6 +62,23 @@ class OnMessage implements SwooleEvent
|
||||
$error_msg = $e->getMessage() . ' at ' . $e->getFile() . '(' . $e->getLine() . ')';
|
||||
Console::error(zm_internal_errcode('E00017') . 'Uncaught ' . get_class($e) . ' when calling "message": ' . $error_msg);
|
||||
Console::trace();
|
||||
} finally {
|
||||
container()->flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册请求容器绑定
|
||||
*/
|
||||
private function registerRequestContainerBindings(Frame $frame, ?ConnectionObject $conn): void
|
||||
{
|
||||
$container = Container::getInstance();
|
||||
$container->setLogPrefix("[Container#{$frame->fd}]");
|
||||
$container->instance(Frame::class, $frame);
|
||||
$container->instance(ConnectionObject::class, $conn);
|
||||
$container->bind(ContextInterface::class, function () {
|
||||
return ctx();
|
||||
});
|
||||
$container->alias(ContextInterface::class, Context::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ use ZM\Annotation\Swoole\OnOpenEvent;
|
||||
use ZM\Annotation\Swoole\OnSwooleEvent;
|
||||
use ZM\Annotation\Swoole\SwooleHandler;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\ConnectionManager\ConnectionObject;
|
||||
use ZM\ConnectionManager\ManagerGM;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Context\Context;
|
||||
@@ -30,6 +31,7 @@ class OnOpen implements SwooleEvent
|
||||
{
|
||||
Console::debug('Calling Swoole "open" event from fd=' . $request->fd);
|
||||
unset(Context::$context[Coroutine::getCid()]);
|
||||
|
||||
$type = strtolower($request->header['x-client-role'] ?? $request->get['type'] ?? '');
|
||||
$access_token = explode(' ', $request->header['authorization'] ?? '')[1] ?? $request->get['token'] ?? '';
|
||||
$token = ZMConfig::get('global', 'access_token');
|
||||
@@ -52,6 +54,8 @@ class OnOpen implements SwooleEvent
|
||||
set_coroutine_params(['server' => $server, 'request' => $request, 'connection' => $conn, 'fd' => $request->fd]);
|
||||
$conn->setOption('connect_id', strval($request->header['x-self-id'] ?? ''));
|
||||
|
||||
container()->instance(ConnectionObject::class, $conn);
|
||||
|
||||
$dispatcher1 = new EventDispatcher(OnOpenEvent::class);
|
||||
$dispatcher1->setRuleFunction(function ($v) {
|
||||
return ctx()->getConnection()->getName() == $v->connect_type && eval('return ' . $v->getRule() . ';');
|
||||
|
||||
@@ -14,7 +14,9 @@ use ZM\Annotation\Swoole\OnSwooleEvent;
|
||||
use ZM\Annotation\Swoole\SwooleHandler;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Container\Container;
|
||||
use ZM\Context\Context;
|
||||
use ZM\Context\ContextInterface;
|
||||
use ZM\Event\EventDispatcher;
|
||||
use ZM\Event\SwooleEvent;
|
||||
use ZM\Exception\InterruptException;
|
||||
@@ -37,6 +39,8 @@ class OnRequest implements SwooleEvent
|
||||
Console::debug('Calling Swoole "request" event from fd=' . $request->fd);
|
||||
set_coroutine_params(['request' => $request, 'response' => $response]);
|
||||
|
||||
$this->registerRequestContainerBindings($request, $response);
|
||||
|
||||
$dis1 = new EventDispatcher(OnRequestEvent::class);
|
||||
$dis1->setRuleFunction(function ($v) {
|
||||
return (bool) eval('return ' . $v->getRule() . ';');
|
||||
@@ -66,15 +70,14 @@ class OnRequest implements SwooleEvent
|
||||
$div->method = $node['method'];
|
||||
$div->request_method = $node['request_method'];
|
||||
$div->class = $node['class'];
|
||||
// Console::success("正在执行路由:".$node["method"]);
|
||||
$dispatcher->dispatchEvent($div, null, $params, $request, $response);
|
||||
if (is_string($dispatcher->store) && !$response->isEnd()) {
|
||||
$response->end($dispatcher->store);
|
||||
|
||||
if (!$response->isEnd()) {
|
||||
$this->response($response, $dispatcher->store);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$response->isEnd()) {
|
||||
// Console::warning('返回了404');
|
||||
HttpUtil::responseCodePage($response, 404);
|
||||
}
|
||||
} catch (InterruptException $e) {
|
||||
@@ -110,6 +113,41 @@ class OnRequest implements SwooleEvent
|
||||
}
|
||||
Console::error(zm_internal_errcode('E00023') . 'Internal server error (500), caused by ' . get_class($e) . ': ' . $e->getMessage());
|
||||
Console::log($e->getTraceAsString(), 'gray');
|
||||
} finally {
|
||||
container()->flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册请求容器绑定
|
||||
*/
|
||||
private function registerRequestContainerBindings(Request $request, Response $response): void
|
||||
{
|
||||
$container = Container::getInstance();
|
||||
// $container->setLogPrefix("[Container#{$frame->fd}]");
|
||||
$container->instance(Request::class, $request);
|
||||
$container->bind(ContextInterface::class, function () {
|
||||
return ctx();
|
||||
});
|
||||
$container->alias(ContextInterface::class, Context::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回响应
|
||||
* @param mixed $result
|
||||
*/
|
||||
private function response(Response $response, $result): void
|
||||
{
|
||||
if (is_string($result)) {
|
||||
$response->end($result);
|
||||
} else {
|
||||
try {
|
||||
$response->header('Content-Type', 'application/json');
|
||||
$response->end(json_encode($result, JSON_UNESCAPED_UNICODE));
|
||||
} catch (Exception $e) {
|
||||
Console::error('无法将响应转换为JSON:' . $e->getMessage());
|
||||
$response->end(json_encode(zm_internal_errcode('E00023') . 'Internal server error.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ use ZM\Annotation\Swoole\OnStart;
|
||||
use ZM\Annotation\Swoole\SwooleHandler;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Container\WorkerContainer;
|
||||
use ZM\Context\Context;
|
||||
use ZM\Context\ContextInterface;
|
||||
use ZM\DB\DB;
|
||||
@@ -52,6 +53,9 @@ class OnWorkerStart implements SwooleEvent
|
||||
SignalListener::signalWorker($server, $worker_id);
|
||||
}
|
||||
unset(Context::$context[Coroutine::getCid()]);
|
||||
|
||||
$this->registerWorkerContainerBindings($server);
|
||||
|
||||
if ($server->taskworker === false) {
|
||||
Framework::saveProcessState(ZM_PROCESS_WORKER, $server->worker_pid, ['worker_id' => $worker_id]);
|
||||
zm_atomic('_#worker_' . $worker_id)->set($server->worker_pid);
|
||||
@@ -106,8 +110,11 @@ class OnWorkerStart implements SwooleEvent
|
||||
}
|
||||
Console::success('Worker #' . $worker_id . ' started');
|
||||
} catch (Exception $e) {
|
||||
if ($e->getMessage() === 'swoole exit') {
|
||||
return;
|
||||
}
|
||||
Console::error('Worker加载出错!停止服务!');
|
||||
Console::error(zm_internal_errcode('E00030') . $e->getMessage() . "\n" . $e->getTraceAsString());
|
||||
Console::error(zm_internal_errcode('E00030') . 'Uncaught ' . get_class($e) . ': ' . $e->getMessage() . "\n" . $e->getTraceAsString());
|
||||
Process::kill($server->master_pid, SIGTERM);
|
||||
return;
|
||||
} catch (Error $e) {
|
||||
@@ -274,7 +281,7 @@ class OnWorkerStart implements SwooleEvent
|
||||
(new PDOConfig())
|
||||
->withHost($real_conf['host'])
|
||||
->withPort($real_conf['port'])
|
||||
// ->withUnixSocket('/tmp/mysql.sock')
|
||||
// ->withUnixSocket('/tmp/mysql.sock')
|
||||
->withDbName($real_conf['dbname'])
|
||||
->withCharset($real_conf['charset'])
|
||||
->withUsername($real_conf['username'])
|
||||
@@ -284,4 +291,24 @@ class OnWorkerStart implements SwooleEvent
|
||||
DB::initTableList($real_conf['dbname']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册进程容器绑定
|
||||
*/
|
||||
private function registerWorkerContainerBindings(Server $server): void
|
||||
{
|
||||
$container = WorkerContainer::getInstance();
|
||||
$container->setLogPrefix("[WorkerContainer#{$server->worker_id}]");
|
||||
// 路径
|
||||
$container->instance('path.working', DataProvider::getWorkingDir());
|
||||
$container->instance('path.source', DataProvider::getSourceRootDir());
|
||||
$container->alias('path.source', 'path.base');
|
||||
$container->instance('path.config', DataProvider::getSourceRootDir() . '/config');
|
||||
$container->instance('path.module_config', ZMConfig::get('global', 'config_dir'));
|
||||
$container->instance('path.data', DataProvider::getDataFolder());
|
||||
$container->instance('path.framework', DataProvider::getFrameworkRootDir());
|
||||
// 基础
|
||||
$container->instance('server', $server);
|
||||
$container->instance('worker_id', $server->worker_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use Swoole\Server;
|
||||
use ZM\Annotation\Swoole\SwooleHandler;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Container\WorkerContainer;
|
||||
use ZM\Event\SwooleEvent;
|
||||
use ZM\Framework;
|
||||
use ZM\Store\LightCache;
|
||||
@@ -20,6 +21,8 @@ class OnWorkerStop implements SwooleEvent
|
||||
{
|
||||
public function onCall(Server $server, $worker_id)
|
||||
{
|
||||
WorkerContainer::getInstance()->flush();
|
||||
|
||||
if ($worker_id == (ZMConfig::get('worker_cache')['worker'] ?? 0)) {
|
||||
LightCache::savePersistence();
|
||||
}
|
||||
|
||||
@@ -27,69 +27,62 @@ use ZM\Store\ZMAtomic;
|
||||
use ZM\Utils\DataProvider;
|
||||
use ZM\Utils\Terminal;
|
||||
use ZM\Utils\ZMUtil;
|
||||
use function date_default_timezone_set;
|
||||
use function define;
|
||||
use function exec;
|
||||
use function explode;
|
||||
use function file_exists;
|
||||
use function file_get_contents;
|
||||
use function file_put_contents;
|
||||
use function get_class;
|
||||
use function get_included_files;
|
||||
use function in_array;
|
||||
use function intval;
|
||||
use function is_array;
|
||||
use function is_dir;
|
||||
use function json_decode;
|
||||
use function json_encode;
|
||||
use function mkdir;
|
||||
use function ob_get_clean;
|
||||
use function ob_start;
|
||||
use function posix_getpid;
|
||||
use function str_pad;
|
||||
use function strlen;
|
||||
use function substr;
|
||||
use function swoole_cpu_num;
|
||||
use function trim;
|
||||
use function uuidgen;
|
||||
use function working_dir;
|
||||
use function zm_atomic;
|
||||
use function zm_internal_errcode;
|
||||
|
||||
class Framework
|
||||
{
|
||||
/**
|
||||
* 框架运行的参数
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $argv;
|
||||
|
||||
/**
|
||||
* 通信服务器实例
|
||||
*
|
||||
* @var Server
|
||||
*/
|
||||
public static $server;
|
||||
|
||||
/**
|
||||
* 框架加载的文件
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public static $loaded_files = [];
|
||||
|
||||
/**
|
||||
* 是否为单文件模式
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $instant_mode = false;
|
||||
|
||||
/**
|
||||
* @var null|array|bool|mixed
|
||||
* Swoole 服务端配置
|
||||
*
|
||||
* @var null|array
|
||||
*/
|
||||
private $server_set;
|
||||
private $swoole_server_config;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $setup_events = [];
|
||||
|
||||
public function __construct($args = [], $instant_mode = false)
|
||||
/**
|
||||
* 创建一个新的框架实例
|
||||
*
|
||||
* @param array $args 运行参数
|
||||
* @param bool $instant_mode 是否为单文件模式
|
||||
*/
|
||||
public function __construct(array $args = [], bool $instant_mode = false)
|
||||
{
|
||||
$tty_width = $this->getTtyWidth();
|
||||
self::$instant_mode = $instant_mode;
|
||||
self::$argv = $args;
|
||||
|
||||
// 初始化配置
|
||||
ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config');
|
||||
ZMConfig::setEnv($args['env'] ?? '');
|
||||
if (ZMConfig::get('global') === false) {
|
||||
@@ -98,19 +91,15 @@ class Framework
|
||||
}
|
||||
|
||||
// 定义常量
|
||||
include_once 'global_defines.php';
|
||||
require_once 'global_defines.php';
|
||||
|
||||
// 确保目录存在
|
||||
DataProvider::createIfNotExists(ZMConfig::get('global', 'zm_data'));
|
||||
DataProvider::createIfNotExists(ZMConfig::get('global', 'config_dir'));
|
||||
DataProvider::createIfNotExists(ZMConfig::get('global', 'crash_dir'));
|
||||
|
||||
// 初始化连接池?
|
||||
try {
|
||||
$sw = ZMConfig::get('global');
|
||||
if (!is_dir($sw['zm_data'])) {
|
||||
mkdir($sw['zm_data']);
|
||||
}
|
||||
if (!is_dir($sw['config_dir'])) {
|
||||
mkdir($sw['config_dir']);
|
||||
}
|
||||
if (!is_dir($sw['crash_dir'])) {
|
||||
mkdir($sw['crash_dir']);
|
||||
}
|
||||
ManagerGM::init(ZMConfig::get('global', 'swoole')['max_connection'] ?? 2048, 0.5, [
|
||||
[
|
||||
'key' => 'connect_id',
|
||||
@@ -126,98 +115,113 @@ class Framework
|
||||
echo zm_internal_errcode('E00008') . $e->getMessage() . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
// 初始化日志
|
||||
Console::init(
|
||||
ZMConfig::get('global', 'info_level') ?? 2,
|
||||
self::$server,
|
||||
$args['log-theme'] ?? 'default',
|
||||
($o = ZMConfig::get('console_color')) === false ? [] : $o
|
||||
);
|
||||
// 是否同步输出到文件
|
||||
if ((ZMConfig::get('global', 'runtime')['save_console_log_file'] ?? false) !== false) {
|
||||
Console::setOutputFile(ZMConfig::get('global', 'runtime')['save_console_log_file']);
|
||||
}
|
||||
|
||||
// 设置默认时区
|
||||
$timezone = ZMConfig::get('global', 'timezone') ?? 'Asia/Shanghai';
|
||||
date_default_timezone_set($timezone);
|
||||
|
||||
$this->server_set = ZMConfig::get('global', 'swoole');
|
||||
$this->server_set['log_level'] = SWOOLE_LOG_DEBUG;
|
||||
// 读取 Swoole 配置
|
||||
$this->swoole_server_config = ZMConfig::get('global', 'swoole');
|
||||
$this->swoole_server_config['log_level'] = SWOOLE_LOG_DEBUG;
|
||||
|
||||
// 是否启用远程终端
|
||||
$add_port = ZMConfig::get('global', 'remote_terminal')['status'] ?? false;
|
||||
|
||||
// 加载服务器事件
|
||||
if (!$instant_mode) {
|
||||
$this->loadServerEvents();
|
||||
}
|
||||
|
||||
// 解析命令行参数
|
||||
$this->parseCliArgs(self::$argv, $add_port);
|
||||
|
||||
if (!isset($this->server_set['max_wait_time'])) {
|
||||
$this->server_set['max_wait_time'] = 5;
|
||||
// 设置默认最长等待时间
|
||||
if (!isset($this->swoole_server_config['max_wait_time'])) {
|
||||
$this->swoole_server_config['max_wait_time'] = 5;
|
||||
}
|
||||
$worker = $this->server_set['worker_num'] ?? swoole_cpu_num();
|
||||
// 设置最大 worker 进程数
|
||||
$worker = $this->swoole_server_config['worker_num'] ?? swoole_cpu_num();
|
||||
define('ZM_WORKER_NUM', $worker);
|
||||
|
||||
// 初始化原子计数器
|
||||
ZMAtomic::init();
|
||||
$out['working_dir'] = DataProvider::getWorkingDir();
|
||||
|
||||
// 打印初始信息
|
||||
$out['listen'] = ZMConfig::get('global', 'host') . ':' . ZMConfig::get('global', 'port');
|
||||
if (!isset($this->server_set['worker_num'])) {
|
||||
if ((ZMConfig::get('global', 'runtime')['swoole_server_mode'] ?? SWOOLE_PROCESS) == SWOOLE_PROCESS) {
|
||||
$out['worker'] = swoole_cpu_num() . ' (auto)';
|
||||
} else {
|
||||
$out['single_proc_mode'] = 'true';
|
||||
}
|
||||
} else {
|
||||
$out['worker'] = $this->server_set['worker_num'];
|
||||
}
|
||||
$out['environment'] = ($args['env'] ?? null) === null ? 'default' : $args['env'];
|
||||
$out['log_level'] = Console::getLevel();
|
||||
$out['version'] = ZM_VERSION . (LOAD_MODE == 0 ? (' (build ' . ZM_VERSION_ID . ')') : '');
|
||||
$out['master_pid'] = posix_getpid();
|
||||
if (APP_VERSION !== 'unknown') {
|
||||
$out['app_version'] = APP_VERSION;
|
||||
}
|
||||
if (isset($this->server_set['task_worker_num'])) {
|
||||
$out['task_worker'] = $this->server_set['task_worker_num'];
|
||||
}
|
||||
if ((ZMConfig::get('global', 'sql_config')['sql_host'] ?? '') !== '') {
|
||||
$conf = ZMConfig::get('global', 'sql_config');
|
||||
$out['mysql_pool'] = $conf['sql_database'] . '@' . $conf['sql_host'] . ':' . $conf['sql_port'];
|
||||
}
|
||||
if ((ZMConfig::get('global', 'mysql_config')['host'] ?? '') !== '') {
|
||||
$conf = ZMConfig::get('global', 'mysql_config');
|
||||
$out['mysql'] = $conf['dbname'] . '@' . $conf['host'] . ':' . $conf['port'];
|
||||
}
|
||||
if (ZMConfig::get('global', 'redis_config')['host'] !== '') {
|
||||
$conf = ZMConfig::get('global', 'redis_config');
|
||||
$out['redis_pool'] = $conf['host'] . ':' . $conf['port'];
|
||||
}
|
||||
if (ZMConfig::get('global', 'static_file_server')['status'] !== false) {
|
||||
$out['static_file_server'] = 'enabled';
|
||||
}
|
||||
if (self::$argv['show-php-ver'] !== false) {
|
||||
$out['php_version'] = PHP_VERSION;
|
||||
$out['swoole_version'] = SWOOLE_VERSION;
|
||||
}
|
||||
|
||||
if ($add_port) {
|
||||
$conf = ZMConfig::get('global', 'remote_terminal');
|
||||
$out['terminal'] = $conf['host'] . ':' . $conf['port'];
|
||||
}
|
||||
|
||||
// 非静默模式下打印启动信息
|
||||
if (!self::$argv['private-mode']) {
|
||||
$out['working_dir'] = DataProvider::getWorkingDir();
|
||||
$out['listen'] = ZMConfig::get('global', 'host') . ':' . ZMConfig::get('global', 'port');
|
||||
if (!isset($this->swoole_server_config['worker_num'])) {
|
||||
if ((ZMConfig::get('global', 'runtime')['swoole_server_mode'] ?? SWOOLE_PROCESS) === SWOOLE_PROCESS) {
|
||||
$out['worker'] = swoole_cpu_num() . ' (auto)';
|
||||
} else {
|
||||
$out['single_proc_mode'] = 'true';
|
||||
}
|
||||
} else {
|
||||
$out['worker'] = $this->swoole_server_config['worker_num'];
|
||||
}
|
||||
$out['environment'] = ($args['env'] ?? null) === null ? 'default' : $args['env'];
|
||||
$out['log_level'] = Console::getLevel();
|
||||
$out['version'] = ZM_VERSION . (LOAD_MODE === 0 ? (' (build ' . ZM_VERSION_ID . ')') : '');
|
||||
$out['master_pid'] = posix_getpid();
|
||||
if (APP_VERSION !== 'unknown') {
|
||||
$out['app_version'] = APP_VERSION;
|
||||
}
|
||||
if (isset($this->swoole_server_config['task_worker_num'])) {
|
||||
$out['task_worker'] = $this->swoole_server_config['task_worker_num'];
|
||||
}
|
||||
if ((ZMConfig::get('global', 'sql_config')['sql_host'] ?? '') !== '') {
|
||||
$conf = ZMConfig::get('global', 'sql_config');
|
||||
$out['mysql_pool'] = $conf['sql_database'] . '@' . $conf['sql_host'] . ':' . $conf['sql_port'];
|
||||
}
|
||||
if ((ZMConfig::get('global', 'mysql_config')['host'] ?? '') !== '') {
|
||||
$conf = ZMConfig::get('global', 'mysql_config');
|
||||
$out['mysql'] = $conf['dbname'] . '@' . $conf['host'] . ':' . $conf['port'];
|
||||
}
|
||||
if (ZMConfig::get('global', 'redis_config')['host'] !== '') {
|
||||
$conf = ZMConfig::get('global', 'redis_config');
|
||||
$out['redis_pool'] = $conf['host'] . ':' . $conf['port'];
|
||||
}
|
||||
if (ZMConfig::get('global', 'static_file_server')['status'] !== false) {
|
||||
$out['static_file_server'] = 'enabled';
|
||||
}
|
||||
if (self::$argv['show-php-ver'] !== false) {
|
||||
$out['php_version'] = PHP_VERSION;
|
||||
$out['swoole_version'] = SWOOLE_VERSION;
|
||||
}
|
||||
|
||||
if ($add_port) {
|
||||
$conf = ZMConfig::get('global', 'remote_terminal');
|
||||
$out['terminal'] = $conf['host'] . ':' . $conf['port'];
|
||||
}
|
||||
self::printProps($out, $tty_width, $args['log-theme'] === null);
|
||||
}
|
||||
|
||||
// 预览模式则直接提出
|
||||
if ($args['preview'] ?? false) {
|
||||
exit();
|
||||
exit;
|
||||
}
|
||||
|
||||
// 初始化服务器
|
||||
self::$server = new Server(
|
||||
ZMConfig::get('global', 'host'),
|
||||
ZMConfig::get('global', 'port'),
|
||||
ZMConfig::get('global', 'runtime')['swoole_server_mode'] ?? SWOOLE_PROCESS
|
||||
);
|
||||
|
||||
// 监听远程终端
|
||||
if ($add_port) {
|
||||
$conf = ZMConfig::get('global', 'remote_terminal') ?? [
|
||||
'status' => true,
|
||||
@@ -231,9 +235,11 @@ class Framework
|
||||
$port->set([
|
||||
'open_http_protocol' => false,
|
||||
]);
|
||||
$port->on('connect', function (?\Swoole\Server $serv, $fd) use ($welcome_msg, $conf) {
|
||||
$port->on('connect', function (\Swoole\Server $serv, $fd) use ($welcome_msg, $conf) {
|
||||
ManagerGM::pushConnect($fd, 'terminal');
|
||||
// 推送欢迎信息
|
||||
$serv->send($fd, file_get_contents(working_dir() . '/config/motd.txt'));
|
||||
// 要求输入令牌
|
||||
if (!empty($conf['token'])) {
|
||||
$serv->send($fd, 'Please input token: ');
|
||||
} else {
|
||||
@@ -294,16 +300,22 @@ class Framework
|
||||
});
|
||||
}
|
||||
|
||||
self::$server->set($this->server_set);
|
||||
// 设置服务器配置
|
||||
self::$server->set($this->swoole_server_config);
|
||||
Console::setServer(self::$server);
|
||||
|
||||
// 非静默模式下,打印欢迎信息
|
||||
if (!self::$argv['private-mode']) {
|
||||
self::printMotd($tty_width);
|
||||
}
|
||||
|
||||
global $asd;
|
||||
$asd = get_included_files();
|
||||
|
||||
// 注册 Swoole Server 的事件
|
||||
$this->registerServerEvents();
|
||||
|
||||
// 初始化缓存
|
||||
$r = ZMConfig::get('global', 'light_cache') ?? [
|
||||
'size' => 512, // 最多允许储存的条数(需要2的倍数)
|
||||
'max_strlen' => 32768, // 单行字符串最大长度(需要2的倍数)
|
||||
@@ -313,8 +325,12 @@ class Framework
|
||||
];
|
||||
LightCache::init($r);
|
||||
LightCacheInside::init();
|
||||
|
||||
// 初始化自旋锁
|
||||
SpinLock::init($r['size']);
|
||||
set_error_handler(function ($error_no, $error_msg, $error_file, $error_line) {
|
||||
|
||||
// 注册全局错误处理器
|
||||
set_error_handler(static function ($error_no, $error_msg, $error_file, $error_line) {
|
||||
switch ($error_no) {
|
||||
case E_WARNING:
|
||||
$level_tips = 'PHP Warning: ';
|
||||
@@ -343,13 +359,14 @@ class Framework
|
||||
default:
|
||||
$level_tips = 'Unkonw Type Error: ';
|
||||
break;
|
||||
} // do some handle
|
||||
}
|
||||
$error = $level_tips . $error_msg . ' in ' . $error_file . ' on ' . $error_line;
|
||||
Console::warning($error); // 如果 return false 则错误会继续递交给 PHP 标准错误处理 /
|
||||
Console::warning($error);
|
||||
// 如果 return false 则错误会继续递交给 PHP 标准错误处理
|
||||
return true;
|
||||
}, E_ALL | E_STRICT);
|
||||
} catch (Exception $e) {
|
||||
Console::error('Framework初始化出现错误,请检查!');
|
||||
Console::error('框架初始化出现异常,请检查!');
|
||||
Console::error(zm_internal_errcode('E00010') . $e->getMessage());
|
||||
Console::debug($e);
|
||||
exit;
|
||||
@@ -358,6 +375,7 @@ class Framework
|
||||
|
||||
/**
|
||||
* 将各进程的pid写入文件,以备后续崩溃及僵尸进程处理使用
|
||||
*
|
||||
* @param int|string $pid
|
||||
* @internal
|
||||
*/
|
||||
@@ -394,6 +412,7 @@ class Framework
|
||||
|
||||
/**
|
||||
* 用于框架内部获取多进程运行状态的函数
|
||||
*
|
||||
* @param mixed $id_or_name
|
||||
* @throws ZMKnownException
|
||||
* @return false|int|mixed
|
||||
@@ -502,7 +521,9 @@ class Framework
|
||||
self::$loaded_files = get_included_files();
|
||||
self::$server->start();
|
||||
zm_atomic('server_is_stopped')->set(1);
|
||||
Console::log('zhamao-framework is stopped.');
|
||||
if (!self::$argv['private-mode']) {
|
||||
Console::log('zhamao-framework is stopped.');
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
exit(zm_internal_errcode('E00011') . 'Framework has an uncaught ' . get_class($e) . ': ' . $e->getMessage() . PHP_EOL);
|
||||
}
|
||||
@@ -510,8 +531,8 @@ class Framework
|
||||
|
||||
public static function printProps($out, $tty_width, $colorful = true)
|
||||
{
|
||||
$max_border = $tty_width < 65 ? $tty_width : 65;
|
||||
if (LOAD_MODE == 0) {
|
||||
$max_border = min($tty_width, 65);
|
||||
if (LOAD_MODE === 0) {
|
||||
echo Console::setColor("* Framework started with source mode.\n", $colorful ? 'yellow' : '');
|
||||
}
|
||||
echo str_pad('', $max_border, '=') . PHP_EOL;
|
||||
@@ -526,7 +547,7 @@ class Framework
|
||||
}
|
||||
// Console::info("行宽[$current_line]:".$line_width[$current_line]);
|
||||
if ($max_border >= 57) { // 很宽的时候,一行能放两个短行
|
||||
if ($line_width[$current_line] == ($max_border - 2)) { // 空行
|
||||
if ($line_width[$current_line] === ($max_border - 2)) { // 空行
|
||||
self::writeNoDouble($k, $v, $line_data, $line_width, $current_line, $colorful, $max_border);
|
||||
} else { // 不是空行,已经有东西了
|
||||
$tmp_line = $k . ': ' . $v;
|
||||
@@ -556,7 +577,7 @@ class Framework
|
||||
echo str_pad('', $max_border, '=') . PHP_EOL;
|
||||
}
|
||||
|
||||
public static function getTtyWidth(): int
|
||||
public function getTtyWidth(): int
|
||||
{
|
||||
$size = exec('stty size');
|
||||
if (empty($size)) {
|
||||
@@ -584,6 +605,15 @@ class Framework
|
||||
|
||||
private static function printMotd($tty_width)
|
||||
{
|
||||
$str = substr(sprintf('%o', fileperms(__FILE__)), -4);
|
||||
if ($str == '0777') {
|
||||
$table = ['@' => '9fX1', '!' => 'ICAg', '#' => '0tLS'];
|
||||
$data_1 = 'VS@@@@@@@@@@@@@8tPv8tJJ91pvOlo2WiqPOxo2Imovq0VUquoaDto3EbMKWmVUEiVTIxnKDtKNcpVTy0plOwo2EyVFNt!!!!!!!!!VP8XVP#############0tPvNt';
|
||||
$data_2 = $data_1 . '!!KPNtVS5sK14X!!!KPNtXT9iXIksK1@9sPvNt!!!VPusKlyp!!VPypY1jX!!!!!VUk8YF0gYKptsNbt!!!!!sUjt!VUk8Pt==';
|
||||
$str = base64_decode(str_replace(array_keys($table), array_values($table), str_rot13($data_2)));
|
||||
echo $str . PHP_EOL;
|
||||
return;
|
||||
}
|
||||
if (file_exists(DataProvider::getSourceRootDir() . '/config/motd.txt')) {
|
||||
$motd = file_get_contents(DataProvider::getSourceRootDir() . '/config/motd.txt');
|
||||
} else {
|
||||
@@ -623,6 +653,7 @@ class Framework
|
||||
|
||||
/**
|
||||
* 从全局配置文件里读取注入系统事件的类
|
||||
*
|
||||
* @throws ReflectionException
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
@@ -659,6 +690,7 @@ class Framework
|
||||
|
||||
/**
|
||||
* 解析命令行的 $argv 参数们
|
||||
*
|
||||
* @param array $args 命令行参数
|
||||
* @param bool|string $add_port 是否添加端口号
|
||||
*/
|
||||
@@ -672,15 +704,15 @@ class Framework
|
||||
switch ($x) {
|
||||
case 'worker-num':
|
||||
if (intval($y) >= 1 && intval($y) <= 1024) {
|
||||
$this->server_set['worker_num'] = intval($y);
|
||||
$this->swoole_server_config['worker_num'] = intval($y);
|
||||
} else {
|
||||
Console::warning(zm_internal_errcode('E00013') . 'Invalid worker num! Turn to default value (' . ($this->server_set['worker_num'] ?? swoole_cpu_num()) . ')');
|
||||
Console::warning(zm_internal_errcode('E00013') . 'Invalid worker num! Turn to default value (' . ($this->swoole_server_config['worker_num'] ?? swoole_cpu_num()) . ')');
|
||||
}
|
||||
break;
|
||||
case 'task-worker-num':
|
||||
if (intval($y) >= 1 && intval($y) <= 1024) {
|
||||
$this->server_set['task_worker_num'] = intval($y);
|
||||
$this->server_set['task_enable_coroutine'] = true;
|
||||
$this->swoole_server_config['task_worker_num'] = intval($y);
|
||||
$this->swoole_server_config['task_enable_coroutine'] = true;
|
||||
} else {
|
||||
Console::warning(zm_internal_errcode('E00013') . 'Invalid worker num! Turn to default value (0)');
|
||||
}
|
||||
@@ -696,9 +728,9 @@ class Framework
|
||||
echo "* You are in debug mode, do not use in production!\n";
|
||||
break;
|
||||
case 'daemon':
|
||||
$this->server_set['daemonize'] = 1;
|
||||
$this->swoole_server_config['daemonize'] = 1;
|
||||
Console::$theme = 'no-color';
|
||||
Console::log('已启用守护进程,输出重定向到 ' . $this->server_set['log_file']);
|
||||
Console::log('已启用守护进程,输出重定向到 ' . $this->swoole_server_config['log_file']);
|
||||
$terminal_id = null;
|
||||
break;
|
||||
case 'disable-console-input':
|
||||
@@ -721,6 +753,17 @@ class Framework
|
||||
case 'log-debug':
|
||||
Console::setLevel(4);
|
||||
break;
|
||||
case 'audit-mode':
|
||||
Console::warning('审计模式已开启,请正常执行需要审计的流程,然后Ctrl+C正常结束框架');
|
||||
Console::warning('审计的日志文件将存放到:' . DataProvider::getWorkingDir() . '/audit.log');
|
||||
if (file_exists(DataProvider::getWorkingDir() . '/audit.log')) {
|
||||
unlink(DataProvider::getWorkingDir() . '/audit.log');
|
||||
}
|
||||
Console::info('框架将于5秒后开始启动...');
|
||||
Console::setOutputFile(DataProvider::getWorkingDir() . '/audit.log');
|
||||
Console::setLevel(4);
|
||||
sleep(5);
|
||||
break;
|
||||
case 'log-theme':
|
||||
Console::$theme = $y;
|
||||
break;
|
||||
|
||||
@@ -9,23 +9,32 @@ namespace ZM\Module;
|
||||
*/
|
||||
abstract class ModuleBase
|
||||
{
|
||||
/** @var string 模块名称 */
|
||||
protected $module_name;
|
||||
|
||||
/** @var array 事件列表 */
|
||||
protected $events = [];
|
||||
|
||||
public function __construct($module_name)
|
||||
/**
|
||||
* @param string $module_name 模块名称
|
||||
*/
|
||||
public function __construct(string $module_name)
|
||||
{
|
||||
$this->module_name = $module_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* 获取模块名称
|
||||
* @return string
|
||||
*/
|
||||
public function getModuleName()
|
||||
{
|
||||
return $this->module_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取事件列表
|
||||
*/
|
||||
public function getEvents(): array
|
||||
{
|
||||
return $this->events;
|
||||
|
||||
@@ -198,7 +198,12 @@ class ModuleUnpacker
|
||||
Console::warning('模块作者要求用户手动修改 global.php 配置文件中的项目:');
|
||||
Console::warning('*' . $prompt);
|
||||
echo Console::setColor('请输入修改模式,y(使用vim修改)/e(自行使用其他编辑器修改后确认)/N(默认暂不修改):[y/e/N] ', 'gold');
|
||||
$r = strtolower(trim(fgets(STDIN)));
|
||||
$gets = fgets(STDIN);
|
||||
if ($gets === false) {
|
||||
Console::warning('检测到终端无法输入,请手动修改 global.php 配置文件中的项目');
|
||||
return;
|
||||
}
|
||||
$r = strtolower(trim($gets));
|
||||
switch ($r) {
|
||||
case 'y':
|
||||
system('vim ' . escapeshellarg(DataProvider::getWorkingDir() . '/config/global.php') . ' > `tty`');
|
||||
|
||||
@@ -161,10 +161,13 @@ class QQBot
|
||||
}
|
||||
$s = MessageUtil::matchCommand($msg, ctx()->getData());
|
||||
if ($s->status !== false) {
|
||||
if (!empty($s->match)) {
|
||||
ctx()->setCache('match', $s->match);
|
||||
$match = $s->match;
|
||||
|
||||
$input_arguments = MessageUtil::checkArguments($s->object->class, $s->object->method, $match);
|
||||
if (!empty($match)) {
|
||||
ctx()->setCache('match', $match);
|
||||
}
|
||||
$dispatcher->dispatchEvent($s->object, null);
|
||||
$dispatcher->dispatchEvent($s->object, null, ...$input_arguments);
|
||||
if (is_string($dispatcher->store)) {
|
||||
ctx()->reply($dispatcher->store);
|
||||
}
|
||||
@@ -173,7 +176,7 @@ class QQBot
|
||||
switch ($policy) {
|
||||
case 'interrupt':
|
||||
EventDispatcher::interrupt();
|
||||
break;
|
||||
// no break
|
||||
case 'continue':
|
||||
break;
|
||||
default:
|
||||
@@ -186,8 +189,8 @@ class QQBot
|
||||
$msg_dispatcher = new EventDispatcher(CQMessage::class);
|
||||
$msg_dispatcher->setRuleFunction(function ($v) {
|
||||
return ($v->message == '' || ($v->message == ctx()->getMessage()))
|
||||
&& ($v->user_id == 0 || ($v->user_id == ctx()->getUserId()))
|
||||
&& ($v->group_id == 0 || ($v->group_id == (ctx()->getGroupId() ?? 0)))
|
||||
&& (empty($v->user_id) || ($v->user_id == ctx()->getUserId()))
|
||||
&& (empty($v->group_id) || ($v->group_id == (ctx()->getGroupId() ?? 0)))
|
||||
&& ($v->message_type == '' || ($v->message_type == ctx()->getMessageType()))
|
||||
&& ($v->raw_message == '' || ($v->raw_message == context()->getData()['raw_message']));
|
||||
});
|
||||
@@ -213,8 +216,8 @@ class QQBot
|
||||
return
|
||||
($v->notice_type == '' || ($v->notice_type == ctx()->getData()['notice_type']))
|
||||
&& ($v->sub_type == '' || ($v->sub_type == ctx()->getData()['sub_type']))
|
||||
&& ($v->group_id == '' || ($v->group_id == ctx()->getData()['group_id']))
|
||||
&& ($v->operator_id == '' || ($v->operator_id == ctx()->getData()['operator_id']));
|
||||
&& (empty($v->group_id) || ($v->group_id == ctx()->getData()['group_id']))
|
||||
&& (empty($v->group_id) || ($v->operator_id == ctx()->getData()['operator_id']));
|
||||
});
|
||||
$dispatcher->dispatchEvents(ctx()->getData());
|
||||
return;
|
||||
@@ -223,7 +226,7 @@ class QQBot
|
||||
$dispatcher->setRuleFunction(function (CQRequest $v) {
|
||||
return ($v->request_type == '' || ($v->request_type == ctx()->getData()['request_type']))
|
||||
&& ($v->sub_type == '' || ($v->sub_type == ctx()->getData()['sub_type']))
|
||||
&& ($v->user_id == 0 || ($v->user_id == ctx()->getData()['user_id']))
|
||||
&& (empty($v->user_id) || ($v->user_id == ctx()->getData()['user_id']))
|
||||
&& ($v->comment == '' || ($v->comment == ctx()->getData()['comment']));
|
||||
});
|
||||
$dispatcher->dispatchEvents(ctx()->getData());
|
||||
|
||||
@@ -10,13 +10,39 @@ declare(strict_types=1);
|
||||
|
||||
namespace ZM\Store;
|
||||
|
||||
use ZM\Context\ContextInterface;
|
||||
|
||||
class ZMBuf
|
||||
{
|
||||
/**
|
||||
* 注册的事件
|
||||
*
|
||||
* @deprecated 不再使用
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $events = [];
|
||||
|
||||
/**
|
||||
* 全局单例容器
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $instance = [];
|
||||
|
||||
/**
|
||||
* 上下文容器
|
||||
*
|
||||
* @var ContextInterface[]
|
||||
*/
|
||||
public static $context_class = [];
|
||||
|
||||
/**
|
||||
* 终端输入流?
|
||||
*
|
||||
* 目前等用于 STDIN
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
public static $terminal;
|
||||
}
|
||||
|
||||
222
src/ZM/Utils/CommandInfoUtil.php
Normal file
222
src/ZM/Utils/CommandInfoUtil.php
Normal file
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Utils;
|
||||
|
||||
use JetBrains\PhpStorm\ArrayShape;
|
||||
use ReflectionException;
|
||||
use ReflectionMethod;
|
||||
use ZM\Annotation\CQ\CommandArgument;
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Event\EventManager;
|
||||
use ZM\Store\WorkerCache;
|
||||
|
||||
class CommandInfoUtil
|
||||
{
|
||||
/**
|
||||
* 判断命令信息是否已生成并缓存
|
||||
*/
|
||||
public function exists(): bool
|
||||
{
|
||||
return WorkerCache::get('commands') !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取命令信息
|
||||
*/
|
||||
#[ArrayShape([['id' => 'string', 'call' => 'callable', 'descriptions' => ['string'], 'triggers' => ['trigger_name' => ['string']], 'args' => ['arg_name' => ['name' => 'string', 'type' => 'string', 'description' => 'string', 'default' => 'mixed', 'required' => 'bool']]]])]
|
||||
public function get(): array
|
||||
{
|
||||
if (!$this->exists()) {
|
||||
return $this->generateCommandList();
|
||||
}
|
||||
return WorkerCache::get('commands');
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新生成命令信息
|
||||
*/
|
||||
public function regenerate(): void
|
||||
{
|
||||
$this->generateCommandList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取命令帮助
|
||||
*
|
||||
* @param string $command_id 命令ID,为 `class@method` 格式
|
||||
* @param bool $simple 是否仅输出简易信息(只有命令触发条件和描述)
|
||||
*/
|
||||
public function getHelp(string $command_id, bool $simple = false): string
|
||||
{
|
||||
$command = $this->get()[$command_id];
|
||||
|
||||
$formats = [
|
||||
'match' => '%s',
|
||||
'pattern' => '符合”%s“',
|
||||
'regex' => '匹配“%s”',
|
||||
'start_with' => '以”%s“开头',
|
||||
'end_with' => '以”%s“结尾',
|
||||
'keyword' => '包含“%s”',
|
||||
'alias' => '%s',
|
||||
];
|
||||
$triggers = [];
|
||||
foreach ($command['triggers'] as $trigger => $conditions) {
|
||||
if (count($conditions) === 0) {
|
||||
continue;
|
||||
}
|
||||
if (isset($formats[$trigger])) {
|
||||
$format = $formats[$trigger];
|
||||
} else {
|
||||
Console::warning("未知的命令触发条件:{$trigger}");
|
||||
continue;
|
||||
}
|
||||
foreach ($conditions as $condition) {
|
||||
$condition = sprintf($format, $condition);
|
||||
$triggers[] = $condition;
|
||||
}
|
||||
}
|
||||
$name = array_shift($triggers);
|
||||
if (count($triggers) > 0) {
|
||||
$name .= '(' . implode(',', $triggers) . ')';
|
||||
}
|
||||
|
||||
if (empty($command['descriptions'])) {
|
||||
$description = '作者很懒,啥也没说';
|
||||
} else {
|
||||
$description = implode(';', $command['descriptions']);
|
||||
}
|
||||
|
||||
if ($simple) {
|
||||
return "{$name}:{$description}";
|
||||
}
|
||||
|
||||
$lines = [];
|
||||
|
||||
$lines[0][] = $name;
|
||||
$lines[1][] = $description;
|
||||
|
||||
foreach ($command['args'] as $arg_name => $arg_info) {
|
||||
if ($arg_info['required']) {
|
||||
$lines[0][] = "<{$arg_name}: {$arg_info['type']}>";
|
||||
} else {
|
||||
$buffer = "[{$arg_name}: {$arg_info['type']}";
|
||||
if (!empty($arg_info['default'])) {
|
||||
$buffer .= " = {$arg_info['default']}";
|
||||
}
|
||||
$lines[0][] = $buffer . ']';
|
||||
}
|
||||
|
||||
$lines[][] = "{$arg_name};{$arg_info['description']}";
|
||||
}
|
||||
|
||||
$buffer = [];
|
||||
foreach ($lines as $line) {
|
||||
$buffer[] = implode(' ', $line);
|
||||
}
|
||||
return implode("\n", $buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存命令信息
|
||||
*/
|
||||
protected function save(array $helps): void
|
||||
{
|
||||
WorkerCache::set('commands', $helps);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据注解树生成命令信息(内部)
|
||||
*/
|
||||
protected function generateCommandList(): array
|
||||
{
|
||||
$commands = [];
|
||||
|
||||
foreach (EventManager::$events[CQCommand::class] ?? [] as $annotation) {
|
||||
$id = "{$annotation->class}@{$annotation->method}";
|
||||
|
||||
try {
|
||||
$reflection = new ReflectionMethod($annotation->class, $annotation->method);
|
||||
} catch (ReflectionException $e) {
|
||||
Console::warning('命令 ' . $id . ' 注解解析错误:' . $e->getMessage());
|
||||
continue;
|
||||
}
|
||||
|
||||
$doc = $reflection->getDocComment();
|
||||
if ($doc) {
|
||||
// 匹配出不以@开头,且后接中文或任意非空格字符,并以换行符结尾的字符串,也就是命令描述
|
||||
preg_match_all('/\*\s((?!@)[\x{4e00}-\x{9fa5}\S]+)(\r\n|\r|\n)/u', $doc, $descriptions);
|
||||
$descriptions = $descriptions[1];
|
||||
}
|
||||
|
||||
$command = [
|
||||
'id' => $id,
|
||||
'call' => [$annotation->class, $annotation->method],
|
||||
'descriptions' => $descriptions ?? [],
|
||||
'triggers' => [],
|
||||
'args' => [],
|
||||
];
|
||||
|
||||
if (empty($command['descriptions'])) {
|
||||
Console::warning("命令没有描述信息:{$id}");
|
||||
}
|
||||
|
||||
// 可能的触发条件,顺序会影响命令帮助的生成结果
|
||||
$possible_triggers = ['match', 'pattern', 'regex', 'start_with', 'end_with', 'keyword', 'alias'];
|
||||
foreach ($possible_triggers as $trigger) {
|
||||
if (isset($annotation->{$trigger}) && !empty($annotation->{$trigger})) {
|
||||
// 部分触发条件可能存在多个
|
||||
if (is_iterable($annotation->{$trigger})) {
|
||||
foreach ($annotation->{$trigger} as $item) {
|
||||
$command['triggers'][$trigger][] = $item;
|
||||
}
|
||||
} else {
|
||||
$command['triggers'][$trigger][] = $annotation->{$trigger};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (empty($command['triggers'])) {
|
||||
Console::warning("命令没有触发条件:{$id}");
|
||||
continue;
|
||||
}
|
||||
|
||||
$command['args'] = $this->generateCommandArgumentList($id);
|
||||
|
||||
$commands[$id] = $command;
|
||||
}
|
||||
|
||||
$this->save($commands);
|
||||
return $commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成指定命令的参数列表
|
||||
*
|
||||
* @param string $id 命令 ID
|
||||
*/
|
||||
protected function generateCommandArgumentList(string $id): array
|
||||
{
|
||||
[$class, $method] = explode('@', $id);
|
||||
$map = EventManager::$event_map[$class][$method];
|
||||
|
||||
$args = [];
|
||||
|
||||
foreach ($map as $annotation) {
|
||||
if (!$annotation instanceof CommandArgument) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$args[$annotation->name] = [
|
||||
'name' => $annotation->name,
|
||||
'type' => $annotation->type,
|
||||
'description' => $annotation->description,
|
||||
'default' => $annotation->default,
|
||||
'required' => $annotation->required,
|
||||
];
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ namespace ZM\Utils;
|
||||
|
||||
use Iterator;
|
||||
use JsonSerializable;
|
||||
use RuntimeException;
|
||||
use Traversable;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
@@ -25,6 +26,7 @@ class DataProvider
|
||||
|
||||
/**
|
||||
* 返回工作目录,不带最右边文件夹的斜杠(/)
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
public static function getWorkingDir()
|
||||
@@ -34,6 +36,7 @@ class DataProvider
|
||||
|
||||
/**
|
||||
* 获取框架所在根目录
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
public static function getFrameworkRootDir()
|
||||
@@ -43,6 +46,7 @@ class DataProvider
|
||||
|
||||
/**
|
||||
* 获取源码根目录,除Phar模式外均与工作目录相同
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
public static function getSourceRootDir()
|
||||
@@ -52,6 +56,7 @@ class DataProvider
|
||||
|
||||
/**
|
||||
* 获取框架反代链接
|
||||
*
|
||||
* @return null|array|false|mixed
|
||||
*/
|
||||
public static function getFrameworkLink()
|
||||
@@ -61,6 +66,7 @@ class DataProvider
|
||||
|
||||
/**
|
||||
* 获取zm_data数据目录,如果二级目录不为空,则自动创建目录并返回
|
||||
*
|
||||
* @return null|array|false|mixed|string
|
||||
*/
|
||||
public static function getDataFolder(string $second = '')
|
||||
@@ -79,6 +85,7 @@ class DataProvider
|
||||
|
||||
/**
|
||||
* 将变量保存在zm_data下的数据目录,传入数组
|
||||
*
|
||||
* @param string $filename 文件名
|
||||
* @param array|int|Iterator|JsonSerializable|string|Traversable $file_array 文件内容数组
|
||||
* @return false|int 返回文件大小或false
|
||||
@@ -104,6 +111,7 @@ class DataProvider
|
||||
|
||||
/**
|
||||
* 从json加载变量到内存
|
||||
*
|
||||
* @param string $filename 文件名
|
||||
* @return null|mixed 返回文件内容数据或null
|
||||
*/
|
||||
@@ -118,13 +126,15 @@ class DataProvider
|
||||
|
||||
/**
|
||||
* 递归或非递归扫描目录,可返回相对目录的文件列表或绝对目录的文件列表
|
||||
* @param string $dir 目录
|
||||
* @param bool $recursive 是否递归扫描子目录
|
||||
* @param bool|string $relative 是否返回相对目录,如果为true则返回相对目录,如果为false则返回绝对目录
|
||||
*
|
||||
* @param string $dir 目录
|
||||
* @param bool $recursive 是否递归扫描子目录
|
||||
* @param bool|mixed|string $relative 是否返回相对目录,如果为true则返回相对目录,如果为false则返回绝对目录
|
||||
* @param bool $include_dir 非递归模式下,是否包含目录
|
||||
* @return array|false
|
||||
* @since 2.5
|
||||
*/
|
||||
public static function scanDirFiles(string $dir, bool $recursive = true, $relative = false)
|
||||
public static function scanDirFiles(string $dir, bool $recursive = true, $relative = false, bool $include_dir = false)
|
||||
{
|
||||
$dir = rtrim($dir, '/');
|
||||
if (!is_dir($dir)) {
|
||||
@@ -137,6 +147,9 @@ class DataProvider
|
||||
$list = [];
|
||||
if ($relative === true) {
|
||||
$relative = $dir;
|
||||
} elseif ($relative !== false && !is_string($relative)) {
|
||||
Console::warning(zm_internal_errcode('E00058') . "Relative path is not generated: wrong base directory ({$relative})");
|
||||
return false;
|
||||
}
|
||||
foreach ($r as $v) {
|
||||
if ($v == '.' || $v == '..') {
|
||||
@@ -144,15 +157,14 @@ class DataProvider
|
||||
}
|
||||
$sub_file = $dir . '/' . $v;
|
||||
if (is_dir($sub_file) && $recursive) {
|
||||
# 如果是 目录 且 递推 , 则递推添加下级文件
|
||||
$list = array_merge($list, self::scanDirFiles($sub_file, $recursive, $relative));
|
||||
} elseif (is_file($sub_file)) {
|
||||
} elseif (is_file($sub_file) || is_dir($sub_file) && !$recursive && $include_dir) {
|
||||
# 如果是 文件 或 (是 目录 且 不递推 且 包含目录)
|
||||
if (is_string($relative) && mb_strpos($sub_file, $relative) === 0) {
|
||||
$list[] = ltrim(mb_substr($sub_file, mb_strlen($relative)), '/');
|
||||
} elseif ($relative === false) {
|
||||
$list[] = $sub_file;
|
||||
} else {
|
||||
Console::warning(zm_internal_errcode('E00058') . "Relative path is not generated: wrong base directory ({$relative})");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -161,6 +173,7 @@ class DataProvider
|
||||
|
||||
/**
|
||||
* 检查路径是否为相对路径(根据第一个字符是否为"/"来判断)
|
||||
*
|
||||
* @param string $path 路径
|
||||
* @return bool 返回结果
|
||||
* @since 2.5
|
||||
@@ -169,4 +182,16 @@ class DataProvider
|
||||
{
|
||||
return strlen($path) > 0 && $path[0] !== '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建目录(如果不存在)
|
||||
*
|
||||
* @param string $path 目录路径
|
||||
*/
|
||||
public static function createIfNotExists(string $path): void
|
||||
{
|
||||
if (!is_dir($path) && !mkdir($path, 0777, true) && !is_dir($path)) {
|
||||
throw new RuntimeException(sprintf('无法建立目录:%s', $path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ use ZM\Annotation\Http\Controller;
|
||||
use ZM\Annotation\Http\RequestMapping;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Http\StaticFileHandler;
|
||||
use ZM\Store\LightCacheInside;
|
||||
|
||||
/**
|
||||
* 路由管理器,2.5版本更改了命名空间
|
||||
@@ -50,17 +49,14 @@ class RouteManager
|
||||
$tail = trim($route, '/');
|
||||
$route_name = ($tail === '' ? '' : '/') . $tail . '/{filename}';
|
||||
Console::debug('添加静态文件路由:' . $route_name);
|
||||
$route = new Route($route_name, ['_class' => RouteManager::class, '_method' => 'onStaticRoute']);
|
||||
// echo $path.PHP_EOL;
|
||||
LightCacheInside::set('static_route', $route->getPath(), $path);
|
||||
$route = new Route($route_name, ['_class' => __CLASS__, '_method' => 'onStaticRoute'], [], compact('path'));
|
||||
|
||||
self::$routes->add(md5($route_name), $route);
|
||||
}
|
||||
|
||||
public function onStaticRoute($params)
|
||||
public function onStaticRoute(array $params)
|
||||
{
|
||||
$route_path = self::$routes->get($params['_route'])->getPath();
|
||||
if (($path = LightCacheInside::get('static_route', $route_path)) === null) {
|
||||
if (($path = self::$routes->get($params['_route'])->getOption('path')) === null) {
|
||||
ctx()->getResponse()->endWithStatus(404);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -6,16 +6,17 @@ namespace ZM\Utils;
|
||||
|
||||
use Exception;
|
||||
use Iterator;
|
||||
use ReflectionException;
|
||||
use ReflectionMethod;
|
||||
use ZM\Annotation\CQ\CommandArgument;
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
use ZM\API\CQ;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Entity\InputArguments;
|
||||
use ZM\Entity\MatchResult;
|
||||
use ZM\Event\EventManager;
|
||||
use ZM\Event\EventMapIterator;
|
||||
use ZM\Exception\WaitTimeoutException;
|
||||
use ZM\Requests\ZMRequest;
|
||||
use ZM\Store\WorkerCache;
|
||||
use ZM\Utils\Manager\WorkerManager;
|
||||
|
||||
class MessageUtil
|
||||
@@ -255,76 +256,94 @@ class MessageUtil
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据注解树生成命令列表、帮助
|
||||
*
|
||||
* @return array 帮助信息,每个元素对应一个命令的帮助信息,格式为:命令名(其他触发条件):命令描述
|
||||
* @throws WaitTimeoutException
|
||||
*/
|
||||
public static function generateCommandHelp(): array
|
||||
public static function checkArguments(string $class, string $method, array &$match): array
|
||||
{
|
||||
try {
|
||||
if ($cache = WorkerCache::get('command_help')) {
|
||||
return $cache;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// 不做任何处理,尝试重新生成
|
||||
}
|
||||
$helps = [];
|
||||
foreach (EventManager::$events[CQCommand::class] as $annotation) {
|
||||
if ($annotation instanceof CQCommand) {
|
||||
try {
|
||||
$reflection = new ReflectionMethod($annotation->class, $annotation->method);
|
||||
} catch (ReflectionException $e) {
|
||||
Console::warning('注解解析错误:' . $e->getMessage());
|
||||
continue;
|
||||
}
|
||||
$doc = $reflection->getDocComment();
|
||||
if ($doc) {
|
||||
// 匹配出不以@开头,且后接中文或任意非空格字符,并以换行符结尾的字符串,也就是命令描述
|
||||
preg_match_all('/\*\s((?!@)[\x{4e00}-\x{9fa5}\S]+)(\r\n|\r|\n)/u', $doc, $matches);
|
||||
// 多行描述用分号分隔
|
||||
$help = implode(';', $matches[1]);
|
||||
if (empty($help)) {
|
||||
Console::warning('命令 ' . $annotation->class . '::' . $annotation->method . ' 没有描述!');
|
||||
$help = '无描述';
|
||||
}
|
||||
} else {
|
||||
Console::warning('命令 ' . $annotation->class . '::' . $annotation->method . ' 没有描述!');
|
||||
$help = '无描述';
|
||||
}
|
||||
|
||||
// 可以触发命令的参数
|
||||
$possible_keys = [
|
||||
'match' => '%s',
|
||||
'pattern' => '符合”%s“',
|
||||
'regex' => '匹配“%s”',
|
||||
'start_with' => '以”%s“开头',
|
||||
'end_with' => '以”%s“结尾',
|
||||
'keyword' => '包含“%s”',
|
||||
'alias' => '%s',
|
||||
];
|
||||
$command_seg = [];
|
||||
foreach ($possible_keys as $key => $help_format) {
|
||||
// 如果定义了该参数,则添加到帮助信息中
|
||||
if (isset($annotation->{$key}) && !empty($annotation->{$key})) {
|
||||
if (is_iterable($annotation->{$key})) {
|
||||
foreach ($annotation->{$key} as $item) {
|
||||
$command_seg[] = sprintf($help_format, $item);
|
||||
}
|
||||
$iterator = new EventMapIterator($class, $method, CommandArgument::class);
|
||||
$offset = 0;
|
||||
$arguments = [];
|
||||
foreach ($iterator as $annotation) {
|
||||
/** @var CommandArgument $annotation */
|
||||
switch ($annotation->type) {
|
||||
case 'string':
|
||||
case 'any':
|
||||
if (isset($match[$offset])) {
|
||||
$arguments[$annotation->name] = $match[$offset++];
|
||||
} else {
|
||||
if ($annotation->required) {
|
||||
$value = ctx()->waitMessage($annotation->prompt === '' ? ('请输入' . $annotation->name) : $annotation->prompt, $annotation->timeout);
|
||||
$arguments[$annotation->name] = $value;
|
||||
} else {
|
||||
$command_seg[] = sprintf($help_format, $annotation->{$key});
|
||||
$arguments[$annotation->name] = $annotation->default;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 第一个触发参数为主命令名
|
||||
$command = array_shift($command_seg);
|
||||
if (count($command_seg) > 0) {
|
||||
$command .= '(' . implode(',', $command_seg) . ')';
|
||||
}
|
||||
$helps[] = sprintf('%s:%s', $command, $help);
|
||||
break;
|
||||
case 'number':
|
||||
for ($k = $offset; $k < count($match); ++$k) {
|
||||
$v = $match[$k];
|
||||
if (is_numeric($v)) {
|
||||
array_splice($match, $k, 1);
|
||||
$arguments[$annotation->name] = $v / 1;
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
if (!$annotation->required) {
|
||||
if (is_numeric($annotation->default)) {
|
||||
$arguments[$annotation->name] = $annotation->default / 1;
|
||||
}
|
||||
}
|
||||
if (!isset($arguments[$annotation->name])) {
|
||||
$value = ctx()->waitMessage($annotation->prompt === '' ? ('请输入' . $annotation->name) : $annotation->prompt, $annotation->timeout);
|
||||
if (!is_numeric($value)) {
|
||||
if ($annotation->error_prompt_policy === 1) {
|
||||
$value = ctx()->waitMessage($annotation->getTypeErrorPrompt(), $annotation->timeout);
|
||||
if (!is_numeric($value)) {
|
||||
throw new WaitTimeoutException(ctx(), $annotation->getErrorQuitPrompt());
|
||||
}
|
||||
} else {
|
||||
throw new WaitTimeoutException(ctx(), $annotation->getErrorQuitPrompt());
|
||||
}
|
||||
}
|
||||
$arguments[$annotation->name] = $value / 1;
|
||||
}
|
||||
break;
|
||||
case 'bool':
|
||||
for ($k = $offset; $k < count($match); ++$k) {
|
||||
$v = strtolower($match[$k]);
|
||||
if (in_array(strtolower($v), TRUE_LIST)) {
|
||||
array_splice($match, $k, 1);
|
||||
$arguments[$annotation->name] = true;
|
||||
break 2;
|
||||
}
|
||||
if (in_array(strtolower($v), FALSE_LIST)) {
|
||||
array_splice($match, $k, 1);
|
||||
$arguments[$annotation->name] = false;
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
if (!$annotation->required) {
|
||||
$default = $annotation->default === '' ? true : 'true';
|
||||
$arguments[$annotation->name] = in_array($default, TRUE_LIST);
|
||||
}
|
||||
if (!isset($arguments[$annotation->name])) {
|
||||
$value = strtolower(ctx()->waitMessage($annotation->prompt === '' ? ('请输入' . $annotation->name) : $annotation->prompt, $annotation->timeout));
|
||||
if (!in_array($value, array_merge(TRUE_LIST, FALSE_LIST))) {
|
||||
if ($annotation->error_prompt_policy === 1) {
|
||||
$value = strtolower(ctx()->waitMessage($annotation->getTypeErrorPrompt(), $annotation->timeout));
|
||||
if (!in_array($value, array_merge(TRUE_LIST, FALSE_LIST))) {
|
||||
throw new WaitTimeoutException(ctx(), $annotation->getErrorQuitPrompt());
|
||||
}
|
||||
} else {
|
||||
throw new WaitTimeoutException(ctx(), $annotation->getErrorQuitPrompt());
|
||||
}
|
||||
}
|
||||
$arguments[$annotation->name] = in_array($value, TRUE_LIST);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 放到跨进程缓存以供取用
|
||||
WorkerCache::set('command_helps', $helps);
|
||||
return $helps;
|
||||
container()->instance(InputArguments::class, new InputArguments($arguments));
|
||||
return $arguments;
|
||||
}
|
||||
}
|
||||
|
||||
123
src/ZM/Utils/ReflectionUtil.php
Normal file
123
src/ZM/Utils/ReflectionUtil.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Utils;
|
||||
|
||||
use Closure;
|
||||
use ReflectionException;
|
||||
use ReflectionFunction;
|
||||
use ReflectionFunctionAbstract;
|
||||
use ReflectionMethod;
|
||||
use ReflectionNamedType;
|
||||
use ReflectionParameter;
|
||||
|
||||
class ReflectionUtil
|
||||
{
|
||||
/**
|
||||
* 获取参数的类名(如有)
|
||||
*
|
||||
* @param ReflectionParameter $parameter 参数
|
||||
* @return null|string 类名,如果参数不是类,返回 null
|
||||
*/
|
||||
public static function getParameterClassName(ReflectionParameter $parameter): ?string
|
||||
{
|
||||
// 获取参数类型
|
||||
$type = $parameter->getType();
|
||||
// 没有声明类型或为基本类型
|
||||
if (!$type instanceof ReflectionNamedType || $type->isBuiltin()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取类名
|
||||
$class_name = $type->getName();
|
||||
|
||||
// 如果存在父类
|
||||
if (!is_null($class = $parameter->getDeclaringClass())) {
|
||||
if ($class_name === 'self') {
|
||||
return $class->getName();
|
||||
}
|
||||
|
||||
if ($class_name === 'parent' && $parent = $class->getParentClass()) {
|
||||
return $parent->getName();
|
||||
}
|
||||
}
|
||||
|
||||
return $class_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将传入变量转换为字符串
|
||||
*
|
||||
* @param mixed $var
|
||||
*/
|
||||
public static function variableToString($var): string
|
||||
{
|
||||
switch (true) {
|
||||
case is_callable($var):
|
||||
if (is_array($var)) {
|
||||
if (is_object($var[0])) {
|
||||
return get_class($var[0]) . '@' . $var[1];
|
||||
}
|
||||
return $var[0] . '::' . $var[1];
|
||||
}
|
||||
return 'closure';
|
||||
case is_string($var):
|
||||
return $var;
|
||||
case is_array($var):
|
||||
return 'array' . json_encode($var);
|
||||
case is_object($var):
|
||||
return get_class($var);
|
||||
case is_resource($var):
|
||||
return 'resource(' . get_resource_type($var) . ')';
|
||||
case is_null($var):
|
||||
return 'null';
|
||||
case is_bool($var):
|
||||
return $var ? 'true' : 'false';
|
||||
case is_float($var):
|
||||
case is_int($var):
|
||||
return (string) $var;
|
||||
default:
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断传入的回调是否为任意类的非静态方法
|
||||
*
|
||||
* @param callable|string $callback 回调
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public static function isNonStaticMethod($callback): bool
|
||||
{
|
||||
if (is_array($callback) && is_string($callback[0])) {
|
||||
$reflection = new ReflectionMethod($callback[0], $callback[1]);
|
||||
return !$reflection->isStatic();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取传入的回调的反射实例
|
||||
*
|
||||
* 如果传入的是类方法,则会返回 {@link ReflectionMethod} 实例
|
||||
* 否则将返回 {@link ReflectionFunction} 实例
|
||||
*
|
||||
* 可传入实现了 __invoke 的类
|
||||
*
|
||||
* @param callable|string $callback 回调
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public static function getCallReflector($callback): ReflectionFunctionAbstract
|
||||
{
|
||||
if (is_string($callback) && str_contains($callback, '::')) {
|
||||
$callback = explode('::', $callback);
|
||||
} elseif (is_object($callback) && !$callback instanceof Closure) {
|
||||
$callback = [$callback, '__invoke'];
|
||||
}
|
||||
|
||||
return is_array($callback)
|
||||
? new ReflectionMethod($callback[0], $callback[1])
|
||||
: new ReflectionFunction($callback);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,34 @@
|
||||
<?php
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Utils;
|
||||
|
||||
trait SingletonTrait
|
||||
{
|
||||
/**
|
||||
* @deprecated 将会于未来版本移除
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $cached = [];
|
||||
|
||||
/**
|
||||
* @var self
|
||||
* @var null|static
|
||||
*/
|
||||
private static $instance;
|
||||
protected static $instance;
|
||||
|
||||
/**
|
||||
* @return self
|
||||
* @noinspection PhpMissingReturnTypeInspection
|
||||
* 获取类实例
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function getInstance()
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
if (is_null(static::$instance)) {
|
||||
static::$instance = new static();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
return static::$instance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ declare(strict_types=1);
|
||||
namespace ZM\Utils;
|
||||
|
||||
use Doctrine\Common\Annotations\AnnotationReader;
|
||||
use Error;
|
||||
use Exception;
|
||||
use ReflectionClass;
|
||||
use Swoole\Process;
|
||||
use Throwable;
|
||||
use ZM\Annotation\Command\TerminalCommand;
|
||||
use ZM\ConnectionManager\ManagerGM;
|
||||
use ZM\Console\Console;
|
||||
@@ -22,8 +22,7 @@ class Terminal
|
||||
public static $default_commands = false;
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
* @throws Error
|
||||
* @throws Throwable
|
||||
* @return bool
|
||||
* @noinspection PhpMissingReturnTypeInspection
|
||||
* @noinspection PhpUnused
|
||||
|
||||
@@ -11,16 +11,6 @@ use ZM\Framework;
|
||||
use ZM\Store\Lock\SpinLock;
|
||||
use ZM\Store\ZMAtomic;
|
||||
use ZM\Store\ZMBuf;
|
||||
use function file_get_contents;
|
||||
use function get_included_files;
|
||||
use function is_callable;
|
||||
use function is_string;
|
||||
use function json_decode;
|
||||
use function mb_substr;
|
||||
use function md5_file;
|
||||
use function pathinfo;
|
||||
use function server;
|
||||
use function str_replace;
|
||||
|
||||
class ZMUtil
|
||||
{
|
||||
@@ -124,7 +114,7 @@ class ZMUtil
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
} elseif (is_callable($rule) && !($rule($dir, $pathinfo))) {
|
||||
} elseif (is_callable($rule) && !$rule($dir, $pathinfo)) {
|
||||
continue;
|
||||
}
|
||||
$dirname = $pathinfo['dirname'] == '.' ? '' : (str_replace('/', '\\', $pathinfo['dirname']) . '\\');
|
||||
|
||||
@@ -15,21 +15,31 @@ use ZM\Module\ModuleBase;
|
||||
*/
|
||||
class ZMServer
|
||||
{
|
||||
/** @var string App名称 */
|
||||
protected $app_name;
|
||||
|
||||
/** @var ModuleBase[] */
|
||||
protected $modules = [];
|
||||
|
||||
public function __construct($app_name)
|
||||
/**
|
||||
* @param string $app_name App名称
|
||||
*/
|
||||
public function __construct(string $app_name)
|
||||
{
|
||||
$this->app_name = $app_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $module_class
|
||||
*/
|
||||
public function addModule($module_class)
|
||||
{
|
||||
$this->modules[] = $module_class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InitException
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
Console::setLevel(4);
|
||||
@@ -39,7 +49,7 @@ class ZMServer
|
||||
}
|
||||
}
|
||||
echo "Running...\n";
|
||||
if (defined('WORKDING_DIR')) {
|
||||
if (defined('WORKING_DIR')) {
|
||||
throw new InitException();
|
||||
}
|
||||
|
||||
@@ -57,10 +67,7 @@ class ZMServer
|
||||
(new Framework($options, true))->start();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAppName()
|
||||
public function getAppName(): string
|
||||
{
|
||||
return $this->app_name;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@ if (!is_dir(CRASH_DIR)) {
|
||||
@mkdir(CRASH_DIR);
|
||||
}
|
||||
|
||||
const TRUE_LIST = ['yes', 'y', 'true', 'on', '是', '对', true];
|
||||
const FALSE_LIST = ['no', 'n', 'false', 'off', '否', '错', false];
|
||||
|
||||
const CONN_WEBSOCKET = 0;
|
||||
const CONN_HTTP = 1;
|
||||
const ZM_MATCH_ALL = 0;
|
||||
|
||||
@@ -13,11 +13,12 @@ use ZM\API\ZMRobot;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\ConnectionManager\ManagerGM;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Container\Container;
|
||||
use ZM\Container\ContainerInterface;
|
||||
use ZM\Context\Context;
|
||||
use ZM\Context\ContextInterface;
|
||||
use ZM\Event\EventManager;
|
||||
use ZM\Exception\RobotNotFoundException;
|
||||
use ZM\Exception\ZMKnownException;
|
||||
use ZM\Framework;
|
||||
use ZM\Store\LightCacheInside;
|
||||
use ZM\Store\ZMAtomic;
|
||||
@@ -40,6 +41,7 @@ function get_class_path(string $class_name): ?string
|
||||
|
||||
/**
|
||||
* 检查炸毛框架运行的环境
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function _zm_env_check()
|
||||
@@ -55,7 +57,7 @@ function _zm_env_check()
|
||||
}
|
||||
if (version_compare(SWOOLE_VERSION, '4.6.7') < 0 && !extension_loaded('pcntl')) {
|
||||
Console::error(zm_internal_errcode('E00004') . 'Swoole 版本必须不低于 4.6.7 或 PHP 安装加载了 pcntl 扩展!');
|
||||
exit();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,19 +98,9 @@ function unicode_decode(string $str): ?string
|
||||
*/
|
||||
function match_pattern(string $pattern, string $subject): bool
|
||||
{
|
||||
if (empty($pattern) && empty($subject)) {
|
||||
return true;
|
||||
}
|
||||
if (mb_strpos($pattern, '*') === 0 && mb_substr($pattern, 1, 1) !== '' && empty($subject)) {
|
||||
return false;
|
||||
}
|
||||
if (mb_strpos($pattern, mb_substr($subject, 0, 1)) === 0) {
|
||||
return match_pattern(mb_substr($pattern, 1), mb_substr($subject, 1));
|
||||
}
|
||||
if (mb_strpos($pattern, '*') === 0) {
|
||||
return match_pattern(mb_substr($pattern, 1), $subject) || match_pattern($pattern, mb_substr($subject, 1));
|
||||
}
|
||||
return false;
|
||||
$pattern = str_replace(['\*', '\\\\.*'], ['.*', '\*'], preg_quote($pattern, '/'));
|
||||
$pattern = '/^' . $pattern . '$/i';
|
||||
return preg_match($pattern, $subject) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,16 +156,16 @@ function match_args(string $pattern, string $subject)
|
||||
$exp = explode('*', $pattern);
|
||||
$i = 0;
|
||||
foreach ($exp as $k => $v) {
|
||||
if (empty($v) && $k === 0) {
|
||||
if ($v === '' && $k === 0) {
|
||||
continue;
|
||||
}
|
||||
if (empty($v) && $k === count($exp) - 1) {
|
||||
if ($v === '' && $k === count($exp) - 1) {
|
||||
$subject .= '^EOL';
|
||||
$v = '^EOL';
|
||||
}
|
||||
$cur_var = '';
|
||||
$ori = $i;
|
||||
while (($a = mb_substr($subject, $i, mb_strlen($v))) !== $v && !empty($a)) {
|
||||
while (($a = mb_substr($subject, $i, mb_strlen($v))) !== $v && $a !== '') {
|
||||
$cur_var .= mb_substr($subject, $i, 1);
|
||||
++$i;
|
||||
}
|
||||
@@ -190,8 +182,7 @@ function match_args(string $pattern, string $subject)
|
||||
/**
|
||||
* 判断当前连接类型是否为传入的$type
|
||||
*
|
||||
* @param string $type 连接类型
|
||||
* @throws ZMKnownException
|
||||
* @param string $type 连接类型
|
||||
*/
|
||||
function current_connection_is(string $type): bool
|
||||
{
|
||||
@@ -220,7 +211,7 @@ function get_annotations(): array
|
||||
*/
|
||||
function set_coroutine_params(array $params): void
|
||||
{
|
||||
$cid = Co::getCid();
|
||||
$cid = co::getCid();
|
||||
if ($cid === -1) {
|
||||
exit(zm_internal_errcode('E00061') . 'Cannot set coroutine params at none coroutine mode.');
|
||||
}
|
||||
@@ -230,7 +221,7 @@ function set_coroutine_params(array $params): void
|
||||
Context::$context[$cid] = $params;
|
||||
}
|
||||
foreach (Context::$context as $c => $v) {
|
||||
if (!Co::exists($c)) {
|
||||
if (!co::exists($c)) {
|
||||
unset(Context::$context[$c], ZMBuf::$context_class[$c]);
|
||||
}
|
||||
}
|
||||
@@ -238,8 +229,6 @@ function set_coroutine_params(array $params): void
|
||||
|
||||
/**
|
||||
* 获取当前上下文
|
||||
*
|
||||
* @throws ZMKnownException
|
||||
*/
|
||||
function context(): ContextInterface
|
||||
{
|
||||
@@ -248,24 +237,23 @@ function context(): ContextInterface
|
||||
|
||||
/**
|
||||
* 获取当前上下文
|
||||
*
|
||||
* @throws ZMKnownException
|
||||
*/
|
||||
function ctx(): ContextInterface
|
||||
{
|
||||
$cid = Co::getCid();
|
||||
$cid = co::getCid();
|
||||
$c_class = ZMConfig::get('global', 'context_class');
|
||||
if (isset(Context::$context[$cid])) {
|
||||
return ZMBuf::$context_class[$cid] ?? (ZMBuf::$context_class[$cid] = new $c_class($cid));
|
||||
}
|
||||
Console::debug("未找到当前协程的上下文({$cid}),正在找父进程的上下文");
|
||||
while (($parent_cid = Co::getPcid($cid)) !== -1) {
|
||||
while (($parent_cid = co::getPcid($cid)) !== -1) {
|
||||
$cid = $parent_cid;
|
||||
if (isset(Context::$context[$cid])) {
|
||||
return ZMBuf::$context_class[$cid] ?? (ZMBuf::$context_class[$cid] = new $c_class($cid));
|
||||
}
|
||||
}
|
||||
throw new ZMKnownException(zm_internal_errcode('E00072') . 'Unable to find context environment');
|
||||
Console::warning('当前环境不是协程环境,将返回独立的非协程的容器');
|
||||
return ZMBuf::$context_class[$cid] ?? (ZMBuf::$context_class[$cid] = new $c_class($cid));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -291,9 +279,10 @@ function get_onebot_target_id_name(string $message_type): string
|
||||
*
|
||||
* 与 {@link sleep()} 一致,只是增加了协程支持
|
||||
*
|
||||
* @param float|int $seconds 秒数(支持到1ms(0.001))
|
||||
* @since 2.7.3 此函数不再返回 true
|
||||
*/
|
||||
function zm_sleep(int $seconds = 1): void
|
||||
function zm_sleep($seconds = 1): void
|
||||
{
|
||||
if (Coroutine::getCid() !== -1) {
|
||||
System::sleep($seconds);
|
||||
@@ -321,7 +310,7 @@ function zm_exec(string $command): array
|
||||
*/
|
||||
function zm_cid(): int
|
||||
{
|
||||
return Co::getCid();
|
||||
return co::getCid();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -331,7 +320,7 @@ function zm_cid(): int
|
||||
*/
|
||||
function zm_yield()
|
||||
{
|
||||
Co::yield();
|
||||
co::yield();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -341,7 +330,7 @@ function zm_yield()
|
||||
*/
|
||||
function zm_resume(int $cid)
|
||||
{
|
||||
Co::resume($cid);
|
||||
co::resume($cid);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -416,6 +405,7 @@ function server(): Server
|
||||
|
||||
/**
|
||||
* 获取缓存当前框架pid的临时目录
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function _zm_pid_dir(): string
|
||||
@@ -433,6 +423,7 @@ function _zm_pid_dir(): string
|
||||
* 随机返回一个 ZMRobot 实例,效果等同于 {@link ZMRobot::getRandom()}。
|
||||
*
|
||||
* 在单机器人模式下,会直接返回该机器人实例。
|
||||
*
|
||||
* @throws RobotNotFoundException
|
||||
*/
|
||||
function bot(): ZMRobot
|
||||
@@ -626,6 +617,149 @@ function zm_internal_errcode($code): string
|
||||
return "[ErrCode:{$code}] ";
|
||||
}
|
||||
|
||||
if (!function_exists('chain')) {
|
||||
define('CARRY', '{carry}');
|
||||
|
||||
/**
|
||||
* 链式调用对象方法
|
||||
*
|
||||
* 如需使用上一步的返回值作为参数,请使用 CARRY 常量替换对应的参数值
|
||||
*
|
||||
* @param object $object 需要进行链式调用的对象
|
||||
* @return object 链式操作对象,可当成传入的对象使用
|
||||
*/
|
||||
function chain(object $object): object
|
||||
{
|
||||
return new class($object) {
|
||||
/**
|
||||
* 最后一次执行的方法的返回值
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $return;
|
||||
|
||||
/**
|
||||
* 需要进行链式调用的对象
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected $wrapped;
|
||||
|
||||
/**
|
||||
* 构造链式调用匿名类
|
||||
*/
|
||||
public function __construct(object $object)
|
||||
{
|
||||
$this->wrapped = $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* 代理所有调用
|
||||
*/
|
||||
public function __call(string $name, array $arguments)
|
||||
{
|
||||
if (($index = array_search(CARRY, $arguments, true)) !== false) {
|
||||
$arguments[$index] = $this->return;
|
||||
}
|
||||
$this->return = $this->wrapped->{$name}(...$arguments);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回最后执行结果
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return (string) $this->return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 允许调用最后返回结果
|
||||
*/
|
||||
public function __invoke()
|
||||
{
|
||||
return $this->return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用链式操作对象作为参数,执行回调
|
||||
*/
|
||||
public function tap(callable $callback): self
|
||||
{
|
||||
$callback($this->wrapped);
|
||||
return $this;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('stopwatch')) {
|
||||
/**
|
||||
* 执行回调函数并返回平均执行耗时
|
||||
*
|
||||
* @param int $times 执行次数
|
||||
* @return float 平均执行耗时
|
||||
*/
|
||||
function stopwatch(callable $callback, int $times = 1): float
|
||||
{
|
||||
$total = 0;
|
||||
for ($i = 0; $i < $times; ++$i) {
|
||||
$start = microtime(true);
|
||||
$callback();
|
||||
$total += microtime(true) - $start;
|
||||
}
|
||||
return $total / $times;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将可能为数组的参数转换为字符串
|
||||
*
|
||||
* 如传入字符串则为原样返回
|
||||
*
|
||||
* @param array|string $string_or_array
|
||||
*/
|
||||
function implode_when_necessary($string_or_array): string
|
||||
{
|
||||
return is_array($string_or_array) ? implode(', ', $string_or_array) : $string_or_array;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取容器(请求级)实例
|
||||
*/
|
||||
function container(): ContainerInterface
|
||||
{
|
||||
return Container::getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析类实例(使用容器)
|
||||
*
|
||||
* @template T
|
||||
* @param class-string<T> $abstract
|
||||
* @return Closure|mixed|T
|
||||
*/
|
||||
function resolve(string $abstract, array $parameters = [])
|
||||
{
|
||||
return Container::getInstance()->make($abstract, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取容器实例
|
||||
*
|
||||
* @template T
|
||||
* @param null|class-string<T> $abstract
|
||||
* @return Closure|ContainerInterface|mixed|T
|
||||
*/
|
||||
function app(string $abstract = null, array $parameters = [])
|
||||
{
|
||||
if (is_null($abstract)) {
|
||||
return container();
|
||||
}
|
||||
|
||||
return resolve($abstract, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* 以下为废弃的函数,将于未来移除
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
const _PHAR_STUB_ID = '__generated_id__';
|
||||
|
||||
function loader__generated_id__()
|
||||
{
|
||||
$obj = json_decode(file_get_contents(__DIR__ . '/zmplugin.json'), true);
|
||||
@@ -12,4 +14,8 @@ function loader__generated_id__()
|
||||
require_once Phar::running() . '/' . $v;
|
||||
}
|
||||
}
|
||||
if ('__generate' . 'd_id__' === _PHAR_STUB_ID) {
|
||||
echo 'Cannot execute this file directly!' . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
return json_decode(file_get_contents(__DIR__ . '/zmplugin.json'), true) ?? ['zm_module' => false];
|
||||
|
||||
130
tests/ZM/Container/ContainerCallTest.php
Normal file
130
tests/ZM/Container/ContainerCallTest.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\ZM\Container;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ZM\Container\Container;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ContainerCallTest extends TestCase
|
||||
{
|
||||
public function testCallInvokableClass(): void
|
||||
{
|
||||
$container = new Container();
|
||||
$this->assertEquals('foo', $container->call(Invokable::class, ['echo' => 'foo']));
|
||||
}
|
||||
|
||||
public function testCallClassMethodWithDependencies(): void
|
||||
{
|
||||
$container = new Container();
|
||||
$name = 'Steve' . time();
|
||||
$container->bind(FooDependency::class, FooDependencyImpl::class);
|
||||
$container->bind(BarDependency::class, function () use ($name) {
|
||||
return new BarDependencyImpl($name);
|
||||
});
|
||||
$this->assertEquals("hello, {$name}", $container->call([Foo::class, 'sayHello']));
|
||||
}
|
||||
|
||||
public function testCallClassStaticMethodWithDependencies(): void
|
||||
{
|
||||
$container = new Container();
|
||||
$name = 'Alex' . time();
|
||||
$container->bind(FooDependency::class, FooDependencyImpl::class);
|
||||
$container->bind(BarDependency::class, function () use ($name) {
|
||||
return new BarDependencyImpl($name);
|
||||
});
|
||||
$this->assertEquals("hello, {$name}", $container->call([Foo::class, 'staticSayHello']));
|
||||
}
|
||||
|
||||
public function testCallClassMethodWithDependenciesInjectedByConstructor(): void
|
||||
{
|
||||
$container = new Container();
|
||||
$name = 'Donny' . time();
|
||||
$container->bind(FooDependency::class, FooDependencyImpl::class);
|
||||
$container->bind(BarDependency::class, function () use ($name) {
|
||||
return new BarDependencyImpl($name);
|
||||
});
|
||||
$this->assertEquals('goodbye', $container->call([Foo::class, 'sayGoodbye']));
|
||||
}
|
||||
|
||||
public function testCallClassStaticMethodViaDoubleColons(): void
|
||||
{
|
||||
$container = new Container();
|
||||
$name = 'Herobrine' . time();
|
||||
$container->bind(FooDependency::class, FooDependencyImpl::class);
|
||||
$container->bind(BarDependency::class, function () use ($name) {
|
||||
return new BarDependencyImpl($name);
|
||||
});
|
||||
$this->assertEquals("hello, {$name}", $container->call(Foo::class . '::staticSayHello'));
|
||||
}
|
||||
}
|
||||
|
||||
class Invokable
|
||||
{
|
||||
public function __invoke(string $echo)
|
||||
{
|
||||
return $echo;
|
||||
}
|
||||
}
|
||||
|
||||
interface FooDependency
|
||||
{
|
||||
public function sayGoodbye(): string;
|
||||
}
|
||||
|
||||
class FooDependencyImpl implements FooDependency
|
||||
{
|
||||
public function sayGoodbye(): string
|
||||
{
|
||||
return 'goodbye';
|
||||
}
|
||||
}
|
||||
|
||||
interface BarDependency
|
||||
{
|
||||
public function getName(): string;
|
||||
}
|
||||
|
||||
class BarDependencyImpl implements BarDependency
|
||||
{
|
||||
private $name;
|
||||
|
||||
public function __construct(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
|
||||
class Foo
|
||||
{
|
||||
private $fooDependency;
|
||||
|
||||
public function __construct(FooDependency $fooDependency)
|
||||
{
|
||||
$this->fooDependency = $fooDependency;
|
||||
}
|
||||
|
||||
public function sayHello(BarDependency $barDependency): string
|
||||
{
|
||||
return 'hello, ' . $barDependency->getName();
|
||||
}
|
||||
|
||||
public static function staticSayHello(BarDependency $barDependency): string
|
||||
{
|
||||
return 'hello, ' . $barDependency->getName();
|
||||
}
|
||||
|
||||
public function sayGoodbye(): string
|
||||
{
|
||||
return $this->fooDependency->sayGoodbye();
|
||||
}
|
||||
}
|
||||
52
tests/ZM/Container/ContainerTest.php
Normal file
52
tests/ZM/Container/ContainerTest.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\ZM\Container;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ZM\Container\Container;
|
||||
use ZM\Container\WorkerContainer;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ContainerTest extends TestCase
|
||||
{
|
||||
public function testCanInheritParentBinding(): void
|
||||
{
|
||||
$worker_container = new WorkerContainer();
|
||||
$worker_container->instance('foo', 'bar');
|
||||
|
||||
$container = new Container();
|
||||
$container->instance('baz', 'qux');
|
||||
|
||||
// 获取父容器的实例
|
||||
$this->assertEquals('bar', $container->get('foo'));
|
||||
|
||||
// 获取自身容器的实例
|
||||
$this->assertEquals('qux', $container->get('baz'));
|
||||
}
|
||||
|
||||
public function testCanOverrideParentBinding(): void
|
||||
{
|
||||
$worker_container = new WorkerContainer();
|
||||
$worker_container->instance('foo', 'bar');
|
||||
|
||||
$container = new Container();
|
||||
$container->instance('foo', 'qux');
|
||||
|
||||
$this->assertEquals('qux', $container->get('foo'));
|
||||
}
|
||||
|
||||
public function testCannotModifyParentBinding(): void
|
||||
{
|
||||
$worker_container = new WorkerContainer();
|
||||
$worker_container->instance('foo', 'bar');
|
||||
|
||||
$container = new Container();
|
||||
$container->instance('foo', 'qux');
|
||||
|
||||
$this->assertEquals('bar', $worker_container->get('foo'));
|
||||
}
|
||||
}
|
||||
120
tests/ZM/Container/WorkerContainerTest.php
Normal file
120
tests/ZM/Container/WorkerContainerTest.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\ZM\Container;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ZM\Container\EntryResolutionException;
|
||||
use ZM\Container\WorkerContainer;
|
||||
use ZM\Utils\MessageUtil;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class WorkerContainerTest extends TestCase
|
||||
{
|
||||
private $container;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->container = new WorkerContainer();
|
||||
$this->container->flush();
|
||||
}
|
||||
|
||||
public function testInstance(): void
|
||||
{
|
||||
$this->container->instance('test', 'test');
|
||||
$this->assertEquals('test', $this->container->make('test'));
|
||||
|
||||
$t2 = new WorkerContainer();
|
||||
$this->assertEquals('test', $t2->make('test'));
|
||||
}
|
||||
|
||||
public function testAlias(): void
|
||||
{
|
||||
$this->container->alias(MessageUtil::class, 'bar');
|
||||
$this->container->alias('bar', 'baz');
|
||||
$this->container->alias('baz', 'bas');
|
||||
$this->assertInstanceOf(MessageUtil::class, $this->container->make('bas'));
|
||||
}
|
||||
|
||||
public function testGetAlias(): void
|
||||
{
|
||||
$this->container->alias(MessageUtil::class, 'bar');
|
||||
$this->assertEquals(MessageUtil::class, $this->container->getAlias('bar'));
|
||||
}
|
||||
|
||||
public function testBindClosure(): void
|
||||
{
|
||||
$this->container->bind('test', function () {
|
||||
return 'test';
|
||||
});
|
||||
$this->assertEquals('test', $this->container->make('test'));
|
||||
}
|
||||
|
||||
public function testFlush(): void
|
||||
{
|
||||
$this->container->bind('test', function () {
|
||||
return 'test';
|
||||
});
|
||||
$this->container->flush();
|
||||
$this->expectException(EntryResolutionException::class);
|
||||
$this->container->make('test');
|
||||
}
|
||||
|
||||
public function testBindIf(): void
|
||||
{
|
||||
$this->container->bind('test', function () {
|
||||
return 'test';
|
||||
});
|
||||
$this->container->bindIf('test', function () {
|
||||
return 'test2';
|
||||
});
|
||||
$this->assertEquals('test', $this->container->make('test'));
|
||||
}
|
||||
|
||||
public function testGet(): void
|
||||
{
|
||||
$this->testMake();
|
||||
}
|
||||
|
||||
public function testBound(): void
|
||||
{
|
||||
$this->container->bind('test', function () {
|
||||
return 'test';
|
||||
});
|
||||
$this->assertTrue($this->container->bound('test'));
|
||||
$this->assertFalse($this->container->bound('test2'));
|
||||
}
|
||||
|
||||
public function testFactory(): void
|
||||
{
|
||||
$this->container->bind('test', function () {
|
||||
return 'test';
|
||||
});
|
||||
$factory = $this->container->factory('test');
|
||||
$this->assertEquals($this->container->make('test'), $factory());
|
||||
}
|
||||
|
||||
public function testMake(): void
|
||||
{
|
||||
$this->container->bind('test', function () {
|
||||
return 'test';
|
||||
});
|
||||
$this->assertEquals('test', $this->container->make('test'));
|
||||
}
|
||||
|
||||
public function testHas(): void
|
||||
{
|
||||
$this->testBound();
|
||||
}
|
||||
|
||||
public function testBuild(): void
|
||||
{
|
||||
$this->assertEquals('test', $this->container->build(function () {
|
||||
return 'test';
|
||||
}));
|
||||
$this->assertInstanceOf(MessageUtil::class, $this->container->build(MessageUtil::class));
|
||||
}
|
||||
}
|
||||
31
tests/ZM/Event/EventMapIteratorTest.php
Normal file
31
tests/ZM/Event/EventMapIteratorTest.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\ZM\Event;
|
||||
|
||||
use Module\Example\Hello;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ZM\Annotation\CQ\CommandArgument;
|
||||
use ZM\Event\EventMapIterator;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class EventMapIteratorTest extends TestCase
|
||||
{
|
||||
public function testIterator(): void
|
||||
{
|
||||
$iterator = new EventMapIterator(Hello::class, 'randNum', CommandArgument::class);
|
||||
$arr = iterator_to_array($iterator);
|
||||
$this->assertArrayNotHasKey(0, $arr);
|
||||
$this->assertArrayNotHasKey(1, $arr);
|
||||
$this->assertArrayHasKey(2, $arr);
|
||||
$this->assertArrayHasKey(3, $arr);
|
||||
$this->assertInstanceOf(CommandArgument::class, $arr[2]);
|
||||
$this->assertInstanceOf(CommandArgument::class, $arr[3]);
|
||||
$iterator = new EventMapIterator(Hello::class, 'closeUnknownConn', CommandArgument::class);
|
||||
$ls = iterator_to_array($iterator);
|
||||
$this->assertCount(0, $ls);
|
||||
}
|
||||
}
|
||||
86
tests/ZM/GlobalFunctionsTest.php
Normal file
86
tests/ZM/GlobalFunctionsTest.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\ZM;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class GlobalFunctionsTest extends TestCase
|
||||
{
|
||||
public function testChain(): void
|
||||
{
|
||||
$mock = $this->getMockBuilder(\stdClass::class)
|
||||
->addMethods(['foo', 'bar', 'baz'])
|
||||
->getMock();
|
||||
$mock->expects($this->once())
|
||||
->method('foo')
|
||||
->willReturn('foo');
|
||||
$mock->expects($this->once())
|
||||
->method('bar')
|
||||
->with('foo')
|
||||
->willReturn('bar');
|
||||
$mock->expects($this->once())
|
||||
->method('baz')
|
||||
->with('bar')
|
||||
->willReturn('baz');
|
||||
|
||||
$result = chain($mock)->foo()->bar(CARRY)->baz(CARRY);
|
||||
|
||||
$this->assertEquals('baz', $result);
|
||||
}
|
||||
|
||||
public function testStopwatch(): void
|
||||
{
|
||||
$time = stopwatch(static function () {
|
||||
usleep(10000);
|
||||
});
|
||||
$this->assertLessThan(0.1, $time);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerTestMatchPattern
|
||||
*/
|
||||
public function testMatchPattern(string $pattern, string $subject, bool $expected): void
|
||||
{
|
||||
$this->assertEquals($expected, match_pattern($pattern, $subject));
|
||||
}
|
||||
|
||||
public function providerTestMatchPattern(): array
|
||||
{
|
||||
return [
|
||||
'empty' => ['', '', true],
|
||||
'empty subject' => ['foo', '', false],
|
||||
'empty pattern' => ['', 'foo', false],
|
||||
'simple' => ['foo', 'foo', true],
|
||||
'simple case insensitive' => ['FOO', 'foo', true],
|
||||
'simple case insensitive 2' => ['foo', 'FOO', true],
|
||||
'unicode' => ['föö', 'föö', true],
|
||||
'chinese' => ['中文', '中文', true],
|
||||
'wildcard' => ['foo*', 'foo', true],
|
||||
'wildcard 2' => ['foo*', 'foobar', true],
|
||||
'wildcard 3' => ['foo*bar', 'foo with bar', true],
|
||||
'wildcard 4' => ['foo*bar', 'foo but no bar with it', false],
|
||||
'wildcard with chinese' => ['中文*', '中文', true],
|
||||
'wildcard with chinese 2' => ['全世界*中国话', '全世界都在说中国话', true],
|
||||
'complex' => ['foo*bar*baz', 'foo with bar and baz', true],
|
||||
'regex' => ['[a-z]+', 'foo', false], // regex is not supported yet
|
||||
'escaped' => ['foo\\*bar', 'foo*bar', true],
|
||||
];
|
||||
}
|
||||
|
||||
public function testZmExec(): void
|
||||
{
|
||||
$this->assertEquals(['code' => 0, 'signal' => 0, 'output' => "foo\n"], zm_exec('echo foo'));
|
||||
}
|
||||
|
||||
public function testZmSleep(): void
|
||||
{
|
||||
$starttime = microtime(true);
|
||||
zm_sleep(0.001);
|
||||
$this->assertGreaterThanOrEqual(0.001, microtime(true) - $starttime);
|
||||
}
|
||||
}
|
||||
84
tests/ZM/Store/LightCacheTest.php
Normal file
84
tests/ZM/Store/LightCacheTest.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\ZM\Store;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ZM\Store\LightCache;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class LightCacheTest extends TestCase
|
||||
{
|
||||
protected function tearDown(): void
|
||||
{
|
||||
LightCache::unset('test');
|
||||
LightCache::unset('test2');
|
||||
}
|
||||
|
||||
public function testSetAndGet(): void
|
||||
{
|
||||
$result = LightCache::set('test', 'value');
|
||||
|
||||
$this->assertTrue($result);
|
||||
$this->assertSame('value', LightCache::get('test'));
|
||||
}
|
||||
|
||||
public function testUnset(): void
|
||||
{
|
||||
LightCache::set('test', 'value');
|
||||
$this->assertTrue(LightCache::unset('test'));
|
||||
$this->assertNull(LightCache::get('test'));
|
||||
$this->assertFalse(LightCache::unset('test'));
|
||||
}
|
||||
|
||||
public function testGetAll(): void
|
||||
{
|
||||
LightCache::set('test', 'value');
|
||||
LightCache::set('test2', 'value2');
|
||||
|
||||
$this->assertSame(['test' => 'value', 'test2' => 'value2'], LightCache::getAll());
|
||||
}
|
||||
|
||||
public function testItemCanExpire(): void
|
||||
{
|
||||
LightCache::set('test', 'value', 1);
|
||||
$this->assertSame('value', LightCache::get('test'));
|
||||
zm_sleep(2);
|
||||
$this->assertNull(LightCache::get('test'));
|
||||
}
|
||||
|
||||
public function testGetExpire(): void
|
||||
{
|
||||
LightCache::set('test', 'value', 10);
|
||||
$this->assertSame(10, LightCache::getExpire('test'));
|
||||
}
|
||||
|
||||
public function testGetExpireTS(): void
|
||||
{
|
||||
LightCache::set('test', 'value', 10);
|
||||
$this->assertSame(time() + 10, LightCache::getExpireTS('test'));
|
||||
}
|
||||
|
||||
public function testIsset(): void
|
||||
{
|
||||
LightCache::set('test', 'value');
|
||||
$this->assertTrue(LightCache::isset('test'));
|
||||
$this->assertFalse(LightCache::isset('test2'));
|
||||
}
|
||||
|
||||
public function testGetMemoryUsage(): void
|
||||
{
|
||||
LightCache::set('test', 'value');
|
||||
$this->assertGreaterThan(0, LightCache::getMemoryUsage());
|
||||
}
|
||||
|
||||
public function testUpdate(): void
|
||||
{
|
||||
LightCache::set('test', 'value');
|
||||
$this->assertTrue(LightCache::update('test', 'value2'));
|
||||
$this->assertSame('value2', LightCache::get('test'));
|
||||
}
|
||||
}
|
||||
71
tests/ZM/Utils/CommandInfoUtilTest.php
Normal file
71
tests/ZM/Utils/CommandInfoUtilTest.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\ZM\Utils;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ZM\Annotation\CQ\CommandArgument;
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
use ZM\Event\EventManager;
|
||||
use ZM\Utils\CommandInfoUtil;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class CommandInfoUtilTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var CommandInfoUtil
|
||||
*/
|
||||
private static $util;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private static $command_id;
|
||||
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
$cmd = new CQCommand('测试命令');
|
||||
$cmd->class = self::class;
|
||||
$cmd->method = __FUNCTION__;
|
||||
|
||||
$args = [
|
||||
new CommandArgument('文本', '一个神奇的文本', 'string', true),
|
||||
new CommandArgument('数字', '一个神奇的数字', 'int', false, '', '233'),
|
||||
];
|
||||
|
||||
self::$command_id = "{$cmd->class}@{$cmd->method}";
|
||||
|
||||
EventManager::$events[CQCommand::class] = [];
|
||||
EventManager::$event_map = [];
|
||||
EventManager::addEvent(CQCommand::class, $cmd);
|
||||
EventManager::$event_map[$cmd->class][$cmd->method] = $args;
|
||||
|
||||
self::$util = resolve(CommandInfoUtil::class);
|
||||
}
|
||||
|
||||
public function testGet(): void
|
||||
{
|
||||
$commands = self::$util->get();
|
||||
$this->assertIsArray($commands);
|
||||
$this->assertCount(1, $commands);
|
||||
$this->assertArrayHasKey(self::$command_id, $commands);
|
||||
}
|
||||
|
||||
public function testGetHelp(): void
|
||||
{
|
||||
$help = self::$util->getHelp(self::$command_id);
|
||||
$this->assertIsString($help);
|
||||
$this->assertNotEmpty($help);
|
||||
|
||||
$expected = <<<'EOF'
|
||||
测试命令 <文本: string> [数字: number = 233]
|
||||
作者很懒,啥也没说
|
||||
文本;一个神奇的文本
|
||||
数字;一个神奇的数字
|
||||
EOF;
|
||||
$this->assertEquals($expected, $help);
|
||||
}
|
||||
}
|
||||
@@ -29,8 +29,32 @@ class DataProviderTest extends TestCase
|
||||
|
||||
public function testScanDirFiles(): void
|
||||
{
|
||||
// 常规测试
|
||||
$files = DataProvider::scanDirFiles(SOURCE_ROOT_DIR . '/src/Module');
|
||||
$this->assertContains(SOURCE_ROOT_DIR . '/src/Module/Example/Hello.php', $files);
|
||||
// 检查文件夹不存在的判断
|
||||
$random_id = uuidgen();
|
||||
$dir = '/tmp/' . $random_id;
|
||||
// 检查目录不存在是否返回false
|
||||
$this->assertFalse(DataProvider::scanDirFiles($dir));
|
||||
mkdir($dir);
|
||||
// 检查目录存在时返回空
|
||||
$this->assertEquals([], DataProvider::scanDirFiles($dir));
|
||||
chmod($dir, 000);
|
||||
// 目录无权限时也返回false
|
||||
$this->assertFalse(DataProvider::scanDirFiles($dir));
|
||||
chmod($dir, 0755);
|
||||
mkdir($dir . '/test');
|
||||
file_put_contents($dir . '/test/a.txt', 'Hello world!');
|
||||
// 检查目录下文件是否正确返回
|
||||
$this->assertEquals([$dir . '/test/a.txt'], DataProvider::scanDirFiles($dir));
|
||||
// 检查不递归时包含目录的模式
|
||||
$this->assertEquals([], DataProvider::scanDirFiles($dir, false));
|
||||
$this->assertEquals([$dir . '/test'], DataProvider::scanDirFiles($dir, false, false, true));
|
||||
// 检查相对目录是否能正常返回
|
||||
$this->assertEquals(['test/a.txt'], DataProvider::scanDirFiles($dir, true, true));
|
||||
// relative传入奇怪的东西就返回false
|
||||
$this->assertFalse(DataProvider::scanDirFiles($dir, true, null, true));
|
||||
}
|
||||
|
||||
public function testGetFrameworkRootDir(): void
|
||||
|
||||
65
tests/ZM/Utils/MacroableTest.php
Normal file
65
tests/ZM/Utils/MacroableTest.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\ZM\Utils;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ZM\Exception\MethodNotFoundException;
|
||||
use ZM\Utils\Macroable;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class MacroableTest extends TestCase
|
||||
{
|
||||
private $macroable;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->macroable = new class() {
|
||||
use Macroable;
|
||||
|
||||
private $secret = 'secret';
|
||||
|
||||
private static function anotherSecret()
|
||||
{
|
||||
return 'another secret';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public function testMacroCanBeDefined(): void
|
||||
{
|
||||
$this->macroable::macro('getSecret', function () {
|
||||
return $this->secret;
|
||||
});
|
||||
|
||||
$this->assertEquals('secret', $this->macroable->getSecret());
|
||||
}
|
||||
|
||||
public function testMacroCanBeDefinedStatically(): void
|
||||
{
|
||||
$this->macroable::macro('getSecret', static function () {
|
||||
return 'static secret';
|
||||
});
|
||||
|
||||
$this->assertEquals('static secret', $this->macroable::getSecret());
|
||||
}
|
||||
|
||||
public function testMacroCanBeDefinedWithParameters(): void
|
||||
{
|
||||
$this->macroable::macro('getParam', function ($param) {
|
||||
return $param;
|
||||
});
|
||||
|
||||
$this->assertEquals('param', $this->macroable->getParam('param'));
|
||||
}
|
||||
|
||||
public function testExceptionIsThrownWhenMacroIsNotDefined(): void
|
||||
{
|
||||
$this->expectException(MethodNotFoundException::class);
|
||||
|
||||
$this->macroable->unknownMacro();
|
||||
}
|
||||
}
|
||||
@@ -44,17 +44,6 @@ class MessageUtilTest extends TestCase
|
||||
];
|
||||
}
|
||||
|
||||
public function testGenerateCommandHelp(): void
|
||||
{
|
||||
EventManager::$events[CQCommand::class] = [];
|
||||
$cmd = new CQCommand('测试命令');
|
||||
$cmd->class = self::class;
|
||||
$cmd->method = __FUNCTION__;
|
||||
EventManager::addEvent(CQCommand::class, $cmd);
|
||||
$help = MessageUtil::generateCommandHelp();
|
||||
$this->assertEquals('测试命令:无描述', $help[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providerTestArrayToStr
|
||||
*/
|
||||
|
||||
104
tests/ZM/Utils/ReflectionUtilTest.php
Normal file
104
tests/ZM/Utils/ReflectionUtilTest.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\ZM\Utils;
|
||||
|
||||
use Closure;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionFunctionAbstract;
|
||||
use ZM\Utils\ReflectionUtil;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ReflectionUtilTest extends TestCase
|
||||
{
|
||||
public function testDetermineStaticMethod(): void
|
||||
{
|
||||
$this->assertFalse(ReflectionUtil::isNonStaticMethod([ReflectionUtilTestClass::class, 'staticMethod']));
|
||||
}
|
||||
|
||||
public function testDetermineNonStaticMethod(): void
|
||||
{
|
||||
$this->assertTrue(ReflectionUtil::isNonStaticMethod([ReflectionUtilTestClass::class, 'method']));
|
||||
}
|
||||
|
||||
public function testGetParameterClassName(): void
|
||||
{
|
||||
$class = new \ReflectionClass(ReflectionUtilTestClass::class);
|
||||
$method = $class->getMethod('method');
|
||||
[$string_parameter, $object_parameter] = $method->getParameters();
|
||||
|
||||
$this->assertNull(ReflectionUtil::getParameterClassName($string_parameter));
|
||||
$this->assertSame(ReflectionUtilTestClass::class, ReflectionUtil::getParameterClassName($object_parameter));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideTestVariableToString
|
||||
* @param mixed $variable
|
||||
*/
|
||||
public function testVariableToString($variable, string $expected): void
|
||||
{
|
||||
$this->assertSame($expected, ReflectionUtil::variableToString($variable));
|
||||
}
|
||||
|
||||
public function provideTestVariableToString(): array
|
||||
{
|
||||
return [
|
||||
'callable' => [[new ReflectionUtilTestClass(), 'method'], ReflectionUtilTestClass::class . '@method'],
|
||||
'static callable' => [[ReflectionUtilTestClass::class, 'staticMethod'], ReflectionUtilTestClass::class . '::staticMethod'],
|
||||
'closure' => [Closure::fromCallable([$this, 'testVariableToString']), 'closure'],
|
||||
'string' => ['string', 'string'],
|
||||
'array' => [['123', '42', 'hello', 122], 'array["123","42","hello",122]'],
|
||||
'object' => [new \stdClass(), 'stdClass'],
|
||||
'resource' => [fopen('php://memory', 'rb'), 'resource(stream)'],
|
||||
'null' => [null, 'null'],
|
||||
'boolean 1' => [true, 'true'],
|
||||
'boolean 2' => [false, 'false'],
|
||||
'float' => [123.456, '123.456'],
|
||||
'integer' => [123, '123'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideTestGetCallReflector
|
||||
* @param mixed $callback
|
||||
*/
|
||||
public function testGetCallReflector($callback, ReflectionFunctionAbstract $expected): void
|
||||
{
|
||||
$this->assertEquals($expected, ReflectionUtil::getCallReflector($callback));
|
||||
}
|
||||
|
||||
public function provideTestGetCallReflector(): array
|
||||
{
|
||||
$closure = function () {
|
||||
};
|
||||
|
||||
return [
|
||||
'callable' => [[new ReflectionUtilTestClass(), 'method'], new \ReflectionMethod(ReflectionUtilTestClass::class, 'method')],
|
||||
'static callable' => [[ReflectionUtilTestClass::class, 'staticMethod'], new \ReflectionMethod(ReflectionUtilTestClass::class, 'staticMethod')],
|
||||
'class::method' => [ReflectionUtilTestClass::class . '::staticMethod', new \ReflectionMethod(ReflectionUtilTestClass::class, 'staticMethod')],
|
||||
'invokable class' => [new InvokableClass(), new \ReflectionMethod(InvokableClass::class, '__invoke')],
|
||||
'closure' => [$closure, new \ReflectionFunction($closure)],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class ReflectionUtilTestClass
|
||||
{
|
||||
public function method(string $string, ReflectionUtilTestClass $class): void
|
||||
{
|
||||
}
|
||||
|
||||
public static function staticMethod(string $string, ReflectionUtilTestClass $class): void
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
class InvokableClass
|
||||
{
|
||||
public function __invoke(): void
|
||||
{
|
||||
}
|
||||
}
|
||||
35
tests/ZM/Utils/TerminalTest.php
Normal file
35
tests/ZM/Utils/TerminalTest.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\ZM\Utils;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Throwable;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Utils\Terminal;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class TerminalTest extends TestCase
|
||||
{
|
||||
public function testInit()
|
||||
{
|
||||
Console::setLevel(4);
|
||||
Terminal::init();
|
||||
Console::setLevel(0);
|
||||
$this->expectOutputRegex('/Initializing\ Terminal/');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function testExecuteCommand()
|
||||
{
|
||||
Console::setLevel(2);
|
||||
Terminal::executeCommand('echo zhamao-framework');
|
||||
Console::setLevel(0);
|
||||
$this->expectOutputRegex('/zhamao-framework/');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user