Skip to content

Commit 5126fb8

Browse files
shanginndg
authored andcommitted
added support for properties hooks [Closes #171]
1 parent bad08ce commit 5126fb8

File tree

10 files changed

+372
-12
lines changed

10 files changed

+372
-12
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "nette/php-generator",
3-
"description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.3 features.",
3+
"description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.4 features.",
44
"keywords": ["nette", "php", "code", "scaffolding"],
55
"homepage": "https://nette.org",
66
"license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"],

readme.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Are you looking for a tool to generate PHP code for [classes](#classes), [functi
88

99
<h3>
1010

11-
✅ Supports all the latest PHP features like [enums](#enums), [attributes](#attributes), etc.<br>
11+
✅ Supports all the latest PHP features like [property hooks](#property-hooks), [enums](#enums), [attributes](#attributes), etc.<br>
1212
✅ Allows you to easily modify [existing classes](#generating-from-existing-ones)<br>
1313
✅ Output compliant with [PSR-12 / PER coding style](#printer-and-psr-compliance)<br>
1414
✅ Highly mature, stable, and widely used library
@@ -665,6 +665,42 @@ class Demo
665665

666666
 <!---->
667667

668+
Property Hooks
669+
--------------
670+
671+
You can also define property hooks (represented by the class [PropertyHook](https://api.nette.org/php-generator/master/Nette/PhpGenerator/PropertyHook.html)) for get and set operations, a feature introduced in PHP 8.4:
672+
673+
```php
674+
$class = new Nette\PhpGenerator\ClassType('Demo');
675+
$prop = $class->addProperty('firstName')
676+
->setType('string');
677+
678+
$prop->addHook('set', 'strtolower($value)')
679+
->addParameter('value')
680+
->setType('string');
681+
682+
$prop->addHook('get')
683+
->setBody('return ucfirst($this->firstName);');
684+
685+
echo $class;
686+
```
687+
688+
This generates:
689+
690+
```php
691+
class Demo
692+
{
693+
public string $firstName {
694+
set(string $value) => strtolower($value);
695+
get {
696+
return ucfirst($this->firstName);
697+
}
698+
}
699+
}
700+
```
701+
702+
 <!---->
703+
668704
Namespace
669705
---------
670706

src/PhpGenerator/Printer.php

Lines changed: 36 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-
private function printFunctionBody(Closure|GlobalFunction|Method $function): string
132+
private function printFunctionBody(Closure|GlobalFunction|Method|PropertyHook $function): string
133133
{
134134
$code = Helpers::simplifyTaggedNames($function->getBody(), $this->namespace);
135135
$code = Strings::normalize($code);
@@ -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)
@@ -351,6 +351,7 @@ private function formatParameters(Closure|GlobalFunction|Method $function, bool
351351
. ($variadic ? '...' : '')
352352
. '$' . $param->getName()
353353
. ($param->hasDefaultValue() && !$variadic ? ' = ' . $this->dump($param->getDefaultValue()) : '')
354+
. ($param instanceof PromotedParameter ? $this->printHooks($param) : '')
354355
. ($multiline ? ",\n" : ', ');
355356
}
356357

@@ -386,13 +387,16 @@ private function printProperty(Property $property, bool $readOnlyClass = false):
386387
. ltrim($this->printType($type, $property->isNullable()) . ' ')
387388
. '$' . $property->getName());
388389

390+
$defaultValue = $property->getValue() === null && !$property->isInitialized()
391+
? ''
392+
: ' = ' . $this->dump($property->getValue(), strlen($def) + 3); // 3 = ' = '
393+
389394
return $this->printDocComment($property)
390395
. $this->printAttributes($property->getAttributes())
391396
. $def
392-
. ($property->getValue() === null && !$property->isInitialized()
393-
? ''
394-
: ' = ' . $this->dump($property->getValue(), strlen($def) + 3)) // 3 = ' = '
395-
. ";\n";
397+
. $defaultValue
398+
. ($this->printHooks($property) ?: ';')
399+
. "\n";
396400
}
397401

398402

@@ -452,6 +456,30 @@ protected function printAttributes(array $attrs, bool $inline = false): string
452456
}
453457

454458

459+
private function printHooks(Property|PromotedParameter $property): string
460+
{
461+
$hooks = $property->getHooks();
462+
if (!$hooks) {
463+
return '';
464+
}
465+
466+
foreach ($property->getHooks() as $type => $hook) {
467+
$hooks[$type] = $this->printDocComment($hook)
468+
. $this->printAttributes($hook->getAttributes())
469+
. ($hook->isFinal() ? 'final ' : '')
470+
. ($hook->getReturnReference() ? '&' : '')
471+
. $type
472+
. ($hook->getParameters() ? $this->printParameters($hook) : '')
473+
. ' '
474+
. ($hook->isShort()
475+
? '=> ' . $hook->getBody() . ';'
476+
: "{\n" . $this->indent($this->printFunctionBody($hook)) . '}');
477+
}
478+
479+
return " {\n" . $this->indent(implode("\n", $hooks)) . "\n}";
480+
}
481+
482+
455483
public function setTypeResolving(bool $state = true): static
456484
{
457485
$this->resolveTypes = $state;

src/PhpGenerator/PromotedParameter.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,10 @@ public function validate(): void
2626
throw new Nette\InvalidStateException("Property \${$this->getName()}: Read-only properties are only supported on typed property.");
2727
}
2828
}
29+
30+
31+
public function __clone(): void
32+
{
33+
$this->hooks = array_map(fn($item) => $item ? clone $item : $item, $this->hooks);
34+
}
2935
}

src/PhpGenerator/Property.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,10 @@ public function validate(): void
106106
throw new Nette\InvalidStateException("Property \$$this->name: Read-only properties are only supported on typed property.");
107107
}
108108
}
109+
110+
111+
public function __clone(): void
112+
{
113+
$this->hooks = array_map(fn($item) => $item ? clone $item : $item, $this->hooks);
114+
}
109115
}

src/PhpGenerator/PropertyHook.php

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Nette\PhpGenerator;
6+
7+
use JetBrains\PhpStorm\Language;
8+
9+
10+
/**
11+
* Definition of a property hook.
12+
*/
13+
final class PropertyHook
14+
{
15+
use Traits\AttributeAware;
16+
use Traits\CommentAware;
17+
18+
private string $body = '';
19+
private bool $short = false;
20+
private bool $final = false;
21+
22+
/** @var Parameter[] */
23+
private array $parameters = [];
24+
private bool $returnReference = false;
25+
26+
27+
/** @param ?mixed[] $args */
28+
public function setBody(
29+
#[Language('PHP')]
30+
string $code,
31+
?array $args = null,
32+
bool $short = false,
33+
): static
34+
{
35+
$this->body = $args === null
36+
? $code
37+
: (new Dumper)->format($code, ...$args);
38+
$this->short = $short;
39+
return $this;
40+
}
41+
42+
43+
public function getBody(): string
44+
{
45+
return $this->body;
46+
}
47+
48+
49+
public function isShort(): bool
50+
{
51+
return $this->short && trim($this->body) !== '';
52+
}
53+
54+
55+
public function setFinal(bool $state = true): static
56+
{
57+
$this->final = $state;
58+
return $this;
59+
}
60+
61+
62+
public function isFinal(): bool
63+
{
64+
return $this->final;
65+
}
66+
67+
68+
/** @internal */
69+
public function setParameters(array $val): static
70+
{
71+
(function (Parameter ...$val) {})(...$val);
72+
$this->parameters = [];
73+
foreach ($val as $v) {
74+
$this->parameters[$v->getName()] = $v;
75+
}
76+
77+
return $this;
78+
}
79+
80+
81+
/** @internal */
82+
public function getParameters(): array
83+
{
84+
return $this->parameters;
85+
}
86+
87+
88+
/**
89+
* Adds a parameter. If it already exists, it overwrites it.
90+
* @param string $name without $
91+
*/
92+
public function addParameter(string $name): Parameter
93+
{
94+
return $this->parameters[$name] = new Parameter($name);
95+
}
96+
97+
98+
public function setReturnReference(bool $state = true): static
99+
{
100+
$this->returnReference = $state;
101+
return $this;
102+
}
103+
104+
105+
public function getReturnReference(): bool
106+
{
107+
return $this->returnReference;
108+
}
109+
}

src/PhpGenerator/PropertyHookType.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the Nette Framework (https://nette.org)
5+
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Nette\PhpGenerator;
11+
12+
use Nette;
13+
14+
15+
/**
16+
* Property hook type.
17+
*/
18+
/*enum*/ final class PropertyHookType
19+
{
20+
use Nette\StaticClass;
21+
22+
public const Set = 'set';
23+
public const Get = 'get';
24+
25+
26+
/** @internal */
27+
public static function from(string $value): string
28+
{
29+
return $value === self::Set || $value === self::Get
30+
? $value
31+
: throw new \ValueError("'$value' is not a valid value of hook type");
32+
}
33+
}

src/PhpGenerator/Traits/PropertyLike.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
namespace Nette\PhpGenerator\Traits;
1111

12+
use Nette\PhpGenerator\PropertyHook;
13+
use Nette\PhpGenerator\PropertyHookType;
1214

1315

1416
/**
@@ -20,6 +22,9 @@ trait PropertyLike
2022

2123
private bool $readOnly = false;
2224

25+
/** @var array<string, ?PropertyHook> */
26+
private array $hooks = [PropertyHookType::Set => null, PropertyHookType::Get => null];
27+
2328

2429
public function setReadOnly(bool $state = true): static
2530
{
@@ -32,4 +37,45 @@ public function isReadOnly(): bool
3237
{
3338
return $this->readOnly;
3439
}
40+
41+
42+
/**
43+
* Replaces all hooks.
44+
* @param PropertyHook[] $hooks
45+
*/
46+
public function setHooks(array $hooks): static
47+
{
48+
(function (PropertyHook ...$hooks) {})(...$hooks);
49+
$this->hooks = $hooks;
50+
return $this;
51+
}
52+
53+
54+
/** @return array<string, PropertyHook> */
55+
public function getHooks(): array
56+
{
57+
return array_filter($this->hooks);
58+
}
59+
60+
61+
/** @param 'set'|'get' $type */
62+
public function addHook(string $type, string $shortBody = ''): PropertyHook
63+
{
64+
return $this->hooks[PropertyHookType::from($type)] = (new PropertyHook)
65+
->setBody($shortBody, short: true);
66+
}
67+
68+
69+
/** @param 'set'|'get' $type */
70+
public function getHook(string $type): ?PropertyHook
71+
{
72+
return $this->hooks[PropertyHookType::from($type)] ?? null;
73+
}
74+
75+
76+
/** @param 'set'|'get' $type */
77+
public function hasHook(string $type): bool
78+
{
79+
return isset($this->hooks[PropertyHookType::from($type)]);
80+
}
3581
}

0 commit comments

Comments
 (0)