Skip to content

Commit

Permalink
Factory::fromClassReflection() extracts property hook bodies (#172)
Browse files Browse the repository at this point in the history
  • Loading branch information
xHeaven authored and dg committed Feb 17, 2025
1 parent 1d90ffe commit a44dfe8
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 1 deletion.
33 changes: 33 additions & 0 deletions src/PhpGenerator/Extractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,36 @@ public function extractMethodBodies(string $className): array
}


/** @return array<string, array<string, array{string, bool}>> */
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(
Expand All @@ -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);
Expand Down
8 changes: 7 additions & 1 deletion src/PhpGenerator/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
}

Expand Down
10 changes: 10 additions & 0 deletions tests/PhpGenerator/ClassType.from.bodies.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
50 changes: 50 additions & 0 deletions tests/PhpGenerator/Extractor.extractPropertyHookBodies.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

use Nette\PhpGenerator\Extractor;
use Tester\Assert;

require __DIR__ . '/../bootstrap.php';


$extractor = new Extractor(<<<'XX'
<?php
namespace NS;
abstract class Foo
{
public string $short {
get => '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);
88 changes: 88 additions & 0 deletions tests/PhpGenerator/expected/ClassType.from.bodies.84.expect
Original file line number Diff line number Diff line change
@@ -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
{
}

0 comments on commit a44dfe8

Please sign in to comment.