diff --git a/src/PhpGenerator/Traits/MethodsAware.php b/src/PhpGenerator/Traits/MethodsAware.php index a28c0613..7cd445a5 100644 --- a/src/PhpGenerator/Traits/MethodsAware.php +++ b/src/PhpGenerator/Traits/MethodsAware.php @@ -79,4 +79,46 @@ public function hasMethod(string $name): bool { return isset($this->methods[strtolower($name)]); } + + + /** + * Inherits method from parent class or interface. + */ + public function inheritMethod(string $name, bool $callParent = false, bool $returnIfExists = false): Method + { + $lower = strtolower($name); + if (isset($this->methods[$lower])) { + return $returnIfExists + ? $this->methods[$lower] + : throw new Nette\InvalidStateException("Cannot inherit method '$name', because it already exists."); + } + + $parents = match (true) { + $this instanceof Nette\PhpGenerator\ClassType => [...(array) $this->getExtends(), ...$this->getImplements()], + $this instanceof Nette\PhpGenerator\InterfaceType => $this->getExtends(), + $this instanceof Nette\PhpGenerator\EnumType => $this->getImplements(), + }; + if (!$parents) { + throw new Nette\InvalidStateException("Class '{$this->getName()}' has neither setExtends() nor setImplements() set."); + } + + foreach ($parents as $parent) { + try { + $rm = new \ReflectionMethod($parent, $name); + } catch (\ReflectionException) { + continue; + } + $method = Method::from([$parent, $name]); + if ($callParent) { + $params = array_map(fn(\ReflectionParameter $param) => '$' . $param->getName(), $rm->getParameters()); + if ($rm->isVariadic()) { + $params[] = '...' . array_pop($params); + } + $method->setBody('parent::' . $rm->getName() . '(' . implode(', ', $params) . ');'); + } + return $this->methods[$lower] = $method; + } + + throw new Nette\InvalidStateException("Method '$name' has not been found in any ancestor: " . implode(', ', $parents)); + } } diff --git a/tests/PhpGenerator/Method.inherit.phpt b/tests/PhpGenerator/Method.inherit.phpt new file mode 100644 index 00000000..08614a1a --- /dev/null +++ b/tests/PhpGenerator/Method.inherit.phpt @@ -0,0 +1,66 @@ + $class->inheritMethod('bar'), + Nette\InvalidStateException::class, + "Class 'Test' has neither setExtends() nor setImplements() set.", +); + +$class->setExtends('Unknown1'); +$class->addImplement('Unknown2'); +Assert::exception( + fn() => $class->inheritMethod('bar'), + Nette\InvalidStateException::class, + "Method 'bar' has not been found in any ancestor: Unknown1, Unknown2", +); + + +// implemented method +$class = new Nette\PhpGenerator\ClassType('Test'); +$class->setExtends(Foo::class); +$method = $class->inheritMethod('bar'); +Assert::match(<<<'XX' + public function bar(int $a, ...$b): void + { + } + + XX, (string) $method); + +$class = new Nette\PhpGenerator\ClassType('Test'); +$class->setExtends(Foo::class); +$method = $class->inheritMethod('Bar', callParent: true); // intentionally case insensitive +Assert::match(<<<'XX' + public function bar(int $a, ...$b): void + { + parent::bar($a, ...$b); + } + + XX, (string) $method); + + +// exists +$class = new Nette\PhpGenerator\ClassType('Test'); +$method = $class->addMethod('bar'); +Assert::same($method, $class->inheritMethod('bar', returnIfExists: true)); +Assert::exception( + fn() => $class->inheritMethod('bar', returnIfExists: false), + Nette\InvalidStateException::class, + "Cannot inherit method 'bar', because it already exists.", +);