Skip to content

Commit 09d7a2b

Browse files
committed
ClassManipulator::implement() can implement abstract classes
1 parent 4628dec commit 09d7a2b

File tree

3 files changed

+70
-15
lines changed

3 files changed

+70
-15
lines changed

readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -902,7 +902,7 @@ $property = $manipulator->inheritProperty('foo');
902902
$property->setValue('new value');
903903
```
904904
905-
The `implement()` method automatically implements all methods from the given interface in your class:
905+
The `implement()` method automatically implements all methods and properties from the given interface or abstract class:
906906
907907
```php
908908
$manipulator->implement(SomeInterface::class);

src/PhpGenerator/ClassManipulator.php

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,32 +68,44 @@ public function inheritMethod(string $name, bool $returnIfExists = false): Metho
6868
} catch (\ReflectionException) {
6969
continue;
7070
}
71-
$method = (new Factory)->fromMethodReflection($rm);
72-
$this->class->addMember($method);
73-
return $method;
71+
return $this->implementMethod($rm);
7472
}
7573

7674
throw new Nette\InvalidStateException("Method '$name' has not been found in any ancestor: " . implode(', ', $parents));
7775
}
7876

7977

8078
/**
81-
* Implements all methods from the given interface.
79+
* Implements all methods from the given interface or abstract class.
8280
*/
8381
public function implement(string $name): void
8482
{
8583
$definition = new \ReflectionClass($name);
86-
if (!$definition->isInterface()) {
87-
throw new Nette\InvalidArgumentException("Class '$name' is not an interface.");
84+
if ($definition->isInterface()) {
85+
$this->class->addImplement($name);
86+
} elseif ($definition->isAbstract()) {
87+
$this->class->setExtends($name);
88+
} else {
89+
throw new Nette\InvalidArgumentException("'$name' is not an interface or abstract class.");
8890
}
8991

90-
$this->class->addImplement($name);
9192
foreach ($definition->getMethods() as $method) {
92-
$this->inheritMethod($method->getName(), returnIfExists: true);
93+
if (!$this->class->hasMethod($method->getName()) && $method->isAbstract()) {
94+
$this->implementMethod($method);
95+
}
9396
}
9497
}
9598

9699

100+
private function implementMethod(\ReflectionMethod $rm): Method
101+
{
102+
$method = (new Factory)->fromMethodReflection($rm);
103+
$method->setAbstract(false);
104+
$this->class->addMember($method);
105+
return $method;
106+
}
107+
108+
97109
/** @deprecated use implement() */
98110
public function implementInterface(string $interfaceName): void
99111
{

tests/PhpGenerator/ClassManipulator.implement.phpt

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,64 @@ use Tester\Assert;
99
require __DIR__ . '/../bootstrap.php';
1010

1111

12-
interface TestInterface
12+
interface ParentInterface
1313
{
14-
public function testMethod();
14+
public function interfaceMethod();
1515
}
1616

17+
interface TestInterface extends ParentInterface
18+
{
19+
}
20+
21+
abstract class ParentAbstract
22+
{
23+
abstract public function abstractMethod();
24+
25+
26+
public function concreteMethod()
27+
{
28+
}
29+
}
30+
31+
abstract class TestAbstract extends ParentAbstract
32+
{
33+
}
34+
35+
1736
$class = new ClassType('TestClass');
1837
$manipulator = new ClassManipulator($class);
1938

20-
// Test valid interface implementation
39+
// Test interface implementation
2140
$manipulator->implement(TestInterface::class);
22-
Assert::true(in_array(TestInterface::class, $class->getImplements(), true));
23-
Assert::true($class->hasMethod('testMethod'));
41+
Assert::match(<<<'XX'
42+
class TestClass implements TestInterface
43+
{
44+
function interfaceMethod()
45+
{
46+
}
47+
}
48+
49+
XX, (string) $class);
50+
51+
52+
// Test abstract class extension
53+
$class = new ClassType('TestClass');
54+
$manipulator = new ClassManipulator($class);
55+
$manipulator->implement(TestAbstract::class);
56+
Assert::match(<<<'XX'
57+
class TestClass extends TestAbstract
58+
{
59+
public function abstractMethod()
60+
{
61+
}
62+
}
63+
64+
XX, (string) $class);
65+
2466

25-
// Test exception for non-interface
67+
// Test exception for regular class
2668
Assert::exception(
2769
fn() => $manipulator->implement(stdClass::class),
2870
InvalidArgumentException::class,
71+
"'stdClass' is not an interface or abstract class.",
2972
);

0 commit comments

Comments
 (0)