Skip to content

Commit

Permalink
Check if handler method exists in MethodToCall
Browse files Browse the repository at this point in the history
By doing this in our value object instead of our middleware, we make
this more reusable for custom scenarios AND we simplify the middleware,
which should help encourage folks to write their own custom ones
instead.
  • Loading branch information
rosstuck committed Sep 16, 2020
1 parent fba07e3 commit b69f81f
Show file tree
Hide file tree
Showing 11 changed files with 105 additions and 147 deletions.
50 changes: 0 additions & 50 deletions src/Handler/CanNotInvokeHandler.php

This file was deleted.

12 changes: 2 additions & 10 deletions src/Handler/CommandHandlerMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace League\Tactician\Handler;

use League\Tactician\Handler\Mapping\CommandToHandlerMapping;
use League\Tactician\Handler\Mapping\MethodDoesNotExist;
use League\Tactician\Middleware;
use Psr\Container\ContainerInterface;
use function get_class;
Expand All @@ -30,7 +31,7 @@ public function __construct(ContainerInterface $container, CommandToHandlerMappi
* Executes a command and optionally returns a value
*
* @return mixed
* @throws CanNotInvokeHandler
* @throws MethodDoesNotExist
*/
public function execute(object $command, callable $next)
{
Expand All @@ -39,15 +40,6 @@ public function execute(object $command, callable $next)
$handler = $this->container->get($handlerMethod->getClassName());
$methodName = $handlerMethod->getMethodName();

// is_callable is used here instead of method_exists, as method_exists
// will fail when given a handler that relies on __call.
if (! \is_callable([$handler, $methodName])) {
throw CanNotInvokeHandler::forCommand(
$command,
'Method ' . $methodName . ' does not exist on handler'
);
}

return $handler->{$methodName}($command);
}
}
28 changes: 28 additions & 0 deletions src/Handler/Mapping/MethodDoesNotExist.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace League\Tactician\Handler\Mapping;

use BadMethodCallException;
use League\Tactician\Exception;

/**
* Thrown when a specific handler object can not be used on a command object.
*
* The most common reason is the receiving method is missing or incorrectly
* named.
*/
final class MethodDoesNotExist extends BadMethodCallException implements Exception
{
/** @var object */
private $command;

public static function on(string $className, string $methodName): self
{
return new self(
"The handler method {$className}::{$methodName} does not exist. Please check your Tactician mapping " .
"configuration or check verify that {$methodName} is actually declared in {$className}"
);
}
}
9 changes: 8 additions & 1 deletion src/Handler/Mapping/MethodToCall.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace League\Tactician\Handler\Mapping;

use League\Tactician\Handler\CanNotInvokeHandler;
use function method_exists;

final class MethodToCall
{
Expand All @@ -15,6 +15,13 @@ final class MethodToCall

public function __construct(string $className, string $methodName)
{
// If the method does not actually exist, we'll also check if __call exists (mainly for
// legacy purposes). That said, we won't rewrite the method name to __call because our
// static analysis checker might still be able to infer data from the original method name.
if (!method_exists($className, $methodName) && !method_exists($className, '__call')) {
throw MethodDoesNotExist::on($className, $methodName);
}

$this->className = $className;
$this->methodName = $methodName;
}
Expand Down
12 changes: 0 additions & 12 deletions tests/Fixtures/Handler/DynamicMethodsHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,10 @@
*/
class DynamicMethodsHandler
{
/** @var string[] */
private $methods = [];

/**
* @return string[]
*/
public function getMethodsInvoked() : array
{
return $this->methods;
}

/**
* @param mixed[] $args
*/
public function __call(string $methodName, array $args) : void
{
$this->methods[] = $methodName;
}
}
32 changes: 0 additions & 32 deletions tests/Handler/CanNotInvokeHandlerExceptionTest.php

This file was deleted.

38 changes: 1 addition & 37 deletions tests/Handler/CommandHandlerMiddlewareTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace League\Tactician\Tests\Handler;

use League\Tactician\Handler\CanNotInvokeHandler;
use League\Tactician\Handler\Mapping\MethodDoesNotExist;
use League\Tactician\Handler\CommandHandlerMiddleware;
use League\Tactician\Handler\Mapping\CommandToHandlerMapping;
use League\Tactician\Handler\Mapping\MethodToCall;
Expand Down Expand Up @@ -58,42 +58,6 @@ public function testHandlerIsExecuted(): void
self::assertEquals('a-return-value', $this->middleware->execute($command, $this->mockNext()));
}

public function testMissingMethodOnHandlerObjectIsDetected(): void
{
$command = new CompleteTaskCommand();

$this->container
->method('get')
->willReturn(new stdClass());

$this->mapping
->method('mapCommandToHandler')
->with(CompleteTaskCommand::class)
->willReturn(new MethodToCall(CompleteTaskCommand::class, 'someMethodThatDoesNotExist'));

$this->expectException(CanNotInvokeHandler::class);
$this->middleware->execute($command, $this->mockNext());
}

public function testDynamicMethodNamesAreSupported(): void
{
$command = new CompleteTaskCommand();
$handler = new DynamicMethodsHandler();

$this->container
->method('get')
->willReturn($handler);

$this->mapping
->method('mapCommandToHandler')
->with(CompleteTaskCommand::class)
->willReturn(new MethodToCall(CompleteTaskCommand::class, 'someHandlerMethod'));

$this->middleware->execute($command, $this->mockNext());

self::assertEquals(['someHandlerMethod'], $handler->getMethodsInvoked());
}

protected function mockNext(): callable
{
return static function (): void {
Expand Down
6 changes: 3 additions & 3 deletions tests/Handler/Mapping/MappingByNamingConventionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use League\Tactician\Handler\Mapping\MapByNamingConvention;
use League\Tactician\Handler\Mapping\MethodName\MethodNameInflector;
use League\Tactician\Tests\Fixtures\Command\AddTaskCommand;
use League\Tactician\Tests\Fixtures\Handler\ConcreteMethodsHandler;
use League\Tactician\Tests\Fixtures\Handler\HandleMethodHandler;
use PHPUnit\Framework\TestCase;

/**
Expand All @@ -26,7 +26,7 @@ public function testMethodsAreDelegatedProperly(): void
->expects(self::once())
->method('getClassName')
->with(AddTaskCommand::class)
->willReturn(ConcreteMethodsHandler::class);
->willReturn(HandleMethodHandler::class);

$methodName
->expects(self::once())
Expand All @@ -36,7 +36,7 @@ public function testMethodsAreDelegatedProperly(): void

$handler = $mapping->mapCommandToHandler(AddTaskCommand::class);

self::assertEquals(ConcreteMethodsHandler::class, $handler->getClassName());
self::assertEquals(HandleMethodHandler::class, $handler->getClassName());
self::assertEquals('handle', $handler->getMethodName());
}
}
4 changes: 2 additions & 2 deletions tests/Handler/Mapping/MappingByStaticListTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ public function testSuccessfulMapping(): void
{
$mapping = new MapByStaticList(
[
AddTaskCommand::class => [ConcreteMethodsHandler::class, 'handle'],
AddTaskCommand::class => [ConcreteMethodsHandler::class, 'handleTaskCompletedCommand'],
]
);

$handler = $mapping->mapCommandToHandler(AddTaskCommand::class);
static::assertEquals(ConcreteMethodsHandler::class, $handler->getClassName());
static::assertEquals('handle', $handler->getMethodName());
static::assertEquals('handleTaskCompletedCommand', $handler->getMethodName());
}

public function testFailedMappingCommandToMethod(): void
Expand Down
25 changes: 25 additions & 0 deletions tests/Handler/Mapping/MethodDoesNotExistTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace League\Tactician\Tests\Handler\Mapping;

use League\Tactician\Exception;
use League\Tactician\Handler\Mapping\MethodDoesNotExist;
use League\Tactician\Tests\Fixtures\Handler\ConcreteMethodsHandler;
use PHPUnit\Framework\TestCase;

/**
* @covers \League\Tactician\Handler\Mapping\MethodDoesNotExist
*/
class MethodDoesNotExistTest extends TestCase
{
public function testExceptionContainsDebuggingInfo() : void
{
$exception = MethodDoesNotExist::on(ConcreteMethodsHandler::class, 'handleTaskCompletedCommand');

self::assertInstanceOf(Exception::class, $exception);
self::assertStringContainsString(ConcreteMethodsHandler::class, $exception->getMessage());
self::assertStringContainsString('handleTaskCompletedCommand', $exception->getMessage());
}
}
36 changes: 36 additions & 0 deletions tests/Handler/Mapping/MethodToCallTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);

namespace League\Tactician\Tests\Handler\Mapping;

use League\Tactician\Handler\Mapping\MethodDoesNotExist;
use League\Tactician\Handler\Mapping\MethodToCall;
use League\Tactician\Tests\Fixtures\Command\CompleteTaskCommand;
use League\Tactician\Tests\Fixtures\Handler\ConcreteMethodsHandler;
use League\Tactician\Tests\Fixtures\Handler\DynamicMethodsHandler;
use PHPUnit\Framework\TestCase;

final class MethodToCallTest extends TestCase
{
public function testBoringGettersAndSetters(): void
{
$methodToCall = new MethodToCall(ConcreteMethodsHandler::class, 'handleTaskCompletedCommand');

self::assertEquals(ConcreteMethodsHandler::class, $methodToCall->getClassName());
self::assertEquals('handleTaskCompletedCommand', $methodToCall->getMethodName());
}

public function testMissingMethodOnHandlerObjectIsDetected(): void
{
$this->expectException(MethodDoesNotExist::class);
new MethodToCall(CompleteTaskCommand::class, 'someMethodThatDoesNotExist');
}

public function testDynamicMethodNamesAreSupported(): void
{
$methodToCall = new MethodToCall(DynamicMethodsHandler::class, 'someMethodThatDoesNotExist');

self::assertEquals(DynamicMethodsHandler::class, $methodToCall->getClassName());
self::assertEquals('someMethodThatDoesNotExist', $methodToCall->getMethodName());
}
}

0 comments on commit b69f81f

Please sign in to comment.