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 961e611

Browse files
committed
Factory & Extractor: added support for property hooks & asymmetric visibility
1 parent 643ec81 commit 961e611

File tree

9 files changed

+629
-13
lines changed

9 files changed

+629
-13
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) => ClassType::VisibilityPublic,
533+
(bool) ($flags & Modifiers::PROTECTED_SET) => ClassType::VisibilityProtected,
534+
(bool) ($flags & Modifiers::PRIVATE_SET) => ClassType::VisibilityPrivate,
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->importHooks($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->importHooks($from, $prop);
271+
$isInterface = $from->getDeclaringClass()->isInterface();
272+
$prop->setFinal($from->isFinal() && !$prop->isPrivate('set'));
273+
$prop->setAbstract($from->isAbstract() && !$isInterface);
274+
}
268275
return $prop;
269276
}
270277

271278

279+
private function importHooks(\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+
? Modifier::Private
288+
: ($from->isProtectedSet() ? Modifier::Protected : $getV);
289+
$defaultSetV = $from->isReadOnly() && $getV !== Modifier::Private
290+
? Modifier::Protected
291+
: $getV;
292+
if ($setV !== $defaultSetV) {
293+
$prop->setVisibility($getV === Modifier::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 */)');

‎src/PhpGenerator/Modifier.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,7 @@ final class Modifier
2222
public const Public = 'public';
2323
public const Protected = 'protected';
2424
public const Private = 'private';
25+
26+
public const Set = 'set';
27+
public const Get = 'get';
2528
}
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

‎tests/PhpGenerator/PropertyLike.asymmetric-visiblity.phpt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
declare(strict_types=1);
88

99
use Nette\PhpGenerator\ClassType;
10+
use Nette\PhpGenerator\Modifier;
1011
use Tester\Assert;
1112

1213
require __DIR__ . '/../bootstrap.php';
@@ -17,8 +18,8 @@ $class = new ClassType('Demo');
1718
// Default visibility
1819
$default = $class->addProperty('first')
1920
->setType('string');
20-
Assert::true($default->isPublic('get'));
21-
Assert::true($default->isPublic('set'));
21+
Assert::true($default->isPublic(Modifier::Get));
22+
Assert::true($default->isPublic(Modifier::Set));
2223
Assert::null($default->getVisibility());
2324
Assert::null($default->getVisibility('set'));
2425

0 commit comments

Comments
(0)

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