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 29, 2024
1 parent 466918d commit b9b02a3
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 3 deletions.
15 changes: 15 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,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', 'strtolower($value)')
->setFinal();
```


 <!---->

Namespace
Expand Down
2 changes: 1 addition & 1 deletion src/PhpGenerator/ClassManipulator.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ private function implementMethod(\ReflectionMethod $rm): Method
private function implementProperty(\ReflectionProperty $rp): Property
{
$property = (new Factory)->fromPropertyReflection($rp);
$property->setHooks([]);
$property->setHooks([])->setAbstract(false);
$this->class->addMember($property);
return $property;
}
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.");
}
}

Expand Down
48 changes: 48 additions & 0 deletions tests/PhpGenerator/Property.abstract-final.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?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', '123');

$class->addProperty('third')
->setFinal();

same(<<<'XX'
abstract class Demo
{
abstract public string $first { set; }
abstract public string $second {
set;
get => 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 b9b02a3

Please sign in to comment.