eee
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
<?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';
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user