mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-03-17 20:54:52 +08:00
737 lines
20 KiB
PHP
737 lines
20 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace ZM\Container;
|
||
|
||
use Closure;
|
||
use Exception;
|
||
use InvalidArgumentException;
|
||
use Psr\Container\ContainerExceptionInterface;
|
||
use Psr\Container\NotFoundExceptionInterface;
|
||
use ReflectionClass;
|
||
use ReflectionException;
|
||
use ReflectionNamedType;
|
||
use ReflectionParameter;
|
||
use ZM\Console\Console;
|
||
use ZM\Utils\ReflectionUtil;
|
||
|
||
trait ContainerTrait
|
||
{
|
||
/**
|
||
* @var array
|
||
*/
|
||
protected $shared = [];
|
||
|
||
/**
|
||
* @var array[]
|
||
*/
|
||
protected $build_stack = [];
|
||
|
||
/**
|
||
* @var array[]
|
||
*/
|
||
protected $with = [];
|
||
|
||
/**
|
||
* 日志前缀
|
||
*
|
||
* @var string
|
||
*/
|
||
protected $log_prefix;
|
||
|
||
/**
|
||
* @var array[]
|
||
*/
|
||
private static $bindings = [];
|
||
|
||
/**
|
||
* @var object[]
|
||
*/
|
||
private static $instances = [];
|
||
|
||
/**
|
||
* @var string[]
|
||
*/
|
||
private static $aliases = [];
|
||
|
||
/**
|
||
* @var Closure[][]
|
||
*/
|
||
private static $extenders = [];
|
||
|
||
public function __construct()
|
||
{
|
||
if ($this->shouldLog()) {
|
||
$this->log('Container created');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 判断对应的类或接口是否已经注册
|
||
*
|
||
* @param string $abstract 类或接口名
|
||
*/
|
||
public function bound(string $abstract): bool
|
||
{
|
||
return array_key_exists($abstract, self::$bindings)
|
||
|| array_key_exists($abstract, self::$instances)
|
||
|| array_key_exists($abstract, $this->shared)
|
||
|| $this->isAlias($abstract);
|
||
}
|
||
|
||
/**
|
||
* 获取类别名(如存在)
|
||
*
|
||
* @param string $abstract 类或接口名
|
||
* @return string 别名,不存在时返回传入的类或接口名
|
||
*/
|
||
public function getAlias(string $abstract): string
|
||
{
|
||
if (!isset(self::$aliases[$abstract])) {
|
||
return $abstract;
|
||
}
|
||
|
||
return $this->getAlias(self::$aliases[$abstract]);
|
||
}
|
||
|
||
/**
|
||
* 注册一个类别名
|
||
*
|
||
* @param string $abstract 类或接口名
|
||
* @param string $alias 别名
|
||
*/
|
||
public function alias(string $abstract, string $alias): void
|
||
{
|
||
if ($alias === $abstract) {
|
||
throw new InvalidArgumentException("[{$abstract}] is same as [{$alias}]");
|
||
}
|
||
|
||
self::$aliases[$alias] = $abstract;
|
||
|
||
if ($this->shouldLog()) {
|
||
$this->log("[{$abstract}] is aliased as [{$alias}]");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 注册绑定
|
||
*
|
||
* @param string $abstract 类或接口名
|
||
* @param null|Closure|string $concrete 返回类实例的闭包,或是类名
|
||
* @param bool $shared 是否共享
|
||
*/
|
||
public function bind(string $abstract, $concrete = null, bool $shared = false): void
|
||
{
|
||
$this->dropStaleInstances($abstract);
|
||
|
||
// 如果没有提供闭包,则默认为自动解析类名
|
||
if (is_null($concrete)) {
|
||
$concrete = $abstract;
|
||
}
|
||
|
||
$concrete_name = '';
|
||
if ($this->shouldLog()) {
|
||
$concrete_name = ReflectionUtil::variableToString($concrete);
|
||
}
|
||
|
||
// 如果不是闭包,则认为是类名,此时将其包装在一个闭包中,以方便后续处理
|
||
if (!$concrete instanceof Closure) {
|
||
$concrete = $this->getClosure($abstract, $concrete);
|
||
}
|
||
|
||
self::$bindings[$abstract] = compact('concrete', 'shared');
|
||
|
||
if ($this->shouldLog()) {
|
||
$this->log("[{$abstract}] is bound to [{$concrete_name}]" . ($shared ? ' (shared)' : ''));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 注册绑定
|
||
*
|
||
* 在已经绑定时不会重复注册
|
||
*
|
||
* @param string $abstract 类或接口名
|
||
* @param null|Closure|string $concrete 返回类实例的闭包,或是类名
|
||
* @param bool $shared 是否共享
|
||
*/
|
||
public function bindIf(string $abstract, $concrete = null, bool $shared = false): void
|
||
{
|
||
if (!$this->bound($abstract)) {
|
||
$this->bind($abstract, $concrete, $shared);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 注册一个单例绑定
|
||
*
|
||
* @param string $abstract 类或接口名
|
||
* @param null|Closure|string $concrete 返回类实例的闭包,或是类名
|
||
*/
|
||
public function singleton(string $abstract, $concrete = null): void
|
||
{
|
||
$this->bind($abstract, $concrete, true);
|
||
}
|
||
|
||
/**
|
||
* 注册一个单例绑定
|
||
*
|
||
* 在已经绑定时不会重复注册
|
||
*
|
||
* @param string $abstract 类或接口名
|
||
* @param null|Closure|string $concrete 返回类实例的闭包,或是类名
|
||
*/
|
||
public function singletonIf(string $abstract, $concrete = null): void
|
||
{
|
||
if (!$this->bound($abstract)) {
|
||
$this->singleton($abstract, $concrete);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 注册一个已有的实例,效果等同于单例绑定
|
||
*
|
||
* @param string $abstract 类或接口名
|
||
* @param mixed $instance 实例
|
||
* @return mixed
|
||
*/
|
||
public function instance(string $abstract, $instance)
|
||
{
|
||
if (isset(self::$instances[$abstract])) {
|
||
return self::$instances[$abstract];
|
||
}
|
||
|
||
self::$instances[$abstract] = $instance;
|
||
|
||
if ($this->shouldLog()) {
|
||
$class_name = ReflectionUtil::variableToString($instance);
|
||
$this->log("[{$abstract}] is bound to [{$class_name}] (instance)");
|
||
}
|
||
|
||
return $instance;
|
||
}
|
||
|
||
/**
|
||
* 获取一个解析对应类实例的闭包
|
||
*
|
||
* @param string $abstract 类或接口名
|
||
*/
|
||
public function factory(string $abstract): Closure
|
||
{
|
||
return function () use ($abstract) {
|
||
return $this->make($abstract);
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 清除所有绑定和实例
|
||
*/
|
||
public function flush(): void
|
||
{
|
||
self::$aliases = [];
|
||
self::$bindings = [];
|
||
self::$instances = [];
|
||
|
||
$this->shared = [];
|
||
$this->build_stack = [];
|
||
$this->with = [];
|
||
|
||
if ($this->shouldLog()) {
|
||
$this->log('Container flushed');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取一个绑定的实例
|
||
*
|
||
* @template T
|
||
* @param class-string<T> $abstract 类或接口名
|
||
* @param array $parameters 参数
|
||
* @throws EntryResolutionException
|
||
* @return Closure|mixed|T 实例
|
||
*/
|
||
public function make(string $abstract, array $parameters = [])
|
||
{
|
||
$abstract = $this->getAlias($abstract);
|
||
|
||
$needs_contextual_build = !empty($parameters);
|
||
|
||
if (isset($this->shared[$abstract])) {
|
||
if ($this->shouldLog()) {
|
||
$this->log(sprintf(
|
||
'[%s] resolved (shared)%s',
|
||
$abstract,
|
||
($needs_contextual_build ? ' with ' . implode(', ', $parameters) : '')
|
||
));
|
||
}
|
||
return $this->shared[$abstract];
|
||
}
|
||
|
||
// 如果已经存在在实例池中(通常意味着单例绑定),则直接返回该实例
|
||
if (isset(self::$instances[$abstract]) && !$needs_contextual_build) {
|
||
if ($this->shouldLog()) {
|
||
$this->log("[{$abstract}] resolved (instance)");
|
||
}
|
||
return self::$instances[$abstract];
|
||
}
|
||
|
||
$this->with[] = $parameters;
|
||
|
||
$concrete = $this->getConcrete($abstract);
|
||
|
||
// 构造该类的实例,并递归解析所有依赖
|
||
if ($this->isBuildable($concrete, $abstract)) {
|
||
$object = $this->build($concrete);
|
||
} else {
|
||
$object = $this->make($concrete);
|
||
}
|
||
|
||
// 如果该类存在扩展器(装饰器),则逐个应用到实例
|
||
foreach ($this->getExtenders($abstract) as $extender) {
|
||
$object = $extender($object, $this);
|
||
}
|
||
|
||
// 如果该类被注册为单例,则需要将其存放在实例池中,方便后续取用同一实例
|
||
if (!$needs_contextual_build && $this->isShared($abstract)) {
|
||
$this->shared[$abstract] = $object;
|
||
if ($this->shouldLog()) {
|
||
$this->log("[{$abstract}] added to shared pool");
|
||
}
|
||
}
|
||
|
||
// 弹出本次构造的覆盖参数
|
||
array_pop($this->with);
|
||
|
||
if ($this->shouldLog()) {
|
||
$this->log(sprintf(
|
||
'[%s] resolved%s',
|
||
$abstract,
|
||
($needs_contextual_build ? ' with ' . implode(', ', $parameters) : '')
|
||
));
|
||
}
|
||
|
||
return $object;
|
||
}
|
||
|
||
/**
|
||
* 实例化具体的类实例
|
||
*
|
||
* @param Closure|string $concrete 类名或对应的闭包
|
||
* @throws EntryResolutionException
|
||
* @return mixed
|
||
*/
|
||
public function build($concrete)
|
||
{
|
||
// 如果传入的是闭包,则直接执行并返回
|
||
if ($concrete instanceof Closure) {
|
||
return $concrete($this, $this->getLastParameterOverride());
|
||
}
|
||
|
||
try {
|
||
$reflection = new ReflectionClass($concrete);
|
||
} catch (ReflectionException $e) {
|
||
throw new EntryResolutionException("指定的类 {$concrete} 不存在", 0, $e);
|
||
}
|
||
|
||
if (!$reflection->isInstantiable()) {
|
||
$this->notInstantiable($concrete);
|
||
}
|
||
|
||
$this->build_stack[] = $concrete;
|
||
|
||
$constructor = $reflection->getConstructor();
|
||
|
||
// 如果不存在构造函数,则代表不需要进一步解析,直接实例化即可
|
||
if (is_null($constructor)) {
|
||
array_pop($this->build_stack);
|
||
return new $concrete();
|
||
}
|
||
|
||
$dependencies = $constructor->getParameters();
|
||
|
||
// 获取所有依赖的实例
|
||
try {
|
||
$instances = $this->resolveDependencies($dependencies);
|
||
} catch (EntryResolutionException $e) {
|
||
array_pop($this->build_stack);
|
||
throw $e;
|
||
}
|
||
|
||
array_pop($this->build_stack);
|
||
|
||
return $reflection->newInstanceArgs($instances);
|
||
}
|
||
|
||
/**
|
||
* 调用对应的方法,并自动注入依赖
|
||
*
|
||
* @param callable|string $callback 对应的方法
|
||
* @param array $parameters 参数
|
||
* @param null|string $default_method 默认方法
|
||
* @return mixed
|
||
*/
|
||
public function call($callback, array $parameters = [], string $default_method = null)
|
||
{
|
||
if ($this->shouldLog()) {
|
||
if (count($parameters)) {
|
||
$str_parameters = array_map([ReflectionUtil::class, 'variableToString'], $parameters);
|
||
$str_parameters = implode(', ', $str_parameters);
|
||
} else {
|
||
$str_parameters = '';
|
||
}
|
||
$this->log(sprintf(
|
||
'Called %s%s(%s)',
|
||
ReflectionUtil::variableToString($callback),
|
||
($default_method ? '@' . $default_method : ''),
|
||
$str_parameters
|
||
));
|
||
}
|
||
return BoundMethod::call($this, $callback, $parameters, $default_method);
|
||
}
|
||
|
||
/**
|
||
* Finds an entry of the container by its identifier and returns it.
|
||
*
|
||
* @param string $id identifier of the entry to look for
|
||
*
|
||
* @throws NotFoundExceptionInterface no entry was found for **this** identifier
|
||
* @throws ContainerExceptionInterface error while retrieving the entry
|
||
*
|
||
* @return mixed entry
|
||
*/
|
||
public function get(string $id)
|
||
{
|
||
try {
|
||
return $this->make($id);
|
||
} catch (Exception $e) {
|
||
if ($this->has($id)) {
|
||
throw new EntryResolutionException('', 0, $e);
|
||
}
|
||
|
||
throw new EntryNotFoundException('', 0, $e);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Returns true if the container can return an entry for the given identifier.
|
||
* Returns false otherwise.
|
||
*
|
||
* `has($id)` returning true does not mean that `get($id)` will not throw an exception.
|
||
* It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
|
||
*
|
||
* @param string $id identifier of the entry to look for
|
||
*/
|
||
public function has(string $id): bool
|
||
{
|
||
return $this->bound($id);
|
||
}
|
||
|
||
/**
|
||
* 扩展一个类或接口
|
||
*
|
||
* @param string $abstract 类或接口名
|
||
* @param Closure $closure 扩展闭包
|
||
*/
|
||
public function extend(string $abstract, Closure $closure): void
|
||
{
|
||
$abstract = $this->getAlias($abstract);
|
||
|
||
// 如果该类已经被解析过,则直接将扩展器应用到该类的实例上
|
||
// 否则,将扩展器存入扩展器池,等待解析
|
||
if (isset(self::$instances[$abstract])) {
|
||
self::$instances[$abstract] = $closure(self::$instances[$abstract], $this);
|
||
} else {
|
||
self::$extenders[$abstract][] = $closure;
|
||
}
|
||
|
||
if ($this->shouldLog()) {
|
||
$this->log("[{$abstract}] extended");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取日志前缀
|
||
*/
|
||
public function getLogPrefix(): string
|
||
{
|
||
return ($this->log_prefix ?: '[WorkerContainer(U)]') . ' ';
|
||
}
|
||
|
||
/**
|
||
* 设置日志前缀
|
||
*/
|
||
public function setLogPrefix(string $prefix): void
|
||
{
|
||
$this->log_prefix = $prefix;
|
||
}
|
||
|
||
/**
|
||
* 获取对应类型的所有扩展器
|
||
*
|
||
* @param string $abstract 类或接口名
|
||
* @return Closure[]
|
||
*/
|
||
protected function getExtenders(string $abstract): array
|
||
{
|
||
$abstract = $this->getAlias($abstract);
|
||
|
||
return self::$extenders[$abstract] ?? [];
|
||
}
|
||
|
||
/**
|
||
* 判断传入的是否为别名
|
||
*/
|
||
protected function isAlias(string $name): bool
|
||
{
|
||
return array_key_exists($name, self::$aliases);
|
||
}
|
||
|
||
/**
|
||
* 抛弃所有过时的实例和别名
|
||
*
|
||
* @param string $abstract 类或接口名
|
||
*/
|
||
protected function dropStaleInstances(string $abstract): void
|
||
{
|
||
unset(
|
||
self::$instances[$abstract],
|
||
self::$aliases[$abstract],
|
||
$this->shared[$abstract]
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 获取一个解析对应类的闭包
|
||
*
|
||
* @param string $abstract 类或接口名
|
||
* @param string $concrete 实际类名
|
||
*/
|
||
protected function getClosure(string $abstract, string $concrete): Closure
|
||
{
|
||
return static function ($container, $parameters = []) use ($abstract, $concrete) {
|
||
$method = $abstract === $concrete ? 'build' : 'make';
|
||
|
||
return $container->{$method}($concrete, $parameters);
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 获取最后一次的覆盖参数
|
||
*/
|
||
protected function getLastParameterOverride(): array
|
||
{
|
||
return $this->with[count($this->with) - 1] ?? [];
|
||
}
|
||
|
||
/**
|
||
* 抛出实例化异常
|
||
*
|
||
* @throws EntryResolutionException
|
||
*/
|
||
protected function notInstantiable(string $concrete, string $reason = ''): void
|
||
{
|
||
if (!empty($this->build_stack)) {
|
||
$previous = implode(', ', $this->build_stack);
|
||
$message = "类 {$concrete} 无法实例化,其被 {$previous} 依赖";
|
||
} else {
|
||
$message = "类 {$concrete} 无法实例化";
|
||
}
|
||
|
||
throw new EntryResolutionException("{$message}:{$reason}");
|
||
}
|
||
|
||
/**
|
||
* 解析依赖
|
||
*
|
||
* @param ReflectionParameter[] $dependencies
|
||
* @throws EntryResolutionException
|
||
*/
|
||
protected function resolveDependencies(array $dependencies): array
|
||
{
|
||
$results = [];
|
||
|
||
foreach ($dependencies as $dependency) {
|
||
// 如果此依赖存在覆盖参数,则使用覆盖参数
|
||
// 否则,将尝试解析参数
|
||
if ($this->hasParameterOverride($dependency)) {
|
||
$results[] = $this->getParameterOverride($dependency);
|
||
continue;
|
||
}
|
||
|
||
// 如果存在临时注入的依赖,则使用临时注入的依赖
|
||
if ($this->hasParameterTypeOverride($dependency)) {
|
||
$results[] = $this->getParameterTypeOverride($dependency);
|
||
continue;
|
||
}
|
||
|
||
// 如果类名为空,则代表此依赖是基本类型,且无法对其进行依赖解析
|
||
$class_name = ReflectionUtil::getParameterClassName($dependency);
|
||
$results[] = is_null($class_name)
|
||
? $this->resolvePrimitive($dependency)
|
||
: $this->resolveClass($dependency);
|
||
|
||
if ($this->shouldLog()) {
|
||
if (is_null($class_name)) {
|
||
if ($dependency->hasType()) {
|
||
$class_name = $dependency->getType();
|
||
} else {
|
||
$class_name = 'Primitive';
|
||
}
|
||
}
|
||
$this->log("Dependency [{$class_name} {$dependency->name}] resolved");
|
||
}
|
||
}
|
||
|
||
return $results;
|
||
}
|
||
|
||
/**
|
||
* 判断传入的参数是否存在覆盖参数
|
||
*/
|
||
protected function hasParameterOverride(ReflectionParameter $parameter): bool
|
||
{
|
||
return array_key_exists($parameter->name, $this->getLastParameterOverride());
|
||
}
|
||
|
||
/**
|
||
* 获取覆盖参数
|
||
*
|
||
* @return mixed
|
||
*/
|
||
protected function getParameterOverride(ReflectionParameter $parameter)
|
||
{
|
||
return $this->getLastParameterOverride()[$parameter->name];
|
||
}
|
||
|
||
/**
|
||
* 判断传入的参数是否存在临时注入的参数
|
||
*/
|
||
protected function hasParameterTypeOverride(ReflectionParameter $parameter): bool
|
||
{
|
||
if (!$parameter->hasType()) {
|
||
return false;
|
||
}
|
||
|
||
$type = $parameter->getType();
|
||
|
||
if (!$type instanceof ReflectionNamedType || $type->isBuiltin()) {
|
||
return false;
|
||
}
|
||
|
||
return array_key_exists($type->getName(), $this->getLastParameterOverride());
|
||
}
|
||
|
||
/**
|
||
* 获取临时注入的参数
|
||
*
|
||
* @return mixed
|
||
*/
|
||
protected function getParameterTypeOverride(ReflectionParameter $parameter)
|
||
{
|
||
$type = $parameter->getType();
|
||
|
||
if (!$type instanceof ReflectionNamedType) {
|
||
return [];
|
||
}
|
||
|
||
return $this->getLastParameterOverride()[$type->getName()];
|
||
}
|
||
|
||
/**
|
||
* 解析基本类型
|
||
*
|
||
* @throws EntryResolutionException 如参数不存在默认值,则抛出异常
|
||
* @return mixed 对应类型的默认值
|
||
*/
|
||
protected function resolvePrimitive(ReflectionParameter $parameter)
|
||
{
|
||
if ($parameter->isDefaultValueAvailable()) {
|
||
return $parameter->getDefaultValue();
|
||
}
|
||
|
||
throw new EntryResolutionException("无法解析类 {$parameter->getDeclaringClass()->getName()} 的参数 {$parameter}");
|
||
}
|
||
|
||
/**
|
||
* 解析类
|
||
*
|
||
* @throws EntryResolutionException 如果无法解析类,则抛出异常
|
||
* @return mixed
|
||
*/
|
||
protected function resolveClass(ReflectionParameter $parameter)
|
||
{
|
||
try {
|
||
// 尝试解析
|
||
return $this->make(ReflectionUtil::getParameterClassName($parameter));
|
||
} 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
|
||
{
|
||
logger()->debug($this->getLogPrefix() . $message);
|
||
}
|
||
}
|