Skip to content

Commit

Permalink
Interfaces can have properties hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
shanginn authored and dg committed Nov 21, 2024
1 parent 409ba32 commit 37461ee
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 10 deletions.
5 changes: 4 additions & 1 deletion src/PhpGenerator/InterfaceType.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ final class InterfaceType extends ClassLike
{
use Traits\ConstantsAware;
use Traits\MethodsAware;
use Traits\PropertiesAware;

/** @var string[] */
private array $extends = [];
Expand Down Expand Up @@ -54,12 +55,13 @@ public function addExtend(string $name): static
/**
* Adds a member. If it already exists, throws an exception or overwrites it if $overwrite is true.
*/
public function addMember(Method|Constant $member, bool $overwrite = false): static
public function addMember(Method|Constant|Property $member, bool $overwrite = false): static
{
$name = $member->getName();
[$type, $n] = match (true) {
$member instanceof Constant => ['consts', $name],
$member instanceof Method => ['methods', strtolower($name)],
$member instanceof Property => ['properties', $name],
};
if (!$overwrite && isset($this->$type[$n])) {
throw new Nette\InvalidStateException("Cannot add member '$name', because it already exists.");
Expand All @@ -75,5 +77,6 @@ public function __clone(): void
$clone = fn($item) => clone $item;
$this->consts = array_map($clone, $this->consts);
$this->methods = array_map($clone, $this->methods);
$this->properties = array_map($clone, $this->properties);
}
}
24 changes: 15 additions & 9 deletions src/PhpGenerator/Printer.php
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,11 @@ public function printClass(
}

$properties = [];
if ($class instanceof ClassType || $class instanceof TraitType) {
if ($class instanceof ClassType || $class instanceof TraitType || $class instanceof InterfaceType) {
foreach ($class->getProperties() as $property) {
$properties[] = $this->printProperty($property, $readOnlyClass);
if (!$class instanceof InterfaceType || ($property->hasGetHook() || $property->hasSetHook())) {
$properties[] = $this->printProperty($property, $readOnlyClass, $class->isInterface());
}
}
}

Expand Down Expand Up @@ -375,7 +377,7 @@ private function printConstant(Constant $const): string
}


private function printProperty(Property $property, bool $readOnlyClass = false): string
private function printProperty(Property $property, bool $readOnlyClass = false, bool $isInterface = false): string
{
$property->validate();
$type = $property->getType();
Expand All @@ -386,9 +388,9 @@ private function printProperty(Property $property, bool $readOnlyClass = false):
. ltrim($this->printType($type, $property->isNullable()) . ' ')
. '$' . $property->getName());

$hooks = $this->printHooks($property);
$hooks = $this->printHooks($property, $isInterface);

$defaultValue = $property->getValue() === null && !$property->isInitialized()
$defaultValue = $isInterface || ($property->getValue() === null && !$property->isInitialized())
? ''
: ' = ' . $this->dump($property->getValue(), strlen($def) + 3); // 3 = ' = '

Expand Down Expand Up @@ -496,7 +498,7 @@ protected function isBraceOnNextLine(bool $multiLine, bool $hasReturnType): bool
}


private function printHooks(Property $property): ?string
private function printHooks(Property $property, bool $isInterface = false): ?string
{
$getHook = $property->getGetHook();
$setHook = $property->getSetHook();
Expand All @@ -505,15 +507,19 @@ private function printHooks(Property $property): ?string
return null;
}

$out = " {\n";
$out = ' {' . ($isInterface ? ' ' : "\n");

if ($getHook !== null) {
$out .= $this->indent("get {\n" . $this->indent($this->printFunctionBody($getHook)) . "}\n");
$out .= $isInterface
? 'get; '
: $this->indent("get {\n" . $this->indent($this->printFunctionBody($getHook)) . "}\n");
}

if ($setHook !== null) {
$params = $this->printParameters($setHook);
$out .= $this->indent('set ' . $params . ' {' . "\n" . $this->indent($this->printFunctionBody($setHook)) . "}\n");
$out .= $isInterface
? 'set; '
: $this->indent('set ' . $params . ' {' . "\n" . $this->indent($this->printFunctionBody($setHook)) . "}\n");
}

return $out . '}';
Expand Down
48 changes: 48 additions & 0 deletions tests/PhpGenerator/Property.hooks.interfaces.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

/**
* Test: Nette\PhpGenerator - PHP 8.4 property hooks for interfaces
*/

declare(strict_types=1);

use Nette\PhpGenerator\InterfaceType;
use Nette\PhpGenerator\PhpFile;
use Nette\PhpGenerator\PropertyHook;
use Nette\PhpGenerator\PsrPrinter;
use Nette\PhpGenerator\Type;

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

$file = new PhpFile;
$file->setStrictTypes();

$namespace = $file->addNamespace('Abc');

$interface = new InterfaceType('HasAuthor');

// This will not be printed because it does not have any hooks
$interface->addProperty('isVisible')
->setType(Type::Bool)
->setPublic();

$interface->addProperty('score')
->setType(Type::Int)
->setPublic()
->setGetHook(new PropertyHook);

$interface->addProperty('author')
->setType('Author')
->setPublic()
->setGetHook(new PropertyHook)
->setSetHook(new PropertyHook);

$expected = <<<'PHP'
interface HasAuthor
{
public int $score { get; }
public Author $author { get; set; }
}
PHP;

same(rtrim($expected), rtrim((new PsrPrinter)->printClass($interface)));

0 comments on commit 37461ee

Please sign in to comment.