zhamao-framework/src/ZM/Container/ContainerTrait.php
2022-06-08 23:14:25 +08:00

737 lines
20 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

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

<?php
declare(strict_types=1);
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);
}
}