mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-03-18 05:04:51 +08:00
commit
f4d75e211f
@ -16,6 +16,7 @@
|
||||
"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/polyfill-ctype": "^1.19",
|
||||
|
||||
@ -6,5 +6,7 @@ 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
|
||||
|
||||
@ -18,6 +18,7 @@ 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;
|
||||
@ -244,4 +245,16 @@ class Hello
|
||||
{
|
||||
bot()->all()->allGroups()->sendGroupMsg(0, ctx()->getMessage());
|
||||
}
|
||||
|
||||
/*
|
||||
* 欢迎来到容器时代
|
||||
*
|
||||
* @param Context $context 通过依赖注入实现的
|
||||
*
|
||||
* @CQCommand("容器你好")
|
||||
*/
|
||||
public function welcomeToContainerAge(Context $context)
|
||||
{
|
||||
$context->reply('欢迎来到容器时代');
|
||||
}
|
||||
}
|
||||
|
||||
98
src/ZM/Container/BoundMethod.php
Normal file
98
src/ZM/Container/BoundMethod.php
Normal file
@ -0,0 +1,98 @@
|
||||
<?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 $parameter) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
75
src/ZM/Container/Container.php
Normal file
75
src/ZM/Container/Container.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Container;
|
||||
|
||||
class Container extends WorkerContainer
|
||||
{
|
||||
/**
|
||||
* 父容器
|
||||
*
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
protected $parent;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->parent = WorkerContainer::getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取父容器
|
||||
*/
|
||||
public function getParent(): ContainerInterface
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->parent->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->parent->bound($abstract)) {
|
||||
return $this->parent->make($abstract, $parameters);
|
||||
}
|
||||
|
||||
return parent::make($abstract, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有绑定和实例
|
||||
*/
|
||||
public function flush(): void
|
||||
{
|
||||
parent::flush();
|
||||
$this->parent->flush();
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
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
|
||||
{
|
||||
}
|
||||
739
src/ZM/Container/WorkerContainer.php
Normal file
739
src/ZM/Container/WorkerContainer.php
Normal file
@ -0,0 +1,739 @@
|
||||
<?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;
|
||||
use ZM\Utils\SingletonTrait;
|
||||
|
||||
class WorkerContainer implements ContainerInterface
|
||||
{
|
||||
use SingletonTrait;
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
@ -188,7 +188,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 +233,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 +285,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;
|
||||
}
|
||||
|
||||
@ -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() . ';');
|
||||
|
||||
@ -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);
|
||||
@ -274,7 +278,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 +288,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();
|
||||
}
|
||||
|
||||
// 初始化服务器
|
||||
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
|
||||
@ -510,8 +529,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 +545,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 +575,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)) {
|
||||
@ -623,6 +642,7 @@ class Framework
|
||||
|
||||
/**
|
||||
* 从全局配置文件里读取注入系统事件的类
|
||||
*
|
||||
* @throws ReflectionException
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
@ -659,6 +679,7 @@ class Framework
|
||||
|
||||
/**
|
||||
* 解析命令行的 $argv 参数们
|
||||
*
|
||||
* @param array $args 命令行参数
|
||||
* @param bool|string $add_port 是否添加端口号
|
||||
*/
|
||||
@ -672,15 +693,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 +717,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':
|
||||
|
||||
@ -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 array<int, ContextInterface>
|
||||
*/
|
||||
public static $context_class = [];
|
||||
|
||||
/**
|
||||
* 终端输入流?
|
||||
*
|
||||
* 目前等用于 STDIN
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
public static $terminal;
|
||||
}
|
||||
|
||||
@ -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,6 +126,7 @@ class DataProvider
|
||||
|
||||
/**
|
||||
* 递归或非递归扫描目录,可返回相对目录的文件列表或绝对目录的文件列表
|
||||
*
|
||||
* @param string $dir 目录
|
||||
* @param bool $recursive 是否递归扫描子目录
|
||||
* @param bool|string $relative 是否返回相对目录,如果为true则返回相对目录,如果为false则返回绝对目录
|
||||
@ -161,6 +170,7 @@ class DataProvider
|
||||
|
||||
/**
|
||||
* 检查路径是否为相对路径(根据第一个字符是否为"/"来判断)
|
||||
*
|
||||
* @param string $path 路径
|
||||
* @return bool 返回结果
|
||||
* @since 2.5
|
||||
@ -169,4 +179,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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,8 @@ 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;
|
||||
@ -40,6 +42,7 @@ function get_class_path(string $class_name): ?string
|
||||
|
||||
/**
|
||||
* 检查炸毛框架运行的环境
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function _zm_env_check()
|
||||
@ -220,7 +223,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 +233,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 +241,6 @@ function set_coroutine_params(array $params): void
|
||||
|
||||
/**
|
||||
* 获取当前上下文
|
||||
*
|
||||
* @throws ZMKnownException
|
||||
*/
|
||||
function context(): ContextInterface
|
||||
{
|
||||
@ -248,18 +249,16 @@ 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));
|
||||
@ -321,7 +320,7 @@ function zm_exec(string $command): array
|
||||
*/
|
||||
function zm_cid(): int
|
||||
{
|
||||
return Co::getCid();
|
||||
return co::getCid();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -331,7 +330,7 @@ function zm_cid(): int
|
||||
*/
|
||||
function zm_yield()
|
||||
{
|
||||
Co::yield();
|
||||
co::yield();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -341,7 +340,7 @@ function zm_yield()
|
||||
*/
|
||||
function zm_resume(int $cid)
|
||||
{
|
||||
Co::resume($cid);
|
||||
co::resume($cid);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -416,6 +415,7 @@ function server(): Server
|
||||
|
||||
/**
|
||||
* 获取缓存当前框架pid的临时目录
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function _zm_pid_dir(): string
|
||||
@ -433,6 +433,7 @@ function _zm_pid_dir(): string
|
||||
* 随机返回一个 ZMRobot 实例,效果等同于 {@link ZMRobot::getRandom()}。
|
||||
*
|
||||
* 在单机器人模式下,会直接返回该机器人实例。
|
||||
*
|
||||
* @throws RobotNotFoundException
|
||||
*/
|
||||
function bot(): ZMRobot
|
||||
@ -638,6 +639,42 @@ 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 以下为废弃的函数,将于未来移除
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
30
tests/ZM/Container/ContainerTest.php
Normal file
30
tests/ZM/Container/ContainerTest.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?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 testInherit(): void
|
||||
{
|
||||
$worker_container = new WorkerContainer();
|
||||
$worker_container->instance('foo', 'bar');
|
||||
|
||||
$container = new Container($worker_container);
|
||||
$container->instance('baz', 'qux');
|
||||
|
||||
// 获取父容器的实例
|
||||
$this->assertEquals('bar', $container->get('foo'));
|
||||
|
||||
// 获取自身容器的实例
|
||||
$this->assertEquals('qux', $container->get('baz'));
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user