zhamao-framework/src/ZM/global_functions.php
2022-05-04 22:25:42 +08:00

875 lines
20 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
use Swoole\Atomic;
use Swoole\Coroutine;
use Swoole\Coroutine as Co;
use Swoole\Coroutine\System;
use Swoole\WebSocket\Server;
use Symfony\Component\VarDumper\VarDumper;
use ZM\API\OneBotV11;
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\Framework;
use ZM\Store\LightCacheInside;
use ZM\Store\ZMAtomic;
use ZM\Store\ZMBuf;
use ZM\Utils\DataProvider;
/**
* 获取类路径
*/
function get_class_path(string $class_name): ?string
{
$dir = str_replace('\\', '/', $class_name);
$dir = DataProvider::getSourceRootDir() . '/src/' . $dir . '.php';
$dir = str_replace('\\', '/', $dir);
if (file_exists($dir)) {
return $dir;
}
return null;
}
/**
* 检查炸毛框架运行的环境
*
* @internal
*/
function _zm_env_check()
{
if (!extension_loaded('swoole')) {
exit(zm_internal_errcode('E00001') . "Can not find swoole extension.\n");
}
if (version_compare(SWOOLE_VERSION, '4.5.0') === -1) {
exit(zm_internal_errcode('E00002') . 'You must install swoole version >= 4.5.0 !');
}
if (version_compare(PHP_VERSION, '7.2') === -1) {
exit(zm_internal_errcode('E00003') . 'PHP >= 7.2 required.');
}
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();
}
}
/**
* 分割消息字符串
*
* @param array $includes 需要进行切割的字符串,默认包含空格及制表符(\t)
*/
function explode_msg(string $msg, array $includes = [' ', "\t"]): array
{
$msg = trim($msg);
foreach ($includes as $v) {
$msg = str_replace($v, "\n", $msg);
}
$msg_seg = explode("\n", $msg);
$ls = [];
foreach ($msg_seg as $v) {
if (empty(trim($v))) {
continue;
}
$ls[] = trim($v);
}
return $ls;
}
/**
* 解码Unicode字符串
*/
function unicode_decode(string $str): ?string
{
return preg_replace_callback('/\\\\u([0-9a-f]{4})/i', static function ($matches) {
return mb_convert_encoding(pack('H*', $matches[1]), 'UTF-8', 'UCS-2BE');
}, $str);
}
/**
* 格式匹配
*/
function match_pattern(string $pattern, string $subject): bool
{
$pattern = str_replace(['\*', '\\\\.*'], ['.*', '\*'], preg_quote($pattern, '/'));
$pattern = '/^' . $pattern . '$/i';
return preg_match($pattern, $subject) === 1;
}
/**
* @requires symfony/polyfill-ctype
* @return array|string[]
*/
function split_explode(string $del, string $string, bool $divide_en = false): array
{
$str = explode($del, $string);
for ($i = 0, $i_max = mb_strlen($str[0]); $i < $i_max; ++$i) {
if (
is_numeric(mb_substr($str[0], $i, 1))
&& (
!is_numeric(mb_substr($str[0], $i - 1, 1))
&& mb_substr($str[0], $i - 1, 1) !== ' '
&& ctype_alpha(mb_substr($str[0], $i - 1, 1)) === false
)
) {
$str[0] = mb_substr($str[0], 0, $i) . ' ' . mb_substr($str[0], $i);
} elseif (
$divide_en
&& mb_substr($str[0], $i - 1, 1) !== ' '
&& ctype_alnum(mb_substr($str[0], $i, 1))
&& !ctype_alnum(mb_substr($str[0], $i - 1, 1))
) {
$str[0] = mb_substr($str[0], 0, $i) . ' ' . mb_substr($str[0], $i);
}
}
$str = implode($del, $str);
$ls = [];
foreach (explode($del, $str) as $v) {
if (empty(trim($v))) {
continue;
}
$ls[] = $v;
}
return $ls ?: [''];
}
/**
* 匹配参数
*
* @return array|false 成功时返回匹配到的参数数组失败时返回false
*/
function match_args(string $pattern, string $subject)
{
$result = [];
if (match_pattern($pattern, $subject)) {
if (mb_strpos($pattern, '*') === false) {
return [];
}
$exp = explode('*', $pattern);
$i = 0;
foreach ($exp as $k => $v) {
if (empty($v) && $k === 0) {
continue;
}
if (empty($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)) {
$cur_var .= mb_substr($subject, $i, 1);
++$i;
}
if ($i !== $ori || $k === 1 || $k === count($exp) - 1) {
$result[] = $cur_var;
}
$i += mb_strlen($v);
}
return $result;
}
return false;
}
/**
* 判断当前连接类型是否为传入的$type
*
* @param string $type 连接类型
*/
function current_connection_is(string $type): bool
{
return ctx()->getConnection()->getName() === $type;
}
/**
* 获取触发当前方法的注解
*/
function get_annotations(): array
{
$s = debug_backtrace()[1];
$list = [];
foreach (EventManager::$events as $v) {
foreach ($v as $vs) {
if ($vs->class === $s['class'] && $vs->method === $s['function']) {
$list[get_class($vs)][] = $vs;
}
}
}
return $list;
}
/**
* 设置协程参数
*/
function set_coroutine_params(array $params): void
{
$cid = co::getCid();
if ($cid === -1) {
exit(zm_internal_errcode('E00061') . 'Cannot set coroutine params at none coroutine mode.');
}
if (isset(Context::$context[$cid])) {
Context::$context[$cid] = array_merge(Context::$context[$cid], $params);
} else {
Context::$context[$cid] = $params;
}
foreach (Context::$context as $c => $v) {
if (!co::exists($c)) {
unset(Context::$context[$c], ZMBuf::$context_class[$c]);
}
}
}
/**
* 获取当前上下文
*/
function context(): ContextInterface
{
return ctx();
}
/**
* 获取当前上下文
*/
function ctx(): ContextInterface
{
$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) {
$cid = $parent_cid;
if (isset(Context::$context[$cid])) {
return ZMBuf::$context_class[$cid] ?? (ZMBuf::$context_class[$cid] = new $c_class($cid));
}
}
Console::warning('当前环境不是协程环境,将返回独立的非协程的容器');
return ZMBuf::$context_class[$cid] ?? (ZMBuf::$context_class[$cid] = new $c_class($cid));
}
/**
* 根据消息类型获取对应的OneBot目标名称
*
* @return string 如传入的消息类型不被支持,将默认返回`user_id`
*/
function get_onebot_target_id_name(string $message_type): string
{
switch ($message_type) {
case 'group':
return 'group_id';
case 'discuss':
return 'discuss_id';
case 'private':
default:
return 'user_id';
}
}
/**
* 协程休眠
*
* 与 {@link sleep()} 一致,只是增加了协程支持
*
* @since 2.7.3 此函数不再返回 true
*/
function zm_sleep(int $seconds = 1): void
{
if (Coroutine::getCid() !== -1) {
System::sleep($seconds);
} else {
usleep($seconds * 1000 * 1000);
}
}
/**
* 协程执行命令
*
* 与 {@link exec()} 一致,只是增加了协程支持
*
* @return array{code: int, signal: int, output: string}
*/
function zm_exec(string $command): array
{
return System::exec($command);
}
/**
* 获取当前协程ID
*
* 与 {@link Co::getCid()} 一致
*/
function zm_cid(): int
{
return co::getCid();
}
/**
* 挂起当前协程
*
* 与 {@link Co::yield()} 一致
*/
function zm_yield()
{
co::yield();
}
/**
* 恢复并继续执行指定协程
*
* 与 {@link Co::resume()} 一致
*/
function zm_resume(int $cid)
{
co::resume($cid);
}
/**
* 指定延迟后执行函数
*
* @param int $delay 延迟时间单位毫秒ms
*/
function zm_timer_after(int $delay, callable $runnable)
{
Swoole\Timer::after($delay, static function () use ($runnable) {
call_with_catch($runnable);
});
}
/**
* 重复在指定时间间隔后执行函数
*
* @param int $interval 间隔时间单位毫秒ms
* @return false|int 定时器ID失败返回false
*/
function zm_timer_tick(int $interval, callable $runnable)
{
if (zm_cid() === -1) {
return go(static function () use ($interval, $runnable) {
Console::debug('Adding extra timer tick of ' . $interval . ' ms');
Swoole\Timer::tick($interval, static function () use ($runnable) {
call_with_catch($runnable);
});
});
}
return Swoole\Timer::tick($interval, static function () use ($runnable) {
call_with_catch($runnable);
});
}
/**
* 执行函数并记录异常
*/
function call_with_catch(callable $callable): void
{
try {
$callable();
} catch (Exception $e) {
$error_msg = $e->getMessage() . ' at ' . $e->getFile() . '(' . $e->getLine() . ')';
Console::error(zm_internal_errcode('E00033') . 'Uncaught exception ' . get_class($e) . ': ' . $error_msg);
Console::trace();
} catch (Error $e) {
$error_msg = $e->getMessage() . ' at ' . $e->getFile() . '(' . $e->getLine() . ')';
Console::error(zm_internal_errcode('E00033') . 'Uncaught ' . get_class($e) . ': ' . $error_msg);
Console::trace();
}
}
/**
* 生成消息的哈希值
*/
function hash_message(array $message): string
{
return md5($message['user_id'] . '^' . $message['self_id'] . '^' . $message['message_type'] . '^' . ($message[$message['message_type'] . '_id'] ?? $message['user_id']));
}
/**
* 获取 Swoole Server 实例
*
* 与 {@link Framework::$server} 一致
*/
function server(): Server
{
return Framework::$server;
}
/**
* 获取缓存当前框架pid的临时目录
*
* @internal
*/
function _zm_pid_dir(): string
{
global $_ZM_HASH;
if (!isset($_ZM_HASH)) {
$_ZM_HASH = md5(DataProvider::getWorkingDir());
}
return '/tmp/.zm_' . $_ZM_HASH;
}
/**
* 获取 ZMRobot 实例
*
* 随机返回一个 ZMRobot 实例,效果等同于 {@link ZMRobot::getRandom()}。
*
* 在单机器人模式下,会直接返回该机器人实例。
*
* @throws RobotNotFoundException
*/
function bot(): ZMRobot
{
if (($conn = LightCacheInside::get('connect', 'conn_fd')) === -2) {
return OneBotV11::getRandom();
}
if ($conn !== -1) {
if (($obj = ManagerGM::get($conn)) !== null) {
return new ZMRobot($obj);
}
throw new RobotNotFoundException('单机器人连接模式可能连接了多个机器人!');
}
throw new RobotNotFoundException('没有任何机器人连接到框架!');
}
/**
* 获取指定连接类型的文件描述符ID
*/
function get_all_fd_of_type(string $type = 'default'): array
{
$fds = [];
foreach (ManagerGM::getAllByName($type) as $obj) {
$fds[] = $obj->getFd();
}
return $fds;
}
/**
* 获取原子计数器
*
* 与 {@link ZMAtomic::get()} 一致
*/
function zm_atomic(string $name): ?Atomic
{
return ZMAtomic::get($name);
}
/**
* 生成 UUID
*/
function uuidgen(bool $uppercase = false): string
{
try {
$data = random_bytes(16);
} catch (Exception $e) {
return '';
}
$data[6] = chr(ord($data[6]) & 0x0F | 0x40);
$data[8] = chr(ord($data[8]) & 0x3F | 0x80);
return $uppercase ? strtoupper(vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4))) :
vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
/**
* 获取框架运行的工作目录
*
* @example 例如你是从 /root/framework-starter/ 目录启动的框架vendor/bin/start server那么 working_dir() 返回的就是 /root/framework-starter。
*/
function working_dir(): string
{
return WORKING_DIR;
}
/**
* 更漂亮地输出变量值
*
* 可替代 {@link var_dump()}
*
* @param mixed $var
* @return array|mixed 返回传入的变量,传入多个变量则会返回数组
*/
function zm_dump($var, ...$moreVars)
{
VarDumper::dump($var);
foreach ($moreVars as $v) {
VarDumper::dump($v);
}
if (func_num_args() > 1) {
return func_get_args();
}
return $var;
}
/**
* 输出info日志
*
* 与 {@link Console::info()} 一致
*
* @param mixed $obj
*/
function zm_info($obj): void
{
Console::info($obj);
}
/**
* 输出warning日志
*
* 与 {@link Console::warning()} 一致
*
* @param mixed $obj
*/
function zm_warning($obj): void
{
Console::warning($obj);
}
/**
* 输出success日志
*
* 与 {@link Console::success()} 一致
*
* @param mixed $obj
*/
function zm_success($obj): void
{
Console::success($obj);
}
/**
* 输出debug日志
*
* 与 {@link Console::debug()} 一致
*
* @param mixed $obj
*/
function zm_debug($obj): void
{
Console::debug($obj);
}
/**
* 输出verbose日志
*
* 与 {@link Console::verbose()} 一致
*
* @param mixed $obj
*/
function zm_verbose($obj): void
{
Console::verbose($obj);
}
/**
* 输出error日志
*
* 与 {@link Console::error()} 一致
*
* @param mixed $obj
*/
function zm_error($obj): void
{
Console::error($obj);
}
/**
* 获取配置项
*
* 与 {@link ZMConfig::get()} 一致
*
* @return mixed
*/
function zm_config(string $name, ?string $key = null)
{
return ZMConfig::get($name, $key);
}
/**
* 生成快速回复闭包
*
* @param mixed $reply
*/
function quick_reply_closure($reply): Closure
{
return static function () use ($reply) {
return $reply;
};
}
/**
* 获取内部错误码
*
* @param int|string $code
*/
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);
}
/**
* 以下为废弃的函数,将于未来移除
*/
/**
* @deprecated 已废弃,请使用 {@link get_all_fd_of_type()}
*/
function getAllFdByConnectType(string $type = 'default'): array
{
return get_all_fd_of_type($type);
}
/**
* @param mixed $class_name
* @deprecated 已废弃,请使用 {@link get_class_path()}
*/
function getClassPath($class_name): ?string
{
return get_class_path($class_name);
}
/**
* @param mixed $msg
* @param mixed $ban_comma
* @deprecated 已废弃,请使用 {@link explode_msg()},参数有变
*/
function explodeMsg($msg, $ban_comma = false): array
{
if ($ban_comma) {
return explode_msg($msg, [' ']);
}
return explode_msg($msg);
}
/**
* @deprecated 已废弃,请使用 {@link current_connection_is()}
*/
function connectIsQQ(): bool
{
return current_connection_is('qq');
}
/**
* @deprecated 已废弃,请使用 {@link current_connection_is()}
*/
function connectIsDefault(): bool
{
return current_connection_is('default');
}
/**
* @param mixed $type
* @deprecated 已废弃,请使用 {@link current_connection_is()}
*/
function connectIs($type): bool
{
return current_connection_is($type);
}
/**
* @deprecated 已废弃,请使用 {@link get_annotations()}
*/
function getAnnotations(): array
{
return get_annotations();
}
/**
* @param mixed $pattern
* @param mixed $context
* @deprecated 已废弃,请使用 {@link match_args()}
*/
function matchArgs($pattern, $context)
{
return match_args($pattern, $context);
}
/**
* @param mixed $pattern
* @param mixed $context
* @deprecated 已废弃,请使用 {@link match_pattern()}
*/
function matchPattern($pattern, $context): bool
{
return match_pattern($pattern, $context);
}
/**
* @param string $message_type 消息类型
* @deprecated 已废弃,请使用 {@link get_onebot_target_id_name()}
*/
function onebot_target_id_name(string $message_type): string
{
return get_onebot_target_id_name($message_type);
}
/**
* @deprecated 已废弃,请直接使用 {@link call_with_catch()}
*/
function zm_go(callable $callable)
{
call_with_catch($callable);
}
/**
* @param mixed $v
* @deprecated 已废弃,请使用 {@link hash_message()}
*/
function zm_data_hash($v): string
{
return hash_message($v);
}