diff --git a/spec/Prophecy/Doubler/Generator/ClassMirrorSpec.php b/spec/Prophecy/Doubler/Generator/ClassMirrorSpec.php index cf66dc57b..de8044807 100644 --- a/spec/Prophecy/Doubler/Generator/ClassMirrorSpec.php +++ b/spec/Prophecy/Doubler/Generator/ClassMirrorSpec.php @@ -564,6 +564,26 @@ function it_ignores_final_methods($class, $method) $classNode->getMethods()->shouldHaveCount(0); } + /** + * @param ReflectionClass $class + * @param ReflectionMethod $method + */ + function it_marks_final_methods_as_unextendable($class, $method) + { + $class->getName()->willReturn('Custom\ClassName'); + $class->isInterface()->willReturn(false); + $class->isFinal()->willReturn(false); + $class->getMethods(ReflectionMethod::IS_ABSTRACT)->willReturn(array()); + $class->getMethods(ReflectionMethod::IS_PUBLIC)->willReturn(array($method)); + + $method->isFinal()->willReturn(true); + $method->getName()->willReturn('finalImplementation'); + + $classNode = $this->reflect($class, array()); + $classNode->getUnextendableMethods()->shouldHaveCount(1); + $classNode->isExtendable('finalImplementation')->shouldReturn(false); + } + /** * @param ReflectionClass $interface */ diff --git a/spec/Prophecy/Doubler/Generator/Node/ClassNodeSpec.php b/spec/Prophecy/Doubler/Generator/Node/ClassNodeSpec.php index 18f0e1cc9..be7e1026e 100644 --- a/spec/Prophecy/Doubler/Generator/Node/ClassNodeSpec.php +++ b/spec/Prophecy/Doubler/Generator/Node/ClassNodeSpec.php @@ -3,6 +3,7 @@ namespace spec\Prophecy\Doubler\Generator\Node; use PhpSpec\ObjectBehavior; +use Prophecy\Exception\Doubler\MethodNotExtendableException; class ClassNodeSpec extends ObjectBehavior { @@ -151,4 +152,49 @@ function its_addProperty_lowercases_visibility_before_setting() $this->addProperty('text', 'PRIVATE'); $this->getProperties()->shouldReturn(array('text' => 'private')); } + + function its_has_no_unextendable_methods_by_default() + { + $this->getUnextendableMethods()->shouldHaveCount(0); + } + + function its_addUnextendableMethods_adds_an_unextendable_method() + { + $this->addUnextendableMethod('testMethod'); + $this->getUnextendableMethods()->shouldHaveCount(1); + } + + function its_methods_are_extendable_by_default() + { + $this->isExtendable('testMethod')->shouldReturn(true); + } + + function its_unextendable_methods_are_not_extendable() + { + $this->addUnextendableMethod('testMethod'); + $this->isExtendable('testMethod')->shouldReturn(false); + } + + function its_addUnextendableMethods_doesnt_create_duplicates() + { + $this->addUnextendableMethod('testMethod'); + $this->addUnextendableMethod('testMethod'); + $this->getUnextendableMethods()->shouldHaveCount(1); + } + + /** + * @param \Prophecy\Doubler\Generator\Node\MethodNode $method + */ + function it_throws_an_exception_when_adding_a_method_that_isnt_extendable($method) + { + $this->addUnextendableMethod('testMethod'); + $method->getName()->willReturn('testMethod'); + + $expectedException = new MethodNotExtendableException( + "Method `testMethod` is not extendable, so can not be added.", + "stdClass", + "testMethod" + ); + $this->shouldThrow($expectedException)->duringAddMethod($method); + } } diff --git a/spec/Prophecy/Exception/Doubler/MethodNotExtendableExceptionSpec.php b/spec/Prophecy/Exception/Doubler/MethodNotExtendableExceptionSpec.php new file mode 100644 index 000000000..5028b0263 --- /dev/null +++ b/spec/Prophecy/Exception/Doubler/MethodNotExtendableExceptionSpec.php @@ -0,0 +1,29 @@ +beConstructedWith('', 'User', 'getName'); + } + + function it_is_DoubleException() + { + $this->shouldHaveType('Prophecy\Exception\Doubler\DoubleException'); + } + + function it_has_MethodName() + { + $this->getMethodName()->shouldReturn('getName'); + } + + function it_has_classname() + { + $this->getClassName()->shouldReturn('User'); + } +} diff --git a/src/Prophecy/Doubler/Generator/ClassMirror.php b/src/Prophecy/Doubler/Generator/ClassMirror.php index af2bd6706..acdb2c15b 100644 --- a/src/Prophecy/Doubler/Generator/ClassMirror.php +++ b/src/Prophecy/Doubler/Generator/ClassMirror.php @@ -110,6 +110,7 @@ private function reflectClassToNode(ReflectionClass $class, Node\ClassNode $node } if (true === $method->isFinal()) { + $node->addUnextendableMethod($method->getName()); continue; } diff --git a/src/Prophecy/Doubler/Generator/Node/ClassNode.php b/src/Prophecy/Doubler/Generator/Node/ClassNode.php index b38cb4ea3..1499a1d32 100644 --- a/src/Prophecy/Doubler/Generator/Node/ClassNode.php +++ b/src/Prophecy/Doubler/Generator/Node/ClassNode.php @@ -11,6 +11,7 @@ namespace Prophecy\Doubler\Generator\Node; +use Prophecy\Exception\Doubler\MethodNotExtendableException; use Prophecy\Exception\InvalidArgumentException; /** @@ -23,6 +24,7 @@ class ClassNode private $parentClass = 'stdClass'; private $interfaces = array(); private $properties = array(); + private $unextendableMethods = array(); /** * @var MethodNode[] @@ -100,6 +102,12 @@ public function getMethods() public function addMethod(MethodNode $method) { + if (!$this->isExtendable($method->getName())){ + $message = sprintf( + 'Method `%s` is not extendable, so can not be added.', $method->getName() + ); + throw new MethodNotExtendableException($message, $this->getParentClass(), $method->getName()); + } $this->methods[$method->getName()] = $method; } @@ -127,4 +135,32 @@ public function hasMethod($name) { return isset($this->methods[$name]); } + + /** + * @return string[] + */ + public function getUnextendableMethods() + { + return $this->unextendableMethods; + } + + /** + * @param string $unextendableMethod + */ + public function addUnextendableMethod($unextendableMethod) + { + if (!$this->isExtendable($unextendableMethod)){ + return; + } + $this->unextendableMethods[] = $unextendableMethod; + } + + /** + * @param string $method + * @return bool + */ + public function isExtendable($method) + { + return !in_array($method, $this->unextendableMethods); + } } diff --git a/src/Prophecy/Exception/Doubler/MethodNotExtendableException.php b/src/Prophecy/Exception/Doubler/MethodNotExtendableException.php new file mode 100644 index 000000000..8437dc2ef --- /dev/null +++ b/src/Prophecy/Exception/Doubler/MethodNotExtendableException.php @@ -0,0 +1,47 @@ +methodName = $methodName; + $this->className = $className; + } + + + /** + * @return string + */ + public function getMethodName() + { + return $this->methodName; + } + + /** + * @return string + */ + public function getClassName() + { + return $this->className; + } + + }