From 86dd8da823300db88eab1910d74be716f71e87e8 Mon Sep 17 00:00:00 2001 From: Nikolai Shangin Date: Thu, 21 Nov 2024 18:50:18 +0100 Subject: [PATCH] Interfaces can have properties hooks --- src/PhpGenerator/InterfaceType.php | 5 +- src/PhpGenerator/Printer.php | 20 +++++--- .../Property.hooks.interfaces.phpt | 48 +++++++++++++++++++ 3 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 tests/PhpGenerator/Property.hooks.interfaces.phpt diff --git a/src/PhpGenerator/InterfaceType.php b/src/PhpGenerator/InterfaceType.php index 8743f8d0..9d884121 100644 --- a/src/PhpGenerator/InterfaceType.php +++ b/src/PhpGenerator/InterfaceType.php @@ -19,6 +19,7 @@ final class InterfaceType extends ClassLike { use Traits\ConstantsAware; use Traits\MethodsAware; + use Traits\PropertiesAware; /** @var string[] */ private array $extends = []; @@ -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."); @@ -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); } } diff --git a/src/PhpGenerator/Printer.php b/src/PhpGenerator/Printer.php index c68f16dc..ccca6240 100644 --- a/src/PhpGenerator/Printer.php +++ b/src/PhpGenerator/Printer.php @@ -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()); + } } } @@ -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(); @@ -386,11 +388,15 @@ private function printProperty(Property $property, bool $readOnlyClass = false): . ltrim($this->printType($type, $property->isNullable()) . ' ') . '$' . $property->getName()); - $hooks = !$property->getSetHook() && !$property->getGetHook() - ? ';' - : " {\n" . $this->printHooks($property) . '}'; + if (!$property->getSetHook() && !$property->getGetHook()) { + $hooks = ';'; + } elseif ($isInterface) { + $hooks = ' { ' . implode(' ', array_filter([$property->getSetHook() ? 'set;' : null, $property->getGetHook() ? 'get;' : null])) . ' }'; + } else { + $hooks = " {\n" . $this->printHooks($property) . '}'; + } - $defaultValue = $property->getValue() === null && !$property->isInitialized() + $defaultValue = $isInterface || ($property->getValue() === null && !$property->isInitialized()) ? '' : ' = ' . $this->dump($property->getValue(), strlen($def) + 3); // 3 = ' = ' diff --git a/tests/PhpGenerator/Property.hooks.interfaces.phpt b/tests/PhpGenerator/Property.hooks.interfaces.phpt new file mode 100644 index 00000000..6539cec6 --- /dev/null +++ b/tests/PhpGenerator/Property.hooks.interfaces.phpt @@ -0,0 +1,48 @@ +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 { set; get; } + } + PHP; + +same(rtrim($expected), rtrim((new PsrPrinter)->printClass($interface)));