Skip to content

Commit

Permalink
Merge pull request #203 from kamioftea/final-constructors
Browse files Browse the repository at this point in the history
[Bugfix] Prevent final methods from being manually extended.
  • Loading branch information
everzet committed Feb 15, 2016
2 parents cb34a7a + dfdf6a0 commit a749c19
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 0 deletions.
20 changes: 20 additions & 0 deletions spec/Prophecy/Doubler/Generator/ClassMirrorSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
46 changes: 46 additions & 0 deletions spec/Prophecy/Doubler/Generator/Node/ClassNodeSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace spec\Prophecy\Doubler\Generator\Node;

use PhpSpec\ObjectBehavior;
use Prophecy\Exception\Doubler\MethodNotExtendableException;

class ClassNodeSpec extends ObjectBehavior
{
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace spec\Prophecy\Exception\Doubler;

use PhpSpec\ObjectBehavior;
use spec\Prophecy\Exception\Prophecy;

class MethodNotExtendableExceptionSpec extends ObjectBehavior
{
function let()
{
$this->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');
}
}
1 change: 1 addition & 0 deletions src/Prophecy/Doubler/Generator/ClassMirror.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ private function reflectClassToNode(ReflectionClass $class, Node\ClassNode $node
}

if (true === $method->isFinal()) {
$node->addUnextendableMethod($method->getName());
continue;
}

Expand Down
36 changes: 36 additions & 0 deletions src/Prophecy/Doubler/Generator/Node/ClassNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Prophecy\Doubler\Generator\Node;

use Prophecy\Exception\Doubler\MethodNotExtendableException;
use Prophecy\Exception\InvalidArgumentException;

/**
Expand All @@ -23,6 +24,7 @@ class ClassNode
private $parentClass = 'stdClass';
private $interfaces = array();
private $properties = array();
private $unextendableMethods = array();

/**
* @var MethodNode[]
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
}
}
47 changes: 47 additions & 0 deletions src/Prophecy/Exception/Doubler/MethodNotExtendableException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php
/**
* Created by PhpStorm.
* User: jeff
* Date: 25/08/2015
* Time: 19:14
*/

namespace Prophecy\Exception\Doubler;

class MethodNotExtendableException extends DoubleException
{
private $methodName;

private $className;

/**
* @param string $message
* @param string $className
* @param string $methodName
*/
public function __construct($message, $className, $methodName)
{
parent::__construct($message);

$this->methodName = $methodName;
$this->className = $className;
}


/**
* @return string
*/
public function getMethodName()
{
return $this->methodName;
}

/**
* @return string
*/
public function getClassName()
{
return $this->className;
}

}

0 comments on commit a749c19

Please sign in to comment.