Skip to content

Commit a44dfe8

Browse files
xHeavendg
authored andcommitted
Factory::fromClassReflection() extracts property hook bodies (#172)
1 parent 1d90ffe commit a44dfe8

File tree

5 files changed

+188
-1
lines changed

5 files changed

+188
-1
lines changed

src/PhpGenerator/Extractor.php

+33
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,36 @@ public function extractMethodBodies(string $className): array
7979
}
8080

8181

82+
/** @return array<string, array<string, array{string, bool}>> */
83+
public function extractPropertyHookBodies(string $className): array
84+
{
85+
if (!class_exists(Node\PropertyHook::class)) {
86+
return [];
87+
}
88+
89+
$nodeFinder = new NodeFinder;
90+
$classNode = $nodeFinder->findFirst(
91+
$this->statements,
92+
fn(Node $node) => $node instanceof Node\Stmt\ClassLike && $node->namespacedName->toString() === $className,
93+
);
94+
95+
$res = [];
96+
foreach ($nodeFinder->findInstanceOf($classNode, Node\Stmt\Property::class) as $propertyNode) {
97+
foreach ($propertyNode->props as $propNode) {
98+
$propName = $propNode->name->toString();
99+
foreach ($propertyNode->hooks as $hookNode) {
100+
$body = $hookNode->body;
101+
if ($body !== null) {
102+
$contents = $this->getReformattedContents(is_array($body) ? $body : [$body], 3);
103+
$res[$propName][$hookNode->name->toString()] = [$contents, !is_array($body)];
104+
}
105+
}
106+
}
107+
}
108+
return $res;
109+
}
110+
111+
82112
public function extractFunctionBody(string $name): ?string
83113
{
84114
$functionNode = (new NodeFinder)->findFirst(
@@ -94,6 +124,9 @@ public function extractFunctionBody(string $name): ?string
94124
/** @param Node[] $nodes */
95125
private function getReformattedContents(array $nodes, int $level): string
96126
{
127+
if (!$nodes) {
128+
return '';
129+
}
97130
$body = $this->getNodeContents(...$nodes);
98131
$body = $this->performReplacements($body, $this->prepareReplacements($nodes, $level));
99132
return Helpers::unindent($body, $level);

src/PhpGenerator/Factory.php

+7-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,13 @@ public function fromClassReflection(
8181
&& !$prop->isPromoted()
8282
&& !$class->isEnum()
8383
) {
84-
$props[] = $this->fromPropertyReflection($prop);
84+
$props[] = $p = $this->fromPropertyReflection($prop);
85+
if ($withBodies) {
86+
$hookBodies ??= $this->getExtractor($declaringClass->getFileName())->extractPropertyHookBodies($declaringClass->name);
87+
foreach ($hookBodies[$prop->getName()] ?? [] as $hookType => [$body, $short]) {
88+
$p->getHook($hookType)->setBody($body, short: $short);
89+
}
90+
}
8591
}
8692
}
8793

tests/PhpGenerator/ClassType.from.bodies.phpt

+10
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,13 @@ Assert::exception(
2929

3030
$res = ClassType::from(Abc\Class7::class, withBodies: true);
3131
sameFile(__DIR__ . '/expected/ClassType.from.bodies.expect', (string) $res);
32+
33+
34+
if (PHP_VERSION_ID >= 80400) {
35+
require __DIR__ . '/fixtures/classes.84.php';
36+
$res = [];
37+
$res[] = ClassType::from(Abc\PropertyHookSignatures::class, withBodies: true);
38+
$res[] = ClassType::from(Abc\AbstractHookSignatures::class, withBodies: true);
39+
$res[] = ClassType::from(Abc\PropertyHookSignaturesChild::class, withBodies: true);
40+
sameFile(__DIR__ . '/expected/ClassType.from.bodies.84.expect', implode("\n", $res));
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Nette\PhpGenerator\Extractor;
6+
use Tester\Assert;
7+
8+
require __DIR__ . '/../bootstrap.php';
9+
10+
11+
$extractor = new Extractor(<<<'XX'
12+
<?php
13+
namespace NS;
14+
15+
abstract class Foo
16+
{
17+
public string $short {
18+
get => 'x';
19+
}
20+
21+
public string $full {
22+
get {
23+
if (true) {
24+
return 'x';
25+
} else {
26+
return 'y';
27+
}
28+
}
29+
}
30+
31+
public string $empty {
32+
set { }
33+
}
34+
35+
abstract public string $abstract { get; }
36+
}
37+
38+
XX);
39+
40+
$bodies = $extractor->extractPropertyHookBodies('NS\Undefined');
41+
Assert::same([], $bodies);
42+
43+
$bodies = $extractor->extractPropertyHookBodies('NS\Foo');
44+
Assert::same([
45+
'short' => ['get' => ["'x'", true]],
46+
'full' => [
47+
'get' => ["if (true) {\n return 'x';\n} else {\n return 'y';\n}", false],
48+
],
49+
'empty' => ['set' => ['', false]],
50+
], $bodies);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
class PropertyHookSignatures
2+
{
3+
public string $basic {
4+
get => 'x';
5+
}
6+
7+
public string $fullGet {
8+
get {
9+
return 'x';
10+
}
11+
}
12+
13+
protected string $refGet {
14+
&get {
15+
return 'x';
16+
}
17+
}
18+
19+
protected string $finalGet {
20+
final get => 'x';
21+
}
22+
23+
public string $basicSet {
24+
set => 'x';
25+
}
26+
27+
public string $fullSet {
28+
set {
29+
}
30+
}
31+
32+
public string $setWithParam {
33+
set(string $foo) {
34+
}
35+
}
36+
37+
public string $setWithParam2 {
38+
set(string|int $value) => '';
39+
}
40+
41+
public string $finalSet {
42+
final set {
43+
}
44+
}
45+
46+
public string $combined {
47+
set {
48+
}
49+
get => 'x';
50+
}
51+
52+
final public string $combinedFinal {
53+
/** comment set */
54+
#[Set]
55+
set {
56+
}
57+
/** comment get */
58+
#[Get]
59+
get => 'x';
60+
}
61+
62+
public string $virtualProp {
63+
set {
64+
}
65+
&get => 'x';
66+
}
67+
}
68+
69+
abstract class AbstractHookSignatures
70+
{
71+
abstract public string $abstractGet { get; }
72+
abstract protected string $abstractSet { set; }
73+
abstract public string $abstractBoth { set; get; }
74+
75+
abstract public string $mixedGet {
76+
set => 'x';
77+
get;
78+
}
79+
80+
abstract public string $mixedSet {
81+
set;
82+
get => 'x';
83+
}
84+
}
85+
86+
class PropertyHookSignaturesChild extends PropertyHookSignatures
87+
{
88+
}

0 commit comments

Comments
 (0)