diff --git a/readme.md b/readme.md index 21c7080f..b8030b14 100644 --- a/readme.md +++ b/readme.md @@ -698,6 +698,21 @@ class Demo } ``` +Properties and property hooks can be abstract or final: + +```php +$class->addProperty('id') + ->setType('int') + ->addHook('get') + ->setAbstract(); + +$class->addProperty('role') + ->setType('string') + ->addHook('set', '$this->role = strtolower($value);') + ->setFinal(); +``` + +   Namespace diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index 113665fa..39336d15 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -380,12 +380,14 @@ private function printProperty(Property $property, bool $readOnlyClass = false, { $property->validate(); $type = $property->getType(); - $def = (($property->getVisibility() ?: 'public') + $def = ($property->isAbstract() && !$isInterface ? 'abstract ' : '') + . ($property->isFinal() ? 'final ' : '') + . ($property->getVisibility() ?: 'public') . ($property->isStatic() ? ' static' : '') . (!$readOnlyClass && $property->isReadOnly() && $type ? ' readonly' : '') . ' ' . ltrim($this->printType($type, $property->isNullable()) . ' ') - . '$' . $property->getName()); + . '$' . $property->getName(); $defaultValue = $property->getValue() === null && !$property->isInitialized() ? '' diff --git a/src/PhpGenerator/Property.php b/src/PhpGenerator/Property.php index b13ebc3b..02024f2e 100644 --- a/src/PhpGenerator/Property.php +++ b/src/PhpGenerator/Property.php @@ -28,6 +28,8 @@ final class Property private ?string $type = null; private bool $nullable = false; private bool $initialized = false; + private bool $final = false; + private bool $abstract = false; public function setValue(mixed $val): static @@ -99,11 +101,46 @@ public function isInitialized(): bool } + public function setFinal(bool $state = true): static + { + $this->final = $state; + return $this; + } + + + public function isFinal(): bool + { + return $this->final; + } + + + public function setAbstract(bool $state = true): static + { + $this->abstract = $state; + return $this; + } + + + public function isAbstract(): bool + { + return $this->abstract; + } + + /** @throws Nette\InvalidStateException */ public function validate(): void { if ($this->readOnly && !$this->type) { throw new Nette\InvalidStateException("Property \$$this->name: Read-only properties are only supported on typed property."); + + } elseif ($this->abstract && $this->final) { + throw new Nette\InvalidStateException("Property \$$this->name cannot be abstract and final at the same time."); + + } elseif ( + $this->abstract + && !Nette\Utils\Arrays::some($this->getHooks(), fn($hook) => $hook->isAbstract()) + ) { + throw new Nette\InvalidStateException("Property \$$this->name: Abstract property must have at least one abstract hook."); } } } diff --git a/tests/PhpGenerator/Property.abstract-final.phpt b/tests/PhpGenerator/Property.abstract-final.phpt new file mode 100644 index 00000000..481e4fec --- /dev/null +++ b/tests/PhpGenerator/Property.abstract-final.phpt @@ -0,0 +1,50 @@ +setAbstract(); + +$class->addProperty('first') + ->setType('string') + ->setAbstract() + ->addHook('set') + ->setAbstract(); + +$prop = $class->addProperty('second') + ->setType('string') + ->setAbstract(); + +$prop->addHook('set') + ->setAbstract(); + +$prop->addHook('get', 'return 123;'); + +$class->addProperty('third') + ->setFinal(); + +same(<<<'XX' + abstract class Demo + { + abstract public string $first { set; } + + abstract public string $second { + set; + get { + return 123; + } + } + + final public $third; + } + + XX, (string) $class); diff --git a/tests/PhpGenerator/Property.validate.phpt b/tests/PhpGenerator/Property.validate.phpt new file mode 100644 index 00000000..e12a4125 --- /dev/null +++ b/tests/PhpGenerator/Property.validate.phpt @@ -0,0 +1,21 @@ +setFinal()->setAbstract(); + $property->validate(); +}, Nette\InvalidStateException::class, 'Property $a cannot be abstract and final at the same time.'); + +Assert::exception(function () { + $property = new Property('a'); + $property->setAbstract(); + $property->validate(); +}, Nette\InvalidStateException::class, 'Property $a: Abstract property must have at least one abstract hook.');