Skip to content

Commit

Permalink
added ClassManipulator
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed Aug 19, 2024
1 parent 60820ea commit bfed454
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 53 deletions.
95 changes: 95 additions & 0 deletions src/PhpGenerator/ClassManipulator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/

declare(strict_types=1);

namespace Nette\PhpGenerator;

use Nette;


final class ClassManipulator
{
public function __construct(
private ClassType $class,
) {
}


/**
* Inherits property from parent class.
*/
public function inheritProperty(string $name, bool $returnIfExists = false): Property
{
$extends = $this->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));
}


/**
* Automatically 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);
}
}
}
43 changes: 4 additions & 39 deletions src/PhpGenerator/ClassType.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}


Expand Down
29 changes: 29 additions & 0 deletions tests/PhpGenerator/ClassManipulator.implementInterface.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

use Nette\PhpGenerator\ClassManipulator;
use Nette\PhpGenerator\ClassType;
use Tester\Assert;

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


interface TestInterface
{
public function testMethod();
}

$class = new ClassType('TestClass');
$manipulator = new ClassManipulator($class);

// Test valid interface implementation
$manipulator->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,
);
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

declare(strict_types=1);

use Nette\PhpGenerator\ClassManipulator;
use Nette\PhpGenerator\ClassType;
use Tester\Assert;

require __DIR__ . '/../bootstrap.php';
Expand All @@ -16,36 +18,38 @@ 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.",
);

$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
{
}

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.",
);
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

declare(strict_types=1);

use Nette\PhpGenerator\ClassManipulator;
use Nette\PhpGenerator\ClassType;
use Tester\Assert;

require __DIR__ . '/../bootstrap.php';
Expand All @@ -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
{
Expand All @@ -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.",
);

0 comments on commit bfed454

Please sign in to comment.