Skip to content

Commit

Permalink
Property can be abstract / final
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed Nov 28, 2024
1 parent 043b355 commit c0e1f2d
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 2 deletions.
15 changes: 15 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions src/PhpGenerator/Printer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
? ''
Expand Down
37 changes: 37 additions & 0 deletions src/PhpGenerator/Property.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.");
}
}
}
50 changes: 50 additions & 0 deletions tests/PhpGenerator/Property.abstract-final.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

/**
* Test: PHP 8.4 abstract/final property
*/

declare(strict_types=1);

use Nette\PhpGenerator\ClassType;

require __DIR__ . '/../bootstrap.php';


$class = (new ClassType('Demo'))
->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);
21 changes: 21 additions & 0 deletions tests/PhpGenerator/Property.validate.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

use Nette\PhpGenerator\Property;
use Tester\Assert;

require __DIR__ . '/../bootstrap.php';


Assert::exception(function () {
$property = new Property('a');
$property->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.');

0 comments on commit c0e1f2d

Please sign in to comment.