From ca63f9858a88c1f8a41904dafe2523c7429f2159 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 19 Aug 2024 02:45:41 +0200 Subject: [PATCH] added ClassManipulator --- readme.md | 33 +++++++ src/PhpGenerator/ClassManipulator.php | 95 +++++++++++++++++++ src/PhpGenerator/ClassType.php | 43 +-------- .../ClassManipulator.implementInterface.phpt | 29 ++++++ ...pt => ClassManipulator.inheritMethod.phpt} | 18 ++-- ... => ClassManipulator.inheritProperty.phpt} | 18 ++-- 6 files changed, 183 insertions(+), 53 deletions(-) create mode 100644 src/PhpGenerator/ClassManipulator.php create mode 100644 tests/PhpGenerator/ClassManipulator.implementInterface.phpt rename tests/PhpGenerator/{Method.inherit.phpt => ClassManipulator.inheritMethod.phpt} (59%) rename tests/PhpGenerator/{Property.inherit.phpt => ClassManipulator.inheritProperty.phpt} (57%) diff --git a/readme.md b/readme.md index 05e4b6a9..13d74d53 100644 --- a/readme.md +++ b/readme.md @@ -878,6 +878,39 @@ It requires `nikic/php-parser` to be installed.   +Class Manipulator +----------------- + +The [ClassManipulator](https://api.nette.org/php-generator/master/Nette/PhpGenerator/ClassManipulator.html) class provides tools for manipulating classes. + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$manipulator = new Nette\PhpGenerator\ClassManipulator($class); +``` + +The `inheritMethod()` method copies a method from a parent class or implemented interface into your class. This allows you to override the method or extend its signature: + +```php +$method = $manipulator->inheritMethod('bar'); +$method->setBody('...'); +``` + +The `inheritProperty()` method copies a property from a parent class into your class. This is useful when you want to have the same property in your class, but possibly with a different default value: + +```php +$property = $manipulator->inheritProperty('foo'); +$property->setValue('new value'); +``` + +The `implementInterface()` method automatically implements all methods from the given interface in your class: + +```php +$manipulator->implementInterface(SomeInterface::class); +// Now your class implements SomeInterface and includes all its methods +``` + +  + Variable Dumping ---------------- diff --git a/src/PhpGenerator/ClassManipulator.php b/src/PhpGenerator/ClassManipulator.php new file mode 100644 index 00000000..d97ee319 --- /dev/null +++ b/src/PhpGenerator/ClassManipulator.php @@ -0,0 +1,95 @@ +class->getExtends(); + if ($this->class->hasProperty($name)) { + return $returnIfExists + ? $this->class->getProperty($name) + : throw new Nette\InvalidStateException("Cannot inherit property '$name', because it already exists."); + + } elseif (!$extends) { + throw new Nette\InvalidStateException("Class '{$this->class->getName()}' has not setExtends() set."); + } + + try { + $rp = new \ReflectionProperty($extends, $name); + } catch (\ReflectionException) { + throw new Nette\InvalidStateException("Property '$name' has not been found in ancestor {$extends}"); + } + + $property = (new Factory)->fromPropertyReflection($rp); + $this->class->addMember($property); + return $property; + } + + + /** + * Inherits method from parent class or interface. + */ + public function inheritMethod(string $name, bool $returnIfExists = false): Method + { + $parents = [...(array) $this->class->getExtends(), ...$this->class->getImplements()]; + if ($this->class->hasMethod($name)) { + return $returnIfExists + ? $this->class->getMethod($name) + : throw new Nette\InvalidStateException("Cannot inherit method '$name', because it already exists."); + + } elseif (!$parents) { + throw new Nette\InvalidStateException("Class '{$this->class->getName()}' has neither setExtends() nor setImplements() set."); + } + + foreach ($parents as $parent) { + try { + $rm = new \ReflectionMethod($parent, $name); + } catch (\ReflectionException) { + continue; + } + $method = (new Factory)->fromMethodReflection($rm); + $this->class->addMember($method); + return $method; + } + + throw new Nette\InvalidStateException("Method '$name' has not been found in any ancestor: " . implode(', ', $parents)); + } + + + /** + * Implements all methods from the given interface. + */ + public function implementInterface(string $interfaceName): void + { + $interface = new \ReflectionClass($interfaceName); + if (!$interface->isInterface()) { + throw new Nette\InvalidArgumentException("Class '$interfaceName' is not an interface."); + } + + $this->class->addImplement($interfaceName); + foreach ($interface->getMethods() as $method) { + $this->inheritMethod($method->getName(), returnIfExists: true); + } + } +} diff --git a/src/PhpGenerator/ClassType.php b/src/PhpGenerator/ClassType.php index f8481f15..4e082685 100644 --- a/src/PhpGenerator/ClassType.php +++ b/src/PhpGenerator/ClassType.php @@ -195,55 +195,20 @@ public function addMember(Method|Property|Constant|TraitUse $member, bool $overw /** - * Inherits property from parent class. + * @deprecated use ClassManipulator::inheritProperty() */ public function inheritProperty(string $name, bool $returnIfExists = false): Property { - if (isset($this->properties[$name])) { - return $returnIfExists - ? $this->properties[$name] - : throw new Nette\InvalidStateException("Cannot inherit property '$name', because it already exists."); - - } elseif (!$this->extends) { - throw new Nette\InvalidStateException("Class '{$this->getName()}' has not setExtends() set."); - } - - try { - $rp = new \ReflectionProperty($this->extends, $name); - } catch (\ReflectionException) { - throw new Nette\InvalidStateException("Property '$name' has not been found in ancestor {$this->extends}"); - } - - return $this->properties[$name] = (new Factory)->fromPropertyReflection($rp); + return (new ClassManipulator($this))->inheritProperty($name, $returnIfExists); } /** - * Inherits method from parent class or interface. + * @deprecated use ClassManipulator::inheritMethod() */ public function inheritMethod(string $name, bool $returnIfExists = false): Method { - $lower = strtolower($name); - $parents = [...(array) $this->extends, ...$this->implements]; - if (isset($this->methods[$lower])) { - return $returnIfExists - ? $this->methods[$lower] - : throw new Nette\InvalidStateException("Cannot inherit method '$name', because it already exists."); - - } elseif (!$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; - } - return $this->methods[$lower] = (new Factory)->fromMethodReflection($rm); - } - - throw new Nette\InvalidStateException("Method '$name' has not been found in any ancestor: " . implode(', ', $parents)); + return (new ClassManipulator($this))->inheritMethod($name, $returnIfExists); } diff --git a/tests/PhpGenerator/ClassManipulator.implementInterface.phpt b/tests/PhpGenerator/ClassManipulator.implementInterface.phpt new file mode 100644 index 00000000..a0e18e52 --- /dev/null +++ b/tests/PhpGenerator/ClassManipulator.implementInterface.phpt @@ -0,0 +1,29 @@ +implementInterface(TestInterface::class); +Assert::true(in_array(TestInterface::class, $class->getImplements(), true)); +Assert::true($class->hasMethod('testMethod')); + +// Test exception for non-interface +Assert::exception( + fn() => $manipulator->implementInterface(stdClass::class), + InvalidArgumentException::class, +); diff --git a/tests/PhpGenerator/Method.inherit.phpt b/tests/PhpGenerator/ClassManipulator.inheritMethod.phpt similarity index 59% rename from tests/PhpGenerator/Method.inherit.phpt rename to tests/PhpGenerator/ClassManipulator.inheritMethod.phpt index 6fcc0523..4927e69c 100644 --- a/tests/PhpGenerator/Method.inherit.phpt +++ b/tests/PhpGenerator/ClassManipulator.inheritMethod.phpt @@ -2,6 +2,8 @@ declare(strict_types=1); +use Nette\PhpGenerator\ClassManipulator; +use Nette\PhpGenerator\ClassType; use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -16,9 +18,10 @@ class Foo // missing parent -$class = new Nette\PhpGenerator\ClassType('Test'); +$class = new ClassType('Test'); +$manipulator = new ClassManipulator($class); Assert::exception( - fn() => $class->inheritMethod('bar'), + fn() => $manipulator->inheritMethod('bar'), Nette\InvalidStateException::class, "Class 'Test' has neither setExtends() nor setImplements() set.", ); @@ -26,16 +29,17 @@ Assert::exception( $class->setExtends('Unknown1'); $class->addImplement('Unknown2'); Assert::exception( - fn() => $class->inheritMethod('bar'), + fn() => $manipulator->inheritMethod('bar'), Nette\InvalidStateException::class, "Method 'bar' has not been found in any ancestor: Unknown1, Unknown2", ); // implement method -$class = new Nette\PhpGenerator\ClassType('Test'); +$class = new ClassType('Test'); $class->setExtends(Foo::class); -$method = $class->inheritMethod('bar'); +$manipulator = new ClassManipulator($class); +$method = $manipulator->inheritMethod('bar'); Assert::match(<<<'XX' public function bar(int $a, ...$b): void { @@ -43,9 +47,9 @@ Assert::match(<<<'XX' XX, (string) $method); -Assert::same($method, $class->inheritMethod('bar', returnIfExists: true)); +Assert::same($method, $manipulator->inheritMethod('bar', returnIfExists: true)); Assert::exception( - fn() => $class->inheritMethod('bar', returnIfExists: false), + fn() => $manipulator->inheritMethod('bar', returnIfExists: false), Nette\InvalidStateException::class, "Cannot inherit method 'bar', because it already exists.", ); diff --git a/tests/PhpGenerator/Property.inherit.phpt b/tests/PhpGenerator/ClassManipulator.inheritProperty.phpt similarity index 57% rename from tests/PhpGenerator/Property.inherit.phpt rename to tests/PhpGenerator/ClassManipulator.inheritProperty.phpt index e110dae8..d9b9bf1b 100644 --- a/tests/PhpGenerator/Property.inherit.phpt +++ b/tests/PhpGenerator/ClassManipulator.inheritProperty.phpt @@ -2,6 +2,8 @@ declare(strict_types=1); +use Nette\PhpGenerator\ClassManipulator; +use Nette\PhpGenerator\ClassType; use Tester\Assert; require __DIR__ . '/../bootstrap.php'; @@ -14,25 +16,27 @@ class Foo // missing parent -$class = new Nette\PhpGenerator\ClassType('Test'); +$class = new ClassType('Test'); +$manipulator = new ClassManipulator($class); Assert::exception( - fn() => $class->inheritProperty('bar'), + fn() => $manipulator->inheritProperty('bar'), Nette\InvalidStateException::class, "Class 'Test' has not setExtends() set.", ); $class->setExtends('Unknown'); Assert::exception( - fn() => $class->inheritProperty('bar'), + fn() => $manipulator->inheritProperty('bar'), Nette\InvalidStateException::class, "Property 'bar' has not been found in ancestor Unknown", ); // implement property -$class = new Nette\PhpGenerator\ClassType('Test'); +$class = new ClassType('Test'); $class->setExtends(Foo::class); -$prop = $class->inheritProperty('bar'); +$manipulator = new ClassManipulator($class); +$prop = $manipulator->inheritProperty('bar'); Assert::match(<<<'XX' class Test extends Foo { @@ -41,9 +45,9 @@ Assert::match(<<<'XX' XX, (string) $class); -Assert::same($prop, $class->inheritProperty('bar', returnIfExists: true)); +Assert::same($prop, $manipulator->inheritProperty('bar', returnIfExists: true)); Assert::exception( - fn() => $class->inheritProperty('bar', returnIfExists: false), + fn() => $manipulator->inheritProperty('bar', returnIfExists: false), Nette\InvalidStateException::class, "Cannot inherit property 'bar', because it already exists.", );