Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit d201c9b

Browse files
committed
Factory & Extractor: added support for property hooks & asymmetric visibility
1 parent 9fa3236 commit d201c9b

File tree

7 files changed

+623
-11
lines changed

7 files changed

+623
-11
lines changed

‎src/PhpGenerator/Extractor.php

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
use Nette;
1313
use PhpParser;
14+
use PhpParser\Modifiers;
1415
use PhpParser\Node;
1516
use PhpParser\NodeFinder;
1617
use PhpParser\ParserFactory;
@@ -323,14 +324,37 @@ private function addPropertyToClass(ClassLike $class, Node\Stmt\Property $node):
323324
foreach ($node->props as $item) {
324325
$prop = $class->addProperty($item->name->toString());
325326
$prop->setStatic($node->isStatic());
326-
$prop->setVisibility($this->toVisibility($node->flags));
327+
$prop->setVisibility($this->toVisibility($node->flags), $this->toSetterVisibility($node->flags));
327328
$prop->setType($node->type ? $this->toPhp($node->type) : null);
328329
if ($item->default) {
329330
$prop->setValue($this->toValue($item->default));
330331
}
331332

332333
$prop->setReadOnly((method_exists($node, 'isReadonly') && $node->isReadonly()) || ($class instanceof ClassType && $class->isReadOnly()));
333334
$this->addCommentAndAttributes($prop, $node);
335+
336+
$prop->setAbstract((bool) ($node->flags & Node\Stmt\Class_::MODIFIER_ABSTRACT));
337+
$prop->setFinal((bool) ($node->flags & Node\Stmt\Class_::MODIFIER_FINAL));
338+
$this->addHooksToProperty($prop, $node);
339+
}
340+
}
341+
342+
343+
private function addHooksToProperty(Property|PromotedParameter $prop, Node\Stmt\Property|Node\Param $node): void
344+
{
345+
if (!class_exists(Node\PropertyHook::class)) {
346+
return;
347+
}
348+
349+
foreach ($node->hooks as $hookNode) {
350+
$hook = $prop->addHook($hookNode->name->toString());
351+
$hook->setFinal((bool) ($hookNode->flags & Modifiers::FINAL));
352+
$this->setupFunction($hook, $hookNode);
353+
if ($hookNode->body === null) {
354+
$hook->setAbstract();
355+
} elseif (!is_array($hookNode->body)) {
356+
$hook->setBody($this->getReformattedContents([$hookNode->body], 1), short: true);
357+
}
334358
}
335359
}
336360

@@ -380,7 +404,7 @@ private function addFunctionToFile(PhpFile $phpFile, Node\Stmt\Function_ $node):
380404

381405

382406
private function addCommentAndAttributes(
383-
PhpFile|ClassLike|Constant|Property|GlobalFunction|Method|Parameter|EnumCase|TraitUse $element,
407+
PhpFile|ClassLike|Constant|Property|GlobalFunction|Method|Parameter|EnumCase|TraitUse|PropertyHook $element,
384408
Node $node,
385409
): void
386410
{
@@ -408,19 +432,29 @@ private function addCommentAndAttributes(
408432
}
409433

410434

411-
private function setupFunction(GlobalFunction|Method $function, Node\FunctionLike $node): void
435+
private function setupFunction(GlobalFunction|Method|PropertyHook $function, Node\FunctionLike $node): void
412436
{
413437
$function->setReturnReference($node->returnsByRef());
414-
$function->setReturnType($node->getReturnType() ? $this->toPhp($node->getReturnType()) : null);
438+
if (!$function instanceof PropertyHook) {
439+
$function->setReturnType($node->getReturnType() ? $this->toPhp($node->getReturnType()) : null);
440+
}
441+
415442
foreach ($node->getParams() as $item) {
416-
$visibility = $this->toVisibility($item->flags);
417-
$isReadonly = (bool) ($item->flags & Node\Stmt\Class_::MODIFIER_READONLY);
418-
$param = $visibility
419-
? ($function->addPromotedParameter($item->var->name))->setVisibility($visibility)->setReadonly($isReadonly)
420-
: $function->addParameter($item->var->name);
443+
$getVisibility = $this->toVisibility($item->flags);
444+
$setVisibility = $this->toSetterVisibility($item->flags);
445+
if ($getVisibility || $setVisibility) {
446+
$param = $function->addPromotedParameter($item->var->name)
447+
->setVisibility($getVisibility, $setVisibility)
448+
->setReadonly((bool) ($item->flags & Node\Stmt\Class_::MODIFIER_READONLY));
449+
$this->addHooksToProperty($param, $item);
450+
} else {
451+
$param = $function->addParameter($item->var->name);
452+
}
421453
$param->setType($item->type ? $this->toPhp($item->type) : null);
422454
$param->setReference($item->byRef);
423-
$function->setVariadic($item->variadic);
455+
if (!$function instanceof PropertyHook) {
456+
$function->setVariadic($item->variadic);
457+
}
424458
if ($item->default) {
425459
$param->setDefaultValue($this->toValue($item->default));
426460
}
@@ -491,6 +525,18 @@ private function toVisibility(int $flags): ?string
491525
}
492526

493527

528+
private function toSetterVisibility(int $flags): ?string
529+
{
530+
return match (true) {
531+
!class_exists(Node\PropertyHook::class) => null,
532+
(bool) ($flags & Modifiers::PUBLIC_SET) => Visibility::Public,
533+
(bool) ($flags & Modifiers::PROTECTED_SET) => Visibility::Protected,
534+
(bool) ($flags & Modifiers::PRIVATE_SET) => Visibility::Private,
535+
default => null,
536+
};
537+
}
538+
539+
494540
private function toPhp(Node $value): string
495541
{
496542
$dolly = clone $value;

‎src/PhpGenerator/Factory.php

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ public function fromParameterReflection(\ReflectionParameter $from): Parameter
204204
$param = (new PromotedParameter($from->name))
205205
->setVisibility($this->getVisibility($property))
206206
->setReadOnly(PHP_VERSION_ID >= 80100 && $property->isReadonly());
207+
$this->addHooks($property, $param);
207208
} else {
208209
$param = new Parameter($from->name);
209210
}
@@ -260,15 +261,58 @@ public function fromPropertyReflection(\ReflectionProperty $from): Property
260261
$prop->setStatic($from->isStatic());
261262
$prop->setVisibility($this->getVisibility($from));
262263
$prop->setType((string) $from->getType());
263-
264264
$prop->setInitialized($from->hasType() && array_key_exists($prop->getName(), $defaults));
265265
$prop->setReadOnly(PHP_VERSION_ID >= 80100 && $from->isReadOnly());
266266
$prop->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
267267
$prop->setAttributes($this->getAttributes($from));
268+
269+
if (PHP_VERSION_ID >= 80400) {
270+
$this->addHooks($from, $prop);
271+
$isInterface = $from->getDeclaringClass()->isInterface();
272+
$prop->setFinal($from->isFinal() && !$prop->isPrivate(PropertyAccessMode::Set));
273+
$prop->setAbstract($from->isAbstract() && !$isInterface);
274+
}
268275
return $prop;
269276
}
270277

271278

279+
private function addHooks(\ReflectionProperty $from, Property|PromotedParameter $prop): void
280+
{
281+
if (PHP_VERSION_ID < 80400) {
282+
return;
283+
}
284+
285+
$getV = $this->getVisibility($from);
286+
$setV = $from->isPrivateSet()
287+
? Visibility::Private
288+
: ($from->isProtectedSet() ? Visibility::Protected : $getV);
289+
$defaultSetV = $from->isReadOnly() && $getV !== Visibility::Private
290+
? Visibility::Protected
291+
: $getV;
292+
if ($setV !== $defaultSetV) {
293+
$prop->setVisibility($getV === Visibility::Public ? null : $getV, $setV);
294+
}
295+
296+
foreach ($from->getHooks() as $type => $hook) {
297+
$params = $hook->getParameters();
298+
if (
299+
count($params) === 1
300+
&& $params[0]->getName() === 'value'
301+
&& $params[0]->getType() == $from->getType() // intentionally ==
302+
) {
303+
$params = [];
304+
}
305+
$prop->addHook($type)
306+
->setParameters(array_map([$this, 'fromParameterReflection'], $params))
307+
->setAbstract($hook->isAbstract())
308+
->setFinal($hook->isFinal())
309+
->setReturnReference($hook->returnsReference())
310+
->setComment(Helpers::unformatDocComment((string) $hook->getDocComment()))
311+
->setAttributes($this->getAttributes($hook));
312+
}
313+
}
314+
315+
272316
public function fromObject(object $obj): Literal
273317
{
274318
return new Literal('new \\' . $obj::class . '(/* unknown */)');
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/**
4+
* @phpVersion 8.4
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
use Nette\PhpGenerator\ClassType;
10+
use Nette\PhpGenerator\InterfaceType;
11+
12+
require __DIR__ . '/../bootstrap.php';
13+
require __DIR__ . '/fixtures/classes.84.php';
14+
15+
$res[] = ClassType::from(Abc\PropertyHookSignatures::class);
16+
$res[] = ClassType::from(Abc\AbstractHookSignatures::class);
17+
$res[] = InterfaceType::from(Abc\InterfaceHookSignatures::class);
18+
$res[] = ClassType::from(Abc\AsymmetricVisibilitySignatures::class);
19+
$res[] = ClassType::from(Abc\CombinedSignatures::class);
20+
$res[] = ClassType::from(Abc\ConstructorAllSignatures::class);
21+
22+
sameFile(__DIR__ . '/expected/ClassType.from.84.expect', implode("\n", $res));

‎tests/PhpGenerator/Extractor.extractAll.phpt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ sameFile(__DIR__ . '/expected/Extractor.classes.81.expect', (string) $file);
1717
$file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/classes.82.php')))->extractAll();
1818
sameFile(__DIR__ . '/expected/Extractor.classes.82.expect', (string) $file);
1919

20+
if (class_exists(PhpParser\Node\PropertyHook::class)) {
21+
$file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/classes.84.php')))->extractAll();
22+
sameFile(__DIR__ . '/expected/Extractor.classes.84.expect', (string) $file);
23+
}
24+
2025
$file = (new Extractor(file_get_contents(__DIR__ . '/fixtures/enum.php')))->extractAll();
2126
sameFile(__DIR__ . '/expected/Extractor.enum.expect', (string) $file);
2227

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
class PropertyHookSignatures
2+
{
3+
public string $basic {
4+
get {
5+
}
6+
}
7+
8+
public string $fullGet {
9+
get {
10+
}
11+
}
12+
13+
protected string $refGet {
14+
&get {
15+
}
16+
}
17+
18+
protected string $finalGet {
19+
final get {
20+
}
21+
}
22+
23+
public string $basicSet {
24+
set {
25+
}
26+
}
27+
28+
public string $fullSet {
29+
set {
30+
}
31+
}
32+
33+
public string $setWithParam {
34+
set(string $foo) {
35+
}
36+
}
37+
38+
public string $setWithParam2 {
39+
set(string|int $value) {
40+
}
41+
}
42+
43+
public string $finalSet {
44+
final set {
45+
}
46+
}
47+
48+
public string $combined {
49+
set {
50+
}
51+
get {
52+
}
53+
}
54+
55+
final public string $combinedFinal {
56+
/** comment set */
57+
#[Set]
58+
set {
59+
}
60+
/** comment get */
61+
#[Get]
62+
get {
63+
}
64+
}
65+
66+
public string $virtualProp {
67+
set {
68+
}
69+
&get {
70+
}
71+
}
72+
}
73+
74+
abstract class AbstractHookSignatures
75+
{
76+
abstract public string $abstractGet { get; }
77+
abstract protected string $abstractSet { set; }
78+
abstract public string $abstractBoth { set; get; }
79+
80+
abstract public string $mixedGet {
81+
set {
82+
}
83+
get;
84+
}
85+
86+
abstract public string $mixedSet {
87+
set;
88+
get {
89+
}
90+
}
91+
}
92+
93+
interface InterfaceHookSignatures
94+
{
95+
public string $get { get; }
96+
97+
public string $set { #[Set]
98+
set; }
99+
public string $both { set; get; }
100+
public string $refGet { &get; }
101+
}
102+
103+
class AsymmetricVisibilitySignatures
104+
{
105+
private(set) string $first;
106+
protected(set) string $second;
107+
protected private(set) string $third;
108+
private(set) string $fourth;
109+
protected(set) string $fifth;
110+
public readonly string $implicit;
111+
private(set) readonly string $readFirst;
112+
private(set) readonly string $readSecond;
113+
protected readonly string $readThird;
114+
public(set) readonly string $readFourth;
115+
private(set) string $firstFinal;
116+
final protected(set) string $secondFinal;
117+
protected private(set) string $thirdFinal;
118+
private(set) string $fourthFinal;
119+
final protected(set) string $fifthFinal;
120+
}
121+
122+
class CombinedSignatures
123+
{
124+
protected(set) string $prop2 {
125+
final set {
126+
}
127+
get {
128+
}
129+
}
130+
131+
protected private(set) string $prop3 {
132+
set {
133+
}
134+
final get {
135+
}
136+
}
137+
}
138+
139+
class ConstructorAllSignatures
140+
{
141+
public function __construct(
142+
private(set) string $prop1,
143+
protected(set) string $prop2,
144+
protected private(set) string $prop3,
145+
private(set) string $prop4,
146+
protected(set) string $prop5,
147+
private(set) readonly string $readProp1,
148+
private(set) readonly string $readProp2,
149+
protected readonly string $readProp3,
150+
public(set) readonly string $readProp4,
151+
public string $hookProp1 {
152+
get {
153+
}
154+
},
155+
protected(set) string $mixedProp1 {
156+
set {
157+
}
158+
get {
159+
}
160+
},
161+
) {
162+
}
163+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /