Skip to content

Commit 6de4f01

Browse files
committed
Property can be abstract / final
1 parent 2fb5e52 commit 6de4f01

File tree

5 files changed

+125
-2
lines changed

5 files changed

+125
-2
lines changed

readme.md

+15
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,21 @@ class Demo
699699
}
700700
```
701701

702+
Properties and property hooks can be abstract or final:
703+
704+
```php
705+
$class->addProperty('id')
706+
->setType('int')
707+
->addHook('get')
708+
->setAbstract();
709+
710+
$class->addProperty('role')
711+
->setType('string')
712+
->addHook('set', 'strtolower($value)')
713+
->setFinal();
714+
```
715+
716+
702717
 <!---->
703718

704719
Namespace

src/PhpGenerator/Printer.php

+4-2
Original file line numberDiff line numberDiff line change
@@ -380,12 +380,14 @@ private function printProperty(Property $property, bool $readOnlyClass = false,
380380
{
381381
$property->validate();
382382
$type = $property->getType();
383-
$def = (($property->getVisibility() ?: 'public')
383+
$def = ($property->isAbstract() && !$isInterface ? 'abstract ' : '')
384+
. ($property->isFinal() ? 'final ' : '')
385+
. ($property->getVisibility() ?: 'public')
384386
. ($property->isStatic() ? ' static' : '')
385387
. (!$readOnlyClass && $property->isReadOnly() && $type ? ' readonly' : '')
386388
. ' '
387389
. ltrim($this->printType($type, $property->isNullable()) . ' ')
388-
. '$' . $property->getName());
390+
. '$' . $property->getName();
389391

390392
$defaultValue = $property->getValue() === null && !$property->isInitialized()
391393
? ''

src/PhpGenerator/Property.php

+37
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ final class Property
2828
private ?string $type = null;
2929
private bool $nullable = false;
3030
private bool $initialized = false;
31+
private bool $final = false;
32+
private bool $abstract = false;
3133

3234

3335
public function setValue(mixed $val): static
@@ -99,11 +101,46 @@ public function isInitialized(): bool
99101
}
100102

101103

104+
public function setFinal(bool $state = true): static
105+
{
106+
$this->final = $state;
107+
return $this;
108+
}
109+
110+
111+
public function isFinal(): bool
112+
{
113+
return $this->final;
114+
}
115+
116+
117+
public function setAbstract(bool $state = true): static
118+
{
119+
$this->abstract = $state;
120+
return $this;
121+
}
122+
123+
124+
public function isAbstract(): bool
125+
{
126+
return $this->abstract;
127+
}
128+
129+
102130
/** @throws Nette\InvalidStateException */
103131
public function validate(): void
104132
{
105133
if ($this->readOnly && !$this->type) {
106134
throw new Nette\InvalidStateException("Property \$$this->name: Read-only properties are only supported on typed property.");
135+
136+
} elseif ($this->abstract && $this->final) {
137+
throw new Nette\InvalidStateException("Property \$$this->name cannot be abstract and final at the same time.");
138+
139+
} elseif (
140+
$this->abstract
141+
&& !Nette\Utils\Arrays::some($this->getHooks(), fn($hook) => $hook->isAbstract())
142+
) {
143+
throw new Nette\InvalidStateException("Property \$$this->name: Abstract property must have at least one abstract hook.");
107144
}
108145
}
109146
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
/**
4+
* Test: PHP 8.4 abstract/final property
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
use Nette\PhpGenerator\ClassType;
10+
11+
require __DIR__ . '/../bootstrap.php';
12+
13+
14+
$class = (new ClassType('Demo'))
15+
->setAbstract();
16+
17+
$class->addProperty('first')
18+
->setType('string')
19+
->setAbstract()
20+
->addHook('set')
21+
->setAbstract();
22+
23+
$prop = $class->addProperty('second')
24+
->setType('string')
25+
->setAbstract();
26+
27+
$prop->addHook('set')
28+
->setAbstract();
29+
30+
$prop->addHook('get', '123');
31+
32+
$class->addProperty('third')
33+
->setFinal();
34+
35+
same(<<<'XX'
36+
abstract class Demo
37+
{
38+
abstract public string $first { set; }
39+
40+
abstract public string $second {
41+
set;
42+
get => 123;
43+
}
44+
45+
final public $third;
46+
}
47+
48+
XX, (string) $class);
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Nette\PhpGenerator\Property;
6+
use Tester\Assert;
7+
8+
require __DIR__ . '/../bootstrap.php';
9+
10+
11+
Assert::exception(function () {
12+
$property = new Property('a');
13+
$property->setFinal()->setAbstract();
14+
$property->validate();
15+
}, Nette\InvalidStateException::class, 'Property $a cannot be abstract and final at the same time.');
16+
17+
Assert::exception(function () {
18+
$property = new Property('a');
19+
$property->setAbstract();
20+
$property->validate();
21+
}, Nette\InvalidStateException::class, 'Property $a: Abstract property must have at least one abstract hook.');

0 commit comments

Comments
 (0)