Files
kiri-router/src/Defer/DeferProxyGenerator.php
T
2026-06-28 20:20:21 +08:00

199 lines
5.4 KiB
PHP

<?php
declare(strict_types=1);
namespace Kiri\Router\Defer;
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;
/**
* @throws \ReflectionException
*/
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);
}
/**
* @throws \ReflectionException
*/
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]);
}
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());
}
$stmts = [
new Stmt\Expression(new Expr\Assign(new Expr\Variable('result'),
new Expr\StaticCall(new Name('parent'), $methodName, $args))),
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,
]);
}
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_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')),
};
}
private static function buildDefaultValueFromScalar(mixed $value): Node\Expr
{
return match (true) {
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')),
};
}
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';
}
}