Skip to content

Commit 4acefba

Browse files
shanginndg
authored andcommitted
Add support for properties hooks [Closes #171]
1 parent 1e85ee7 commit 4acefba

File tree

4 files changed

+244
-8
lines changed

4 files changed

+244
-8
lines changed

src/PhpGenerator/Printer.php

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ public function printMethod(Method $method, ?PhpNamespace $namespace = null, boo
129129
}
130130

131131

132-
protected function printFunctionBody(Closure|GlobalFunction|Method $function): string
132+
protected function printFunctionBody(Closure|GlobalFunction|Method|PropertyHook $function): string
133133
{
134134
return ltrim(rtrim(Strings::normalize(
135135
Helpers::simplifyTaggedNames($function->getBody(), $this->namespace),
@@ -313,7 +313,7 @@ protected function printUses(PhpNamespace $namespace, string $of = PhpNamespace:
313313
}
314314

315315

316-
protected function printParameters(Closure|GlobalFunction|Method $function, int $column = 0): string
316+
protected function printParameters(Closure|GlobalFunction|Method|PropertyHook $function, int $column = 0): string
317317
{
318318
$special = false;
319319
foreach ($function->getParameters() as $param) {
@@ -332,13 +332,13 @@ protected function printParameters(Closure|GlobalFunction|Method $function, int
332332
}
333333

334334

335-
private function formatParameters(Closure|GlobalFunction|Method $function, bool $multiline): string
335+
private function formatParameters(Closure|GlobalFunction|Method|PropertyHook $function, bool $multiline): string
336336
{
337337
$params = $function->getParameters();
338338
$res = '';
339339

340340
foreach ($params as $param) {
341-
$variadic = $function->isVariadic() && $param === end($params);
341+
$variadic = !$function instanceof PropertyHook && $function->isVariadic() && $param === end($params);
342342
$attrs = $this->printAttributes($param->getAttributes(), inline: true);
343343
$res .=
344344
$this->printDocComment($param)
@@ -386,13 +386,20 @@ private function printProperty(Property $property, bool $readOnlyClass = false):
386386
. ltrim($this->printType($type, $property->isNullable()) . ' ')
387387
. '$' . $property->getName());
388388

389+
$hooks = !$property->getSetHook() && !$property->getGetHook()
390+
? ';'
391+
: " {\n" . $this->printHooks($property) . '}';
392+
393+
$defaultValue = $property->getValue() === null && !$property->isInitialized()
394+
? ''
395+
: ' = ' . $this->dump($property->getValue(), strlen($def) + 3); // 3 = ' = '
396+
389397
return $this->printDocComment($property)
390398
. $this->printAttributes($property->getAttributes())
391399
. $def
392-
. ($property->getValue() === null && !$property->isInitialized()
393-
? ''
394-
: ' = ' . $this->dump($property->getValue(), strlen($def) + 3)) // 3 = ' = '
395-
. ";\n";
400+
. $defaultValue
401+
. $hooks
402+
. "\n";
396403
}
397404

398405

@@ -489,4 +496,36 @@ protected function isBraceOnNextLine(bool $multiLine, bool $hasReturnType): bool
489496
{
490497
return $this->bracesOnNextLine && (!$multiLine || $hasReturnType);
491498
}
499+
500+
501+
private function printHooks(Property $property): ?string
502+
{
503+
$out = null;
504+
505+
if ($setHook = $property->getSetHook()) {
506+
$out .= $this->indent(
507+
$this->printDocComment($setHook)
508+
. $this->printAttributes($setHook->getAttributes())
509+
. 'set '
510+
. ($setHook->getParameter() ? $this->printParameters($setHook) . ' ' : '')
511+
. ($setHook->isShortcut()
512+
? '=> ' . rtrim($setHook->getBody(), "\n") . ";\n"
513+
: "{\n" . $this->indent($this->printFunctionBody($setHook)) . "}\n"),
514+
);
515+
}
516+
517+
if ($getHook = $property->getGetHook()) {
518+
$out .= $this->indent(
519+
$this->printDocComment($getHook)
520+
. $this->printAttributes($getHook->getAttributes())
521+
. ($getHook->getReturnReference() ? '&' : '')
522+
. 'get '
523+
. ($getHook->isShortcut()
524+
? '=> ' . rtrim($getHook->getBody(), "\n") . ";\n"
525+
: "{\n" . $this->indent($this->printFunctionBody($getHook)) . "}\n"),
526+
);
527+
}
528+
529+
return $out;
530+
}
492531
}

src/PhpGenerator/Property.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ final class Property
2929
private bool $nullable = false;
3030
private bool $initialized = false;
3131
private bool $readOnly = false;
32+
private ?PropertyHook $getHook = null;
33+
private ?PropertyHook $setHook = null;
3234

3335

3436
public function setValue(mixed $val): static
@@ -113,6 +115,56 @@ public function isReadOnly(): bool
113115
}
114116

115117

118+
public function setSetHook(?PropertyHook $hook): static
119+
{
120+
$this->setHook = $hook;
121+
return $this;
122+
}
123+
124+
125+
public function getSetHook(): ?PropertyHook
126+
{
127+
return $this->setHook;
128+
}
129+
130+
131+
public function addSetHook(string $body, bool $shortcut = false): PropertyHook
132+
{
133+
return $this->setHook = (new PropertyHook)->setBody($body, $shortcut);
134+
}
135+
136+
137+
public function hasSetHook(): bool
138+
{
139+
return $this->setHook !== null;
140+
}
141+
142+
143+
public function setGetHook(?PropertyHook $hook): static
144+
{
145+
$this->getHook = $hook;
146+
return $this;
147+
}
148+
149+
150+
public function getGetHook(): ?PropertyHook
151+
{
152+
return $this->getHook;
153+
}
154+
155+
156+
public function addGetHook(string $body, bool $shortcut = false): PropertyHook
157+
{
158+
return $this->getHook = (new PropertyHook)->setBody($body, $shortcut);
159+
}
160+
161+
162+
public function hasGetHook(): bool
163+
{
164+
return $this->getHook !== null;
165+
}
166+
167+
116168
/** @throws Nette\InvalidStateException */
117169
public function validate(): void
118170
{

src/PhpGenerator/PropertyHook.php

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Nette\PhpGenerator;
6+
7+
class PropertyHook
8+
{
9+
use Traits\AttributeAware;
10+
use Traits\CommentAware;
11+
12+
private ?string $body = null;
13+
private bool $shortcut = false; // Whether to use the '=>' syntax
14+
private ?Parameter $parameter = null;
15+
private bool $returnReference = false;
16+
17+
18+
public function setBody(string $body, bool $shortcut = false): self
19+
{
20+
$this->body = $body;
21+
$this->shortcut = $shortcut;
22+
return $this;
23+
}
24+
25+
26+
public function getBody(): ?string
27+
{
28+
return $this->body;
29+
}
30+
31+
32+
public function isShortcut(): bool
33+
{
34+
return $this->shortcut;
35+
}
36+
37+
38+
/**
39+
* Adds a parameter. If it already exists, it overwrites it.
40+
* @param string $name without $
41+
*/
42+
public function setParameter(string $name = 'value'): Parameter
43+
{
44+
$param = new Parameter($name);
45+
return $this->parameter = $param;
46+
}
47+
48+
49+
public function getParameter(): ?Parameter
50+
{
51+
return $this->parameter;
52+
}
53+
54+
55+
/** @internal */
56+
public function getParameters(): array
57+
{
58+
return $this->parameter ? [$this->parameter] : [];
59+
}
60+
61+
62+
public function setReturnReference(bool $state = true): static
63+
{
64+
$this->returnReference = $state;
65+
return $this;
66+
}
67+
68+
69+
public function getReturnReference(): bool
70+
{
71+
return $this->returnReference;
72+
}
73+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
/**
4+
* Test: Nette\PhpGenerator - PHP 8.4 property hooks for classes
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
use Nette\PhpGenerator\ClassType;
10+
use Nette\PhpGenerator\PhpFile;
11+
use Nette\PhpGenerator\PropertyHook;
12+
use Nette\PhpGenerator\PsrPrinter;
13+
14+
require __DIR__ . '/../bootstrap.php';
15+
16+
$file = new PhpFile;
17+
$file->setStrictTypes();
18+
19+
$class = new ClassType('Locale');
20+
21+
$class->addProperty('languageCode')
22+
->setType('string')
23+
->setPublic();
24+
25+
$countryCodeSetHookClosure = (new PropertyHook)
26+
->setBody('$this->countryCode = strtoupper($countryCode);');
27+
28+
$countryCodeSetHookClosure->setParameter('countryCode')->setType('string');
29+
30+
$class->addProperty('countryCode')
31+
->setType('string')
32+
->setValue('AA')
33+
->setPublic()
34+
->setSetHook($countryCodeSetHookClosure);
35+
36+
$combinedCodeGetHookClosure = (new PropertyHook)
37+
->setBody('return \sprintf("%s_%s", $this->languageCode, $this->countryCode);');
38+
39+
$combinedCodeSetHookClosure = (new PropertyHook)
40+
->setBody('[$this->languageCode, $this->countryCode] = explode(\'_\', $value, 2);');
41+
42+
$combinedCodeSetHookClosure->setParameter('value')->setType('string');
43+
44+
$class->addProperty('combinedCode')
45+
->setType('string')
46+
->setPublic()
47+
->setGetHook($combinedCodeGetHookClosure)
48+
->setSetHook($combinedCodeSetHookClosure);
49+
50+
$expected = <<<'PHP'
51+
class Locale
52+
{
53+
public string $languageCode;
54+
55+
public string $countryCode = 'AA' {
56+
set (string $countryCode) {
57+
$this->countryCode = strtoupper($countryCode);
58+
}
59+
}
60+
61+
public string $combinedCode {
62+
set (string $value) {
63+
[$this->languageCode, $this->countryCode] = explode('_', $value, 2);
64+
}
65+
get {
66+
return \sprintf("%s_%s", $this->languageCode, $this->countryCode);
67+
}
68+
}
69+
}
70+
PHP;
71+
72+
same(rtrim($expected), rtrim((new PsrPrinter)->printClass($class)));

0 commit comments

Comments
 (0)