diff --git a/src/PhpGenerator/Extractor.php b/src/PhpGenerator/Extractor.php index 97d2f732..69f43c2f 100644 --- a/src/PhpGenerator/Extractor.php +++ b/src/PhpGenerator/Extractor.php @@ -79,6 +79,36 @@ public function extractMethodBodies(string $className): array } + /** @return array> */ + public function extractPropertyHookBodies(string $className): array + { + if (!class_exists(Node\PropertyHook::class)) { + return []; + } + + $nodeFinder = new NodeFinder; + $classNode = $nodeFinder->findFirst( + $this->statements, + fn(Node $node) => $node instanceof Node\Stmt\ClassLike && $node->namespacedName->toString() === $className, + ); + + $res = []; + foreach ($nodeFinder->findInstanceOf($classNode, Node\Stmt\Property::class) as $propertyNode) { + foreach ($propertyNode->props as $propNode) { + $propName = $propNode->name->toString(); + foreach ($propertyNode->hooks as $hookNode) { + $body = $hookNode->body; + if ($body !== null) { + $contents = $this->getReformattedContents(is_array($body) ? $body : [$body], 3); + $res[$propName][$hookNode->name->toString()] = [$contents, !is_array($body)]; + } + } + } + } + return $res; + } + + public function extractFunctionBody(string $name): ?string { $functionNode = (new NodeFinder)->findFirst( @@ -94,6 +124,9 @@ public function extractFunctionBody(string $name): ?string /** @param Node[] $nodes */ private function getReformattedContents(array $nodes, int $level): string { + if (!$nodes) { + return ''; + } $body = $this->getNodeContents(...$nodes); $body = $this->performReplacements($body, $this->prepareReplacements($nodes, $level)); return Helpers::unindent($body, $level); diff --git a/src/PhpGenerator/Factory.php b/src/PhpGenerator/Factory.php index c8125e56..39e5ebab 100644 --- a/src/PhpGenerator/Factory.php +++ b/src/PhpGenerator/Factory.php @@ -81,7 +81,13 @@ public function fromClassReflection( && !$prop->isPromoted() && !$class->isEnum() ) { - $props[] = $this->fromPropertyReflection($prop); + $props[] = $p = $this->fromPropertyReflection($prop); + if ($withBodies) { + $hookBodies ??= $this->getExtractor($declaringClass->getFileName())->extractPropertyHookBodies($declaringClass->name); + foreach ($hookBodies[$prop->getName()] ?? [] as $hookType => [$body, $short]) { + $p->getHook($hookType)->setBody($body, short: $short); + } + } } } diff --git a/tests/PhpGenerator/ClassType.from.bodies.phpt b/tests/PhpGenerator/ClassType.from.bodies.phpt index 5d0dc9e6..35a99d7d 100644 --- a/tests/PhpGenerator/ClassType.from.bodies.phpt +++ b/tests/PhpGenerator/ClassType.from.bodies.phpt @@ -29,3 +29,13 @@ Assert::exception( $res = ClassType::from(Abc\Class7::class, withBodies: true); sameFile(__DIR__ . '/expected/ClassType.from.bodies.expect', (string) $res); + + +if (PHP_VERSION_ID >= 80400) { + require __DIR__ . '/fixtures/classes.84.php'; + $res = []; + $res[] = ClassType::from(Abc\PropertyHookSignatures::class, withBodies: true); + $res[] = ClassType::from(Abc\AbstractHookSignatures::class, withBodies: true); + $res[] = ClassType::from(Abc\PropertyHookSignaturesChild::class, withBodies: true); + sameFile(__DIR__ . '/expected/ClassType.from.bodies.84.expect', implode("\n", $res)); +} diff --git a/tests/PhpGenerator/Extractor.extractPropertyHookBodies.phpt b/tests/PhpGenerator/Extractor.extractPropertyHookBodies.phpt new file mode 100644 index 00000000..39e53fc9 --- /dev/null +++ b/tests/PhpGenerator/Extractor.extractPropertyHookBodies.phpt @@ -0,0 +1,50 @@ + 'x'; + } + + public string $full { + get { + if (true) { + return 'x'; + } else { + return 'y'; + } + } + } + + public string $empty { + set { } + } + + abstract public string $abstract { get; } + } + + XX); + +$bodies = $extractor->extractPropertyHookBodies('NS\Undefined'); +Assert::same([], $bodies); + +$bodies = $extractor->extractPropertyHookBodies('NS\Foo'); +Assert::same([ + 'short' => ['get' => ["'x'", true]], + 'full' => [ + 'get' => ["if (true) {\n return 'x';\n} else {\n return 'y';\n}", false], + ], + 'empty' => ['set' => ['', false]], +], $bodies); diff --git a/tests/PhpGenerator/expected/ClassType.from.bodies.84.expect b/tests/PhpGenerator/expected/ClassType.from.bodies.84.expect new file mode 100644 index 00000000..62891892 --- /dev/null +++ b/tests/PhpGenerator/expected/ClassType.from.bodies.84.expect @@ -0,0 +1,88 @@ +class PropertyHookSignatures +{ + public string $basic { + get => 'x'; + } + + public string $fullGet { + get { + return 'x'; + } + } + + protected string $refGet { + &get { + return 'x'; + } + } + + protected string $finalGet { + final get => 'x'; + } + + public string $basicSet { + set => 'x'; + } + + public string $fullSet { + set { + } + } + + public string $setWithParam { + set(string $foo) { + } + } + + public string $setWithParam2 { + set(string|int $value) => ''; + } + + public string $finalSet { + final set { + } + } + + public string $combined { + set { + } + get => 'x'; + } + + final public string $combinedFinal { + /** comment set */ + #[Set] + set { + } + /** comment get */ + #[Get] + get => 'x'; + } + + public string $virtualProp { + set { + } + &get => 'x'; + } +} + +abstract class AbstractHookSignatures +{ + abstract public string $abstractGet { get; } + abstract protected string $abstractSet { set; } + abstract public string $abstractBoth { set; get; } + + abstract public string $mixedGet { + set => 'x'; + get; + } + + abstract public string $mixedSet { + set; + get => 'x'; + } +} + +class PropertyHookSignaturesChild extends PropertyHookSignatures +{ +}