mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-03-17 20:54:52 +08:00
add worker container
This commit is contained in:
parent
0cd73a6304
commit
1d778fd78e
@ -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,6 @@ 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#'
|
||||
dynamicConstantNames:
|
||||
- SWOOLE_VERSION
|
||||
|
||||
108
src/ZM/Container/ContainerInterface.php
Normal file
108
src/ZM/Container/ContainerInterface.php
Normal file
@ -0,0 +1,108 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* 获取一个绑定的实例
|
||||
*
|
||||
* @param string $abstract 类或接口名
|
||||
* @param array $parameters 参数
|
||||
* @return mixed 实例
|
||||
*/
|
||||
public function make(string $abstract, array $parameters = []);
|
||||
|
||||
/**
|
||||
* 调用对应的方法,并自动注入依赖
|
||||
*
|
||||
* @param callable $callback 对应的方法
|
||||
* @param array $parameters 参数
|
||||
* @return mixed
|
||||
*/
|
||||
public function call(callable $callback, array $parameters = []);
|
||||
}
|
||||
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
|
||||
{
|
||||
}
|
||||
624
src/ZM/Container/WorkerContainer.php
Normal file
624
src/ZM/Container/WorkerContainer.php
Normal file
@ -0,0 +1,624 @@
|
||||
<?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;
|
||||
|
||||
class WorkerContainer implements ContainerInterface
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $shared = [];
|
||||
|
||||
/**
|
||||
* @var array[]
|
||||
*/
|
||||
protected $buildStack = [];
|
||||
|
||||
/**
|
||||
* @var array[]
|
||||
*/
|
||||
protected $with = [];
|
||||
|
||||
/**
|
||||
* @var array[]
|
||||
*/
|
||||
private static $bindings = [];
|
||||
|
||||
/**
|
||||
* @var object[]
|
||||
*/
|
||||
private static $instances = [];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private static $aliases = [];
|
||||
|
||||
/**
|
||||
* @var Closure[][]
|
||||
*/
|
||||
private static $extenders = [];
|
||||
|
||||
/**
|
||||
* 判断对应的类或接口是否已经注册
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册绑定
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
// 如果不是闭包,则认为是类名,此时将其包装在一个闭包中,以方便后续处理
|
||||
if (!$concrete instanceof Closure) {
|
||||
$concrete = $this->getClosure($abstract, $concrete);
|
||||
}
|
||||
|
||||
self::$bindings[$abstract] = compact('concrete', '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;
|
||||
|
||||
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->buildStack = [];
|
||||
$this->with = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个绑定的实例
|
||||
*
|
||||
* @param string $abstract 类或接口名
|
||||
* @param array $parameters 参数
|
||||
* @throws EntryResolutionException
|
||||
* @throws ReflectionException
|
||||
* @return mixed 实例
|
||||
*/
|
||||
public function make(string $abstract, array $parameters = [])
|
||||
{
|
||||
$abstract = $this->getAlias($abstract);
|
||||
|
||||
$needs_contextual_build = !empty($parameters);
|
||||
|
||||
if (isset($this->shared[$abstract])) {
|
||||
return $this->shared[$abstract];
|
||||
}
|
||||
|
||||
// 如果已经存在在实例池中(通常意味着单例绑定),则直接返回该实例
|
||||
if (isset(self::$instances[$abstract]) && !$needs_contextual_build) {
|
||||
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;
|
||||
}
|
||||
|
||||
// 弹出本次构造的覆盖参数
|
||||
array_pop($this->with);
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实例化具体的类实例
|
||||
*
|
||||
* @param Closure|string $concrete 类名或对应的闭包
|
||||
* @throws EntryResolutionException
|
||||
* @throws ReflectionException
|
||||
* @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} 不存在");
|
||||
}
|
||||
|
||||
if (!$reflection->isInstantiable()) {
|
||||
$this->notInstantiable($concrete);
|
||||
}
|
||||
|
||||
$this->buildStack[] = $concrete;
|
||||
|
||||
$constructor = $reflection->getConstructor();
|
||||
|
||||
// 如果不存在构造函数,则代表不需要进一步解析,直接实例化即可
|
||||
if (is_null($constructor)) {
|
||||
array_pop($this->buildStack);
|
||||
return new $concrete();
|
||||
}
|
||||
|
||||
$dependencies = $constructor->getParameters();
|
||||
|
||||
// 获取所有依赖的实例
|
||||
$instances = $this->resolveDependencies($dependencies);
|
||||
|
||||
array_pop($this->buildStack);
|
||||
|
||||
return $reflection->newInstanceArgs($instances);
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用对应的方法,并自动注入依赖
|
||||
*
|
||||
* @param callable $callback 对应的方法
|
||||
* @param array $parameters 参数
|
||||
* @return mixed
|
||||
*/
|
||||
public function call(callable $callback, array $parameters = [])
|
||||
{
|
||||
// TODO: Implement call() 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对应类型的所有扩展器
|
||||
*
|
||||
* @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->buildStack)) {
|
||||
$previous = implode(', ', $this->buildStack);
|
||||
$message = "类 {$concrete} 无法实例化,其被 {$previous} 依赖";
|
||||
} else {
|
||||
$message = "类 {$concrete} 无法实例化";
|
||||
}
|
||||
|
||||
throw new EntryResolutionException("{$message}:{$reason}");
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析依赖
|
||||
*
|
||||
* @param ReflectionParameter[] $dependencies
|
||||
* @throws EntryResolutionException
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
// 获取参数类型
|
||||
$type = $dependency->getType();
|
||||
// 没有声明类型或为基本类型
|
||||
if (!$type instanceof ReflectionNamedType || $type->isBuiltin()) {
|
||||
$class_name = null;
|
||||
} else {
|
||||
// 获取类名
|
||||
$class_name = $type->getName();
|
||||
// 如果存在父类
|
||||
if (!is_null($class = $dependency->getDeclaringClass())) {
|
||||
if ($class_name === 'self') {
|
||||
$class_name = $class->getName();
|
||||
} elseif ($class_name === 'parent') {
|
||||
$class_name = $class->getParentClass()->getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果类名为空,则代表此依赖是基本类型,且无法对其进行依赖解析
|
||||
$results[] = is_null($class_name)
|
||||
? $this->resolvePrimitive($dependency)
|
||||
: $this->resolveClass($dependency);
|
||||
}
|
||||
|
||||
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 如果无法解析类,则抛出异常
|
||||
* @throws ReflectionException 实际上不会抛出,请无视
|
||||
* @return mixed
|
||||
*/
|
||||
protected function resolveClass(ReflectionParameter $parameter)
|
||||
{
|
||||
try {
|
||||
// 尝试解析
|
||||
return $this->make($parameter->getClass()->name);
|
||||
} catch (EntryResolutionException $e) {
|
||||
// 如果参数是可选的,则返回默认值
|
||||
if ($parameter->isOptional()) {
|
||||
return $parameter->getDefaultValue();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
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