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'; } }