eee
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kiri\Router\Annotate;
|
||||
|
||||
use Kiri\Di\Interface\InjectMethodInterface;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
|
||||
class Defer implements InjectMethodInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string|array $callback Class name or [Class::class, 'method']
|
||||
* @param array $params
|
||||
*/
|
||||
public function __construct(readonly public string|array $callback, readonly public array $params = [])
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $method
|
||||
* @return void
|
||||
*/
|
||||
public function dispatch(string $class, string $method): void
|
||||
{
|
||||
DeferRegistry::add($class, $method, $this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kiri\Router\Annotate;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\PrettyPrinter\Standard;
|
||||
use ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
|
||||
class DeferProxyGenerator
|
||||
{
|
||||
|
||||
private static ?string $cacheDir = null;
|
||||
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
* @param array $construct
|
||||
* @return object
|
||||
*/
|
||||
public static function create(string $className, array $construct): object
|
||||
{
|
||||
$proxyClass = $className . '__DeferProxy';
|
||||
$cacheFile = self::getCacheFile($className);
|
||||
|
||||
if (!class_exists($proxyClass, false)) {
|
||||
if ($cacheFile !== null && file_exists($cacheFile)) {
|
||||
require_once $cacheFile;
|
||||
}
|
||||
|
||||
if (!class_exists($proxyClass, false)) {
|
||||
$code = self::generate($className);
|
||||
if ($cacheFile !== null) {
|
||||
$dir = dirname($cacheFile);
|
||||
if (!is_dir($dir)) {
|
||||
mkdir($dir, 0755, true);
|
||||
}
|
||||
file_put_contents($cacheFile, '<?php ' . $code);
|
||||
require_once $cacheFile;
|
||||
} else {
|
||||
eval($code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (class_exists($proxyClass, false)) {
|
||||
$reflect = new ReflectionClass($proxyClass);
|
||||
return $reflect->newInstanceArgs($construct);
|
||||
}
|
||||
|
||||
$reflect = new ReflectionClass($className);
|
||||
return $reflect->newInstanceArgs($construct);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
* @return string
|
||||
*/
|
||||
private static function generate(string $className): string
|
||||
{
|
||||
$reflect = new ReflectionClass($className);
|
||||
$methods = DeferRegistry::getAll($className);
|
||||
$stmts = [];
|
||||
|
||||
foreach ($methods as $methodName => $defers) {
|
||||
if (!$reflect->hasMethod($methodName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$method = $reflect->getMethod($methodName);
|
||||
if ($method->isPrivate() || $method->isStatic() || $method->isFinal() || $method->isConstructor() || $method->isDestructor()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$stmts[] = self::buildMethod($method);
|
||||
}
|
||||
|
||||
if (empty($stmts)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$classNode = new Stmt\Class_(
|
||||
new Name($className . '__DeferProxy'),
|
||||
[
|
||||
'extends' => new Name\FullyQualified($className),
|
||||
'stmts' => $stmts,
|
||||
]
|
||||
);
|
||||
|
||||
$namespace = $reflect->getNamespaceName();
|
||||
|
||||
$namespaceNode = new Stmt\Namespace_(
|
||||
$namespace !== '' ? new Name($namespace) : null,
|
||||
[$classNode]
|
||||
);
|
||||
|
||||
$printer = new Standard();
|
||||
return $printer->prettyPrintFile([$namespaceNode]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param ReflectionMethod $method
|
||||
* @return Stmt\ClassMethod
|
||||
*/
|
||||
private static function buildMethod(ReflectionMethod $method): Stmt\ClassMethod
|
||||
{
|
||||
$methodName = $method->getName();
|
||||
$params = [];
|
||||
$args = [];
|
||||
|
||||
foreach ($method->getParameters() as $param) {
|
||||
$type = null;
|
||||
$refType = $param->getType();
|
||||
if ($refType instanceof \ReflectionNamedType) {
|
||||
$type = new Name($refType->getName());
|
||||
}
|
||||
|
||||
$default = null;
|
||||
if ($param->isDefaultValueAvailable()) {
|
||||
$default = self::buildDefaultValue($param);
|
||||
}
|
||||
|
||||
$var = new Expr\Variable($param->getName());
|
||||
|
||||
$params[] = new Node\Param(
|
||||
$var,
|
||||
$default,
|
||||
$type,
|
||||
byRef: $param->isPassedByReference(),
|
||||
variadic: $param->isVariadic()
|
||||
);
|
||||
|
||||
$args[] = new Node\Arg(
|
||||
$var,
|
||||
byRef: $param->isPassedByReference(),
|
||||
unpack: $param->isVariadic()
|
||||
);
|
||||
}
|
||||
|
||||
$returnType = null;
|
||||
$refReturnType = $method->getReturnType();
|
||||
if ($refReturnType instanceof \ReflectionNamedType) {
|
||||
$returnType = new Name($refReturnType->getName());
|
||||
}
|
||||
|
||||
$parentCall = new Expr\StaticCall(
|
||||
new Name('parent'),
|
||||
$methodName,
|
||||
$args
|
||||
);
|
||||
|
||||
$stmts = [
|
||||
new Stmt\Expression(new Expr\Assign(new Expr\Variable('result'), $parentCall)),
|
||||
new Stmt\Expression(
|
||||
new Expr\StaticCall(
|
||||
new Name\FullyQualified(DeferRegistry::class),
|
||||
'execute',
|
||||
[
|
||||
new Node\Arg(new Expr\ClassConstFetch(new Name\FullyQualified($method->getDeclaringClass()->getName()), 'class')),
|
||||
new Node\Arg(new Node\Scalar\String_($methodName)),
|
||||
]
|
||||
)
|
||||
),
|
||||
new Stmt\Return_(new Expr\Variable('result')),
|
||||
];
|
||||
|
||||
return new Stmt\ClassMethod(
|
||||
$methodName,
|
||||
[
|
||||
'flags' => $method->isPublic() ? Stmt\Class_::MODIFIER_PUBLIC : Stmt\Class_::MODIFIER_PROTECTED,
|
||||
'params' => $params,
|
||||
'returnType' => $returnType,
|
||||
'stmts' => $stmts,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \ReflectionParameter $param
|
||||
* @return Node\Expr
|
||||
*/
|
||||
private static function buildDefaultValue(\ReflectionParameter $param): Node\Expr
|
||||
{
|
||||
if (!$param->isDefaultValueAvailable()) {
|
||||
return new Expr\ConstFetch(new Name('null'));
|
||||
}
|
||||
|
||||
$value = $param->getDefaultValue();
|
||||
|
||||
return match (true) {
|
||||
is_null($value) => new Expr\ConstFetch(new Name('null')),
|
||||
is_bool($value) => new Expr\ConstFetch(new Name($value ? 'true' : 'false')),
|
||||
is_int($value) => new Node\Scalar\LNumber($value),
|
||||
is_float($value) => new Node\Scalar\DNumber($value),
|
||||
is_string($value)=> new Node\Scalar\String_($value),
|
||||
is_array($value) => new Expr\Array_(
|
||||
array_map(fn($k, $v) => new Expr\ArrayItem(
|
||||
self::buildDefaultValueFromScalar($v),
|
||||
is_string($k) ? new Node\Scalar\String_($k) : null
|
||||
), array_keys($value), $value)
|
||||
),
|
||||
default => new Expr\ConstFetch(new Name('null')),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return Node\Expr
|
||||
*/
|
||||
private static function buildDefaultValueFromScalar(mixed $value): Node\Expr
|
||||
{
|
||||
return match (true) {
|
||||
is_null($value) => new Expr\ConstFetch(new Name('null')),
|
||||
is_bool($value) => new Expr\ConstFetch(new Name($value ? 'true' : 'false')),
|
||||
is_int($value) => new Node\Scalar\LNumber($value),
|
||||
is_float($value) => new Node\Scalar\DNumber($value),
|
||||
is_string($value)=> new Node\Scalar\String_($value),
|
||||
default => new Expr\ConstFetch(new Name('null')),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
* @return string|null
|
||||
*/
|
||||
private static function getCacheFile(string $className): ?string
|
||||
{
|
||||
if (self::$cacheDir === null) {
|
||||
if (defined('APP_PATH')) {
|
||||
self::$cacheDir = APP_PATH . 'runtime/proxies/';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return self::$cacheDir . str_replace('\\', '_', $className) . '__DeferProxy.php';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kiri\Router\Annotate;
|
||||
|
||||
class DeferRegistry
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array<string, Defer[]> "ClassName::method" => Defer[]
|
||||
*/
|
||||
private static array $registry = [];
|
||||
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $method
|
||||
* @param Defer $defer
|
||||
* @return void
|
||||
*/
|
||||
public static function add(string $class, string $method, Defer $defer): void
|
||||
{
|
||||
$key = self::key($class, $method);
|
||||
self::$registry[$key][] = $defer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $method
|
||||
* @return Defer[]
|
||||
*/
|
||||
public static function get(string $class, string $method): array
|
||||
{
|
||||
return self::$registry[self::key($class, $method)] ?? [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasAny(string $class): bool
|
||||
{
|
||||
$prefix = $class . '::';
|
||||
foreach (array_keys(self::$registry) as $key) {
|
||||
if (str_starts_with($key, $prefix)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @return array<string, Defer[]> method => Defer[]
|
||||
*/
|
||||
public static function getAll(string $class): array
|
||||
{
|
||||
$result = [];
|
||||
$prefix = $class . '::';
|
||||
foreach (self::$registry as $key => $defers) {
|
||||
if (str_starts_with($key, $prefix)) {
|
||||
$method = substr($key, strlen($prefix));
|
||||
$result[$method] = $defers;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $method
|
||||
* @return void
|
||||
*/
|
||||
public static function execute(string $class, string $method): void
|
||||
{
|
||||
$key = self::key($class, $method);
|
||||
if (!isset(self::$registry[$key])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$defers = self::$registry[$key];
|
||||
unset(self::$registry[$key]);
|
||||
|
||||
foreach ($defers as $defer) {
|
||||
try {
|
||||
$callback = $defer->callback;
|
||||
$params = $defer->params;
|
||||
|
||||
if (is_array($callback)) {
|
||||
[$cbClass, $cbMethod] = $callback;
|
||||
$instance = \Kiri::getDi()->get($cbClass);
|
||||
call_user_func([$instance, $cbMethod], ...$params);
|
||||
} else {
|
||||
$instance = \Kiri::getDi()->get($callback);
|
||||
call_user_func([$instance, '__invoke'], ...$params);
|
||||
}
|
||||
} catch (\Throwable $throwable) {
|
||||
\Kiri::getLogger()->error('Defer callback failed: ' . $throwable->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public static function clear(): void
|
||||
{
|
||||
self::$registry = [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $method
|
||||
* @return string
|
||||
*/
|
||||
private static function key(string $class, string $method): string
|
||||
{
|
||||
return $class . '::' . $method;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user