Skip to content

Commit

Permalink
Implemented ReflectionMethod::getClosure()
Browse files Browse the repository at this point in the history
  • Loading branch information
kukulich committed Sep 4, 2017
1 parent 8c61a3a commit 7514585
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 4 deletions.
2 changes: 1 addition & 1 deletion docs/compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@

| Method | Supported |
|--------|-----------|
| getClosure | :x: No - see ([#14](https://github.com/Roave/BetterReflection/issues/14)) |
| getClosure | :heavy_check_mark: Yes |
| getDeclaringClass | :heavy_check_mark: Yes |
| getModifiers | :heavy_check_mark: Yes |
| getPrototype | :heavy_check_mark: Yes |
Expand Down
14 changes: 12 additions & 2 deletions src/Reflection/Adapter/ReflectionMethod.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@

namespace Roave\BetterReflection\Reflection\Adapter;

use ReflectionException as CoreReflectionException;
use ReflectionMethod as CoreReflectionMethod;
use Roave\BetterReflection\Reflection\Exception\NoObjectProvided;
use Roave\BetterReflection\Reflection\Exception\NotAnObject;
use Roave\BetterReflection\Reflection\ReflectionMethod as BetterReflectionMethod;
use Throwable;

class ReflectionMethod extends CoreReflectionMethod
{
Expand Down Expand Up @@ -299,9 +303,15 @@ public function isDestructor()
/**
* {@inheritDoc}
*/
public function getClosure($object)
public function getClosure($object = null)
{
throw new Exception\NotImplemented('Not implemented');
try {
$this->betterReflectionMethod->getClosure($object);
} catch (NoObjectProvided | NotAnObject $e) {
return null;
} catch (Throwable $e) {
throw new CoreReflectionException($e->getMessage(), 0, $e);
}
}

/**
Expand Down
69 changes: 69 additions & 0 deletions src/Reflection/ReflectionMethod.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@

namespace Roave\BetterReflection\Reflection;

use Closure;
use PhpParser\Node\Stmt\ClassMethod as MethodNode;
use PhpParser\Node\Stmt\Namespace_;
use ReflectionMethod as CoreReflectionMethod;
use Roave\BetterReflection\Reflection\Exception\ClassDoesNotExist;
use Roave\BetterReflection\Reflection\Exception\NoObjectProvided;
use Roave\BetterReflection\Reflection\Exception\NotAnObject;
use Roave\BetterReflection\Reflection\Exception\ObjectNotInstanceOfClass;
use Roave\BetterReflection\Reflector\Reflector;
use RuntimeException;

Expand Down Expand Up @@ -294,4 +299,68 @@ public function getImplementingClass() : ReflectionClass
{
return $this->implementingClass;
}

/**
* @param object|null $object
*
* @return \Closure
*
* @throws ClassDoesNotExist
*/
public function getClosure($object = null) : Closure
{
$declaringClassName = $this->getDeclaringClass()->getName();

if ($this->isStatic()) {
$this->assertClassExist($declaringClassName);

return function (...$args) use ($declaringClassName) {
return Closure::bind(function (string $declaringClassName, string $methodName, array $methodArgs) {
return $declaringClassName::{$methodName}(...$methodArgs);
}, null, $declaringClassName)->__invoke($declaringClassName, $this->getName(), $args);
};
}

$this->assertObject($object);

return function (...$args) use ($declaringClassName, $object) {
return Closure::bind(function ($object, string $methodName, array $methodArgs) {
return $object->{$methodName}(...$methodArgs);
}, $object, $declaringClassName)->__invoke($object, $this->getName(), $args);
};
}

/**
* @throws ClassDoesNotExist
*/
private function assertClassExist(string $className) : void
{
if ( ! \class_exists($className, false)) {
throw new ClassDoesNotExist('Method cannot be used as the class is not loaded');
}
}

/**
* @param object $object
*
* @throws NoObjectProvided
* @throws NotAnObject
* @throws ObjectNotInstanceOfClass
*/
private function assertObject($object) : void
{
if (null === $object) {
throw NoObjectProvided::create();
}

if ( ! \is_object($object)) {
throw NotAnObject::fromNonObject($object);
}

$declaringClassName = $this->getDeclaringClass()->getName();

if (\get_class($object) !== $declaringClassName) {
throw ObjectNotInstanceOfClass::fromClassName($declaringClassName);
}
}
}
13 changes: 13 additions & 0 deletions test/unit/Fixture/ClassWithNonStaticMethod.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Roave\BetterReflectionTest\Fixture;

class ClassWithNonStaticMethod
{
private $constant = 100;

public function sum(int $a, int $b) : int
{
return $this->constant + $a + $b;
}
}
11 changes: 11 additions & 0 deletions test/unit/Fixture/ClassWithStaticMethod.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Roave\BetterReflectionTest\Fixture;

class ClassWithStaticMethod
{
public static function sum(int $a, int $b) : int
{
return $a + $b;
}
}
46 changes: 45 additions & 1 deletion test/unit/Reflection/Adapter/ReflectionMethodTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@
use Exception;
use PHPUnit\Framework\TestCase;
use ReflectionClass as CoreReflectionClass;
use ReflectionException as CoreReflectionException;
use ReflectionMethod as CoreReflectionMethod;
use Roave\BetterReflection\Reflection\Adapter\Exception\NotImplemented;
use Roave\BetterReflection\Reflection\Adapter\ReflectionClass as ReflectionClassAdapter;
use Roave\BetterReflection\Reflection\Adapter\ReflectionMethod as ReflectionMethodAdapter;
use Roave\BetterReflection\Reflection\Exception\NoObjectProvided;
use Roave\BetterReflection\Reflection\Exception\NotAnObject;
use Roave\BetterReflection\Reflection\Exception\ObjectNotInstanceOfClass;
use Roave\BetterReflection\Reflection\ReflectionClass as BetterReflectionClass;
use Roave\BetterReflection\Reflection\ReflectionMethod as BetterReflectionMethod;
use Roave\BetterReflection\Reflection\ReflectionParameter as BetterReflectionParameter;
Expand Down Expand Up @@ -47,6 +51,9 @@ public function methodExpectationProvider() : array

$mockType = $this->createMock(BetterReflectionType::class);

$closure = function () : void {
};

return [
// Inherited
['__toString', null, '', []],
Expand Down Expand Up @@ -84,7 +91,7 @@ public function methodExpectationProvider() : array
['isStatic', null, true, []],
['isConstructor', null, true, []],
['isDestructor', null, true, []],
['getClosure', NotImplemented::class, null, [new stdClass()]],
['getClosure', null, $closure, []],
['getModifiers', null, 123, []],
['invoke', NotImplemented::class, null, [new stdClass(), '']],
['invokeArgs', NotImplemented::class, null, [new stdClass(), []]],
Expand Down Expand Up @@ -168,4 +175,41 @@ public function testGetDeclaringClass() : void
self::assertInstanceOf(ReflectionClassAdapter::class, $reflectionMethodAdapter->getDeclaringClass());
self::assertSame('DeclaringClass', $reflectionMethodAdapter->getDeclaringClass()->getName());
}

public function testGetClosureReturnsNullWhenNoObject() : void
{
$betterReflectionMethod = $this->createMock(BetterReflectionMethod::class);
$betterReflectionMethod
->method('getClosure')
->willThrowException(NoObjectProvided::create());

$reflectionMethodAdapter = new ReflectionMethodAdapter($betterReflectionMethod);

$this->assertNull($reflectionMethodAdapter->getClosure());
}

public function testGetClosureReturnsNullWhenNotAnObject() : void
{
$betterReflectionMethod = $this->createMock(BetterReflectionMethod::class);
$betterReflectionMethod
->method('getClosure')
->willThrowException(NotAnObject::fromNonObject('string'));

$reflectionMethodAdapter = new ReflectionMethodAdapter($betterReflectionMethod);

$this->assertNull($reflectionMethodAdapter->getClosure('string'));
}

public function testGetClosureThrowsExceptionWhenObjectNotInstanceOfClass() : void
{
$betterReflectionMethod = $this->createMock(BetterReflectionMethod::class);
$betterReflectionMethod
->method('getClosure')
->willThrowException(ObjectNotInstanceOfClass::fromClassName('Foo'));

$reflectionMethodAdapter = new ReflectionMethodAdapter($betterReflectionMethod);

$this->expectException(CoreReflectionException::class);
$reflectionMethodAdapter->getClosure(new stdClass());
}
}
88 changes: 88 additions & 0 deletions test/unit/Reflection/ReflectionMethodTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

use A\Foo;
use ClassWithMethodsAndTraitMethods;
use Closure;
use ExtendedClassWithMethodsAndTraitMethods;
use Php4StyleCaseInsensitiveConstruct;
use Php4StyleConstruct;
Expand All @@ -15,7 +16,11 @@
use ReflectionClass;
use ReflectionMethod as CoreReflectionMethod;
use Reflector;
use Roave\BetterReflection\Reflection\Exception\ClassDoesNotExist;
use Roave\BetterReflection\Reflection\Exception\MethodPrototypeNotFound;
use Roave\BetterReflection\Reflection\Exception\NoObjectProvided;
use Roave\BetterReflection\Reflection\Exception\NotAnObject;
use Roave\BetterReflection\Reflection\Exception\ObjectNotInstanceOfClass;
use Roave\BetterReflection\Reflection\ReflectionFunctionAbstract;
use Roave\BetterReflection\Reflection\ReflectionMethod;
use Roave\BetterReflection\Reflection\ReflectionParameter;
Expand All @@ -26,12 +31,15 @@
use Roave\BetterReflection\SourceLocator\Type\SingleFileSourceLocator;
use Roave\BetterReflection\SourceLocator\Type\StringSourceLocator;
use Roave\BetterReflectionTest\BetterReflectionSingleton;
use Roave\BetterReflectionTest\Fixture\ClassWithNonStaticMethod;
use Roave\BetterReflectionTest\Fixture\ClassWithStaticMethod;
use Roave\BetterReflectionTest\Fixture\ExampleClass;
use Roave\BetterReflectionTest\Fixture\Methods;
use Roave\BetterReflectionTest\Fixture\Php4StyleConstructInNamespace;
use Roave\BetterReflectionTest\Fixture\UpperCaseConstructDestruct;
use RuntimeException;
use SplDoublyLinkedList;
use stdClass;
use TraitWithMethod;

/**
Expand Down Expand Up @@ -426,4 +434,84 @@ public function testGetDeclaringAndImplementingClassWithMethodFromParentClass()
self::assertSame(ClassWithMethodsAndTraitMethods::class, $methodReflection->getImplementingClass()->getName());
self::assertSame($methodReflection->getDeclaringClass(), $methodReflection->getImplementingClass());
}

public function testGetClosureOfStaticMethodThrowsExceptionWhenClassDoesNotExist() : void
{
$php = <<<'PHP'
<?php
class Foo
{
public static function boo()
{
}
}
PHP;

$this->expectException(ClassDoesNotExist::class);

$classReflection = (new ClassReflector(new StringSourceLocator($php, $this->astLocator)))->reflect('Foo');
$methodReflection = $classReflection->getMethod('boo');

$methodReflection->getClosure();
}

public function testGetClosureOfStaticMethod() : void
{
$classWithStaticMethodFile = __DIR__ . '/../Fixture/ClassWithStaticMethod.php';
require_once $classWithStaticMethodFile;

$classReflection = (new ClassReflector(new SingleFileSourceLocator($classWithStaticMethodFile, $this->astLocator)))->reflect(ClassWithStaticMethod::class);
$methodReflection = $classReflection->getMethod('sum');

$closure = $methodReflection->getClosure();

self::assertInstanceOf(Closure::class, $closure);
self::assertSame(3, $closure(1, 2));
}

public function testGetClosureOfObjectMethodThrowsExceptionWhenNoObject() : void
{
$this->expectException(NoObjectProvided::class);

$classReflection = (new ClassReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/ClassWithNonStaticMethod.php', $this->astLocator)))->reflect(ClassWithNonStaticMethod::class);
$methodReflection = $classReflection->getMethod('sum');

$methodReflection->getClosure(null);
}

public function testGetClosureOfObjectMethodThrowsExceptionWhenObjectNotAnObject() : void
{
$this->expectException(NotAnObject::class);

$classReflection = (new ClassReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/ClassWithNonStaticMethod.php', $this->astLocator)))->reflect(ClassWithNonStaticMethod::class);
$methodReflection = $classReflection->getMethod('sum');

$methodReflection->getClosure(123);
}

public function testGetClosureOfObjectMethodThrowsExceptionWhenObjectNotInstanceOfClass() : void
{
$this->expectException(ObjectNotInstanceOfClass::class);

$classReflection = (new ClassReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/ClassWithNonStaticMethod.php', $this->astLocator)))->reflect(ClassWithNonStaticMethod::class);
$methodReflection = $classReflection->getMethod('sum');

$methodReflection->getClosure(new stdClass());
}

public function testGetClosureOfObjectMethod() : void
{
$classWithNonStaticMethodFile = __DIR__ . '/../Fixture/ClassWithNonStaticMethod.php';
require_once $classWithNonStaticMethodFile;

$classReflection = (new ClassReflector(new SingleFileSourceLocator($classWithNonStaticMethodFile, $this->astLocator)))->reflect(ClassWithNonStaticMethod::class);
$methodReflection = $classReflection->getMethod('sum');

$object = new ClassWithNonStaticMethod();

$closure = $methodReflection->getClosure($object);

self::assertInstanceOf(Closure::class, $closure);
self::assertSame(103, $closure(1, 2));
}
}

0 comments on commit 7514585

Please sign in to comment.