Skip to content

Commit

Permalink
Merge pull request #40 from tomaszdurka/issue-40
Browse files Browse the repository at this point in the history
Readd support for late static binding
  • Loading branch information
tomaszdurka committed Oct 13, 2016
2 parents ce6e3cf + 5cc07b1 commit ef0c76f
Show file tree
Hide file tree
Showing 11 changed files with 113 additions and 89 deletions.
28 changes: 9 additions & 19 deletions source/Mocka/Classes/ClassDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,35 +21,25 @@ public function __construct($className) {

/**
* @param string $methodName
* @return AbstractInvokable|null
* @return Callable|null
*/
public function findOriginalMethod($methodName) {
$method = $this->findOriginalMethodReflection($methodName);
if ($method) {
if ($method->isAbstract()) {
return new Stub();
} else {
return new Spy($method);
}
}
return null;
}

/**
* @param string $methodName
* @return \ReflectionMethod|null
*/
public function findOriginalMethodReflection($methodName) {
$class = new \ReflectionClass($this->_className);

$traitAliasName = '_mockaTraitAlias_' . $methodName;
if ($class->hasMethod($traitAliasName)) {
return $class->getMethod($traitAliasName);
if ($class->getMethod($traitAliasName)->isAbstract()) {
return function () {};
}
return ['self', $traitAliasName];
}

$parentClass = $this->_findOriginalParentClass($class);
if ($parentClass && $parentClass->hasMethod($methodName)) {
return $parentClass->getMethod($methodName);
if ($parentClass->getMethod($methodName)->isAbstract()) {
return function () {};
}
return ['parent', $methodName];
}

return null;
Expand Down
59 changes: 44 additions & 15 deletions source/Mocka/Classes/ClassMockTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
namespace Mocka\Classes;

use Mocka\Exception;
use Mocka\Invokables\Invokable\AbstractInvokable;
use Mocka\Invokables\Invokable\Spy;
use Mocka\Invokables\Invokable\Stub;
use Mocka\Overrides\MethodOverrides\ClassOverrides;
use Mocka\Overrides\MethodOverrides\InstanceOverrides;
use Mocka\Overrides\Manager;
Expand All @@ -20,7 +21,7 @@ public function getOverrides() {

/**
* @param string $name
* @return AbstractInvokable
* @return Stub
* @deprecated
*/
public function mockMethod($name) {
Expand All @@ -36,26 +37,41 @@ public function unmockMethod($name) {
}

/**
* @param string $name
* @param array $arguments
* @param string $name
* @param array $arguments
* @return mixed
* @throws Exception
*/
private function _callMethod($name, array $arguments) {
$classDefinition = new ClassDefinition(__CLASS__);
$originalMethod = $classDefinition->findOriginalMethod($name);

$override = $this->getOverrides()->find($name);


if ($override) {
return $override->getInvokable()->invoke($this, $arguments);
$invokable = $override->getInvokable();
if ($invokable instanceof Spy) {
$returnValue = null;
if ($originalMethod) {
$returnValue = call_user_func_array($originalMethod, $arguments);
}
$invokable->addInvocation($this, $arguments, $returnValue);
return $returnValue;
}
if ($invokable instanceof Stub) {
return $invokable->invoke($this, $arguments);
}
throw new Exception('Unsupported invokable');
}
$classDefinition = new ClassDefinition(__CLASS__);
$originalMethod = $classDefinition->findOriginalMethod($name);
if ($originalMethod) {
return $originalMethod->invoke($this, $arguments);
return call_user_func_array($originalMethod, $arguments);
}

if ('__construct' === $name) {
return null;
}

throw new Exception('Cannot find method');
throw new Exception("Cannot find method {$name}");
}

/**
Expand All @@ -65,17 +81,30 @@ private function _callMethod($name, array $arguments) {
* @throws Exception
*/
private static function _callStaticMethod($name, array $arguments) {
$classDefinition = new ClassDefinition(get_called_class());
$originalMethod = $classDefinition->findOriginalMethod($name);

$manager = Manager::getInstance();
$classOverrides = new ClassOverrides($manager, get_called_class());

$override = $classOverrides->find($name);
if ($override) {
return $override->getInvokable()->invoke(null, $arguments);
$invokable = $override->getInvokable();
if ($invokable instanceof Spy) {
$returnValue = null;
if ($originalMethod) {
$returnValue = call_user_func_array($originalMethod, $arguments);
}
$invokable->addInvocation(null, $arguments, $returnValue);
return $returnValue;
}
if ($invokable instanceof Stub) {
return $invokable->invoke(null, $arguments);
}
throw new Exception('Unsupported invokable');
}

$classDefinition = new ClassDefinition(get_called_class());
$originalMethod = $classDefinition->findOriginalMethod($name);
if ($originalMethod) {
return $originalMethod->invoke(null, $arguments);
return call_user_func_array($originalMethod, $arguments);
}
throw new Exception('Cannot find method');
}
Expand Down
17 changes: 0 additions & 17 deletions source/Mocka/Invokables/Invokable/AbstractInvokable.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,10 @@ abstract class AbstractInvokable {
/** @var Invocations */
private $_invocations;

/**
* @param Invocation $invocation
*/
abstract protected function _invoke(Invocation $invocation);

public function __construct() {
$this->_invocations = new Invocations();
}

/**
* @param mixed|null $context
* @param array $arguments
* @return mixed|null
*/
public function invoke($context, array $arguments) {
$invocation = new Invocation($context, $arguments);
$this->_invoke($invocation);
$this->getInvocations()->add($invocation);
return $invocation->getReturnValue();
}

/**
* @return Invocations
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

namespace Mocka\Invokable\Invokable;

interface OriginalMethodWrapperInterface {};
30 changes: 7 additions & 23 deletions source/Mocka/Invokables/Invokable/Spy.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,14 @@

class Spy extends AbstractInvokable {

/** @var \ReflectionFunctionAbstract */
private $_method;

/**
* @param ReflectionFunctionAbstract $method
*/
public function __construct(ReflectionFunctionAbstract $method) {
$this->_method = $method;
parent::__construct();
}

/**
* @param Invocation $invocation
* @throws Exception
* @param mixed $context
* @param array $arguments
* @param mixed $returnValue
*/
protected function _invoke(Invocation $invocation) {
if ($this->_method instanceof \ReflectionMethod) {
$this->_method->setAccessible(true);
$result = $this->_method->invokeArgs($invocation->getContext(), $invocation->getArguments());
} elseif ($this->_method instanceof \ReflectionFunction) {
$result = $this->_method->invokeArgs($invocation->getArguments());
} else {
throw new Exception('Fatal error, method is not a ReflectionFunctionAbstract');
}
$invocation->setReturnValue($result);
public function addInvocation($context, $arguments, $returnValue) {
$invocation = new Invocation($context, $arguments);
$invocation->setReturnValue($returnValue);
$this->getInvocations()->add($invocation);
}
}
14 changes: 10 additions & 4 deletions source/Mocka/Invokables/Invokable/Stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function set($body) {
}

/**
* @param int|int[] $at
* @param int|int[] $at
* @param string|\Closure $body
* @return static
*/
Expand All @@ -43,11 +43,18 @@ public function at($at, $body) {
return $this;
}

protected function _invoke(Invocation $invocation) {
$arguments = $invocation->getArguments();
/**
* @param mixed|null $context
* @param array $arguments
* @return mixed|null
*/
public function invoke($context, array $arguments) {
$invocation = new Invocation($context, $arguments);
$closure = $this->_getClosure($this->getInvocations()->getCount());
$result = call_user_func_array($closure, $arguments);
$invocation->setReturnValue($result);
$this->getInvocations()->add($invocation);
return $result;
}

/**
Expand Down Expand Up @@ -75,5 +82,4 @@ protected function _normalizeBody($body) {
}
return $closure;
}

}
9 changes: 3 additions & 6 deletions source/Mocka/Overrides/MethodOverrides/AbstractOverrides.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ public function __construct(Manager $manager) {

/**
* @param string $methodName
* @return Override
* @return \Mocka\Invokables\Invokable\AbstractInvokable
* @throws Exception
*/
public function get($methodName) {
$override = $this->find($methodName);
if (!$override) {
throw new Exception('Override not found');
}
return $override;
return $override->getInvokable();
}

/**
Expand Down Expand Up @@ -77,10 +77,7 @@ public function spy($methodName) {
$context = $this->_createContext($methodName);
$this->_manager->removeByContext($context);

$classDefinition = new ClassDefinition(get_parent_class($context->getClassName()));
$originalMethodReflection = $classDefinition->findOriginalMethodReflection($methodName);
$invokable = new Spy($originalMethodReflection);

$invokable = new Spy();
$override = new Override($context, $invokable);
$this->_manager->add($override);
return $invokable;
Expand Down
6 changes: 5 additions & 1 deletion tests/mocks/AbstractClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@ public function __construct($arg1, $arg2 = null) {
}

public function bar() {
return 'bar';
return static::jar();
}

public final function zoo() {
}

public function getCalledClass() {
return get_called_class();
}

protected function _foo(){
}

Expand Down
28 changes: 25 additions & 3 deletions tests/source/Mocka/ClassMockTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public function testMockMethod() {
/** @var ClassMockTrait|AbstractClass $object */
$object = $classMock->newInstanceWithoutConstructor();

$this->assertSame('bar', $object->bar());
$this->assertSame('jar', $object->bar());

$classMock->mockMethod('bar')->set(function () {
return 'foo';
Expand All @@ -47,13 +47,13 @@ public function testUnmockMethod() {
/** @var ClassMockTrait|AbstractClass $object */
$object = $classMock->newInstanceWithoutConstructor();

$this->assertSame('bar', $object->bar());
$this->assertSame('jar', $object->bar());

$classMock->mockMethod('bar');
$this->assertSame(null, $object->bar());

$classMock->unmockMethod('bar');
$this->assertSame('bar', $object->bar());
$this->assertSame('jar', $object->bar());
}

public function testMockMethodFromTrait() {
Expand All @@ -68,6 +68,15 @@ public function testMockMethodFromTrait() {
$this->assertSame(null, $object->bar());
}

public function testGetCalledClass() {
$factory = new ClassMockFactory();
$classMock = $factory->loadClassMock(null, '\\MockaMocks\\AbstractClass');
/** @var AbstractClass $object */
$object = $classMock->newInstanceWithoutConstructor();

$this->assertSame($classMock->getClassName(), $object->getCalledClass());
}

public function testMockStaticMethod() {
$factory = new ClassMockFactory();
$classMock = $factory->loadClassMock(null, '\\MockaMocks\\AbstractClass');
Expand All @@ -80,6 +89,19 @@ public function testMockStaticMethod() {
$this->assertSame('foo', $className::jar());
}

public function testMockStaticMethodCalledFromOther() {
$factory = new ClassMockFactory();
$classMock = $factory->loadClassMock(null, '\\MockaMocks\\AbstractClass');
/** @var AbstractClass $object */
$object = $classMock->newInstanceWithoutConstructor();

$this->assertSame('jar', $object->bar());
$classMock->mockMethod('jar')->set(function () {
return 'foo';
});
$this->assertSame('foo', $object->bar());
}

public function testNewInstanceConstructorArgs() {
$factory = new ClassMockFactory();
$classMock = $factory->loadClassMock(null, '\\MockaMocks\\AbstractClass');
Expand Down
2 changes: 1 addition & 1 deletion tests/source/Mocka/ClassTraitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public function testCallMockedMethod() {
/** @var \MockaMocks\AbstractClass|\Mocka\Classes\ClassMockTrait $object */
$object = $mockClass->newInstanceWithoutConstructor();
$this->assertNull($object->foo());
$this->assertSame('bar', $object->bar());
$this->assertSame('jar', $object->bar());

$mockClass->mockMethod('foo')->set(function () {
return 'foo';
Expand Down
4 changes: 4 additions & 0 deletions tests/source/Mocka/ClassWrapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ public function bar() {
return \$this->_callMethod(__FUNCTION__, func_get_args());
}
public function getCalledClass() {
return \$this->_callMethod(__FUNCTION__, func_get_args());
}
protected function _foo() {
return \$this->_callMethod(__FUNCTION__, func_get_args());
}
Expand Down

0 comments on commit ef0c76f

Please sign in to comment.