Skip to content

Commit

Permalink
feat(callOriginal): add way to call original methods/functions
Browse files Browse the repository at this point in the history
use:
 - `callOriginal` for dynamic class methods and functions
 - `callOriginalStatic` for static class methods
  • Loading branch information
rtm-ctrlz committed Nov 14, 2021
1 parent 9ac035a commit 561376e
Show file tree
Hide file tree
Showing 15 changed files with 396 additions and 80 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ ij_smart_tabs = false
ij_wrap_on_typing = false
ij_visual_guides = 100, 120

[phpstan.neon]
[{phpstan.neon,phpstan.neon.dist}]
ij_visual_guides = none
indent_size = 2

Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
}
},
"require-dev": {
"squizlabs/php_codesniffer": "^3.5",
"phpstan/phpstan": "^0.12.65"
"squizlabs/php_codesniffer": "^3.6",
"phpstan/phpstan": "^1.1.2"
},
"autoload-dev": {
"psr-4": {
Expand Down
7 changes: 7 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,10 @@ parameters:
- ./test
bootstrapFiles:
- ./test/phpstan/runkit7.stub.php
ignoreErrors:
- message: '/Parameter #2 \$code of class ReflectionException constructor expects int, mixed given\./'
paths:
- ./src/ClassMethod/UndefinedClassMethod.php
- ./src/Constant/MockedClassConstant.php
- ./src/Constant/UndefinedClassConstant.php
- ./src/Functions/UndefinedFunction.php
4 changes: 2 additions & 2 deletions src/ClassMethod/BaseClassMethod.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ abstract class BaseClassMethod extends MockedEntity
/**
* BaseClassMethod constructor.
*
* @param string $class
* @param string $method
* @param string $class
* @param string $method
*
* @phpstan-param class-string $class
* @noinspection PhpUndefinedClassInspection
Expand Down
65 changes: 54 additions & 11 deletions src/ClassMethod/MockedClassMethod.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Closure;
use ReflectionException;
use ReflectionMethod;
use RuntimeException;

use function get_declared_classes;
use function get_parent_class;
Expand All @@ -20,9 +21,6 @@
class MockedClassMethod extends UndefinedClassMethod
{

/** @var Closure */
protected $closure;

/**
* @var array<string, self>
* @phpstan-var array<class-string, self>
Expand All @@ -42,19 +40,18 @@ class MockedClassMethod extends UndefinedClassMethod
*/
public function __construct(string $class, string $method, Closure $closure = null)
{
$this->closure =
$closure ??
static function (): void {
};

parent::__construct($class, $method);

$reflection = new ReflectionMethod($this->class, $this->stashedName);
$flags = $reflection->isStatic() ? RUNKIT7_ACC_STATIC : 0;

$flags |= $this->getVisibility($reflection);

$closure = $this->closure;
$closureUse =
$closure ??
static function (): void {
};

runkit7_method_add(
$this->class,
$this->method,
Expand All @@ -63,15 +60,61 @@ static function (): void {
*
* @return mixed
*/
static function (...$args) use ($closure) {
return $closure(...$args);
static function (...$args) use ($closureUse) {
return $closureUse(...$args);
},
$flags
);

$this->stubChildrenMethods();
}

/**
* @param object|null $object
* @param mixed ...$args
*
* @return mixed
* @throws ReflectionException
*/
public function callOriginal($object, ...$args)
{
if (!($object instanceof $this->class)) {
throw new RuntimeException('Object of "' . $this->class . '" expected.');
}
$ref = (new ReflectionMethod($this->class, $this->stashedName));
if ($ref->isStatic()) {
throw new RuntimeException(
'Method "' . $this->class . '::' . $this->method . '" is static and cannot be called dynamically.'
);
}
if (!$ref->isPublic()) {
$ref->setAccessible(true);
}

return $ref->invoke($object, ...$args);
}

/**
* @param mixed ...$args
*
* @return mixed
* @throws ReflectionException
*/
public function callOriginalStatic(...$args)
{
$ref = (new ReflectionMethod($this->class, $this->stashedName));
if (!$ref->isStatic()) {
throw new RuntimeException(
'Method "' . $this->class . '::' . $this->method . '" is dynamic and cannot be called statically.'
);
}
if (!$ref->isPublic()) {
$ref->setAccessible(true);
}

return $ref->invoke(null, ...$args);
}

public function __destruct()
{
runkit7_method_remove($this->class, $this->method);
Expand Down
17 changes: 13 additions & 4 deletions src/Functions/MockedFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,20 @@ static function (...$args) use ($closure) {
runkit7_function_rename($tmpName, $this->getFullName());
}

/**
* @param mixed ...$args
*
* @return mixed
*/
public function callOriginal(...$args)
{
/** @phpstan-ignore-next-line */
return ($this->stashedName)(...$args);
}

public function __destruct()
{
if (isset($this->stashedName)) {
runkit7_function_remove($this->getFullName());
parent::__destruct();
}
runkit7_function_remove($this->getFullName());
parent::__destruct();
}
}
4 changes: 1 addition & 3 deletions src/Functions/UndefinedFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ public function __construct(string $function)

public function __destruct()
{
if (isset($this->stashedName)) {
runkit7_function_rename($this->stashedName, $this->getFullName());
}
runkit7_function_rename($this->stashedName, $this->getFullName());
}
}
10 changes: 10 additions & 0 deletions src/Phpunit/MockedFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ static function (...$args) use (&$mockObject, &$method) {
}
}

/**
* @param mixed ...$args
*
* @return mixed
*/
public function callOriginal(...$args)
{
return $this->mockedFunction->callOriginal(...$args);
}

public function getMocker(): InvocationMocker
{
return $this->invocationMocker;
Expand Down
51 changes: 40 additions & 11 deletions src/Phpunit/MockedMethod.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@

class MockedMethod
{
/** @var MockedClassMethod */
/**
* @var MockedClassMethod
*
* It is safe to ignore "only write" error
* @phpstan-ignore-next-line
*/
private $mockedMethod;

/** @var InvocationMocker */
Expand Down Expand Up @@ -42,21 +47,22 @@ public function __construct(
string $method,
InvocationOrder $invocationRule = null
) {
$mockObject = $testCase->getMockBuilder(EmptyClass::class)
->disableOriginalConstructor()
->disableOriginalClone()
->disableArgumentCloning()
->disallowMockingUnknownTypes()
->addMethods([$method])
->getMock();
$this->mockObject = $testCase
->getMockBuilder(EmptyClass::class)
->disableOriginalConstructor()
->disableOriginalClone()
->disableArgumentCloning()
->disallowMockingUnknownTypes()
->addMethods([$method])
->getMock();

$this->mockObject = $mockObject;
if ($invocationRule === null) {
$this->invocationMocker = $mockObject->method($method);
$this->invocationMocker = $this->mockObject->method($method);
} else {
$this->invocationMocker = $mockObject->expects($invocationRule)->method($method);
$this->invocationMocker = $this->mockObject->expects($invocationRule)->method($method);
}

$mockObject = $this->mockObject;
$this->mockedMethod = new MockedClassMethod(
$class,
$method,
Expand All @@ -71,6 +77,29 @@ static function (...$args) use ($mockObject, $method) {
);
}

/**
* @param object $object
* @param mixed ...$args
*
* @return void
* @throws ReflectionException
*/
public function callOriginal($object, ...$args)
{
$this->mockedMethod->callOriginal($object, ...$args);
}

/**
* @param mixed ...$args
*
* @return void
* @throws ReflectionException
*/
public function callOriginalStatic(...$args)
{
$this->mockedMethod->callOriginalStatic(...$args);
}

public function getMocker(): InvocationMocker
{
return $this->invocationMocker;
Expand Down
Loading

0 comments on commit 561376e

Please sign in to comment.