Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow any callable as a handler #7

Merged
merged 1 commit into from
Feb 7, 2018
Merged

Conversation

shadowhand
Copy link
Member

Callable handlers, not just closures, should be allowed.

Refs #4, #5

if (is_string($requestHandler)) {
$container = $this->container ?: new RequestHandlerContainer();
$requestHandler = $container->get($requestHandler);
if (is_string($requestHandler) && $this->container->has($requestHandler)) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added Container::has() check because a string can be a callable.

}

if (is_callable($requestHandler)) {
$requestHandler = new CallableHandler($requestHandler);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might as well use existing instanceof checks for CallableHandler.

Copy link
Member

@oscarotero oscarotero Feb 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Middlewares\Utils\CallableHandler implements both interfaces (MiddlewareInterface and RequestHandlerInterface) and is callable too (has an __invoke), so I'd move is_callable() to the end, in order to avoid a CallableHandler inside other CallableHandler.

@shadowhand
Copy link
Member Author

shadowhand commented Feb 5, 2018

cc @luketlancaster

@oscarotero
Copy link
Member

Is there an use case for this?

The reason why I didn't allow strings as callables is because you never know whether you want a container entry that doesn't exist (so it must throw a notFoundException) or you want a function name. And due the default container can resolve strings, I'd use strings always as container ids and never as callables.

And the reason of why only Closure is allowed is because they can be created easily from any callable thanks to Closure::fromCallable, that provide a better control, performance and safety.

@shadowhand
Copy link
Member Author

shadowhand commented Feb 5, 2018

The use case is pretty simple:

class FooController
{
    public function __invoke(ServerRequestInterface $request) { /* ... */ }
}

And the route is defined as:

'foo' => FooController::class,

I would rather not write this for every single route:

'foo' => \Closure::fromCallable(FooController::class),

@shadowhand
Copy link
Member Author

you never know whether you want a container entry that doesn't exist

This is why I added $container->has(...) to the check.

@oscarotero
Copy link
Member

oscarotero commented Feb 5, 2018

The default RequestHandlerContainer should resolve FooController::class, isn't?

This is why I added $container->has(...) to the check.

Let's say that I have a controller called print and my container should return an instance of Controllers\PrintController. If the controller does not exist yet, it still being a valid callable so no error is throw.
And if it's not a valid callable it throws a RuntimeException instead a NotFoundException so, at the end, you will get random results depending of the name of your controllers.

@shadowhand
Copy link
Member Author

If the controller does not exist yet

This argument doesn't make sense. By the time this code runs, the container should know what objects it has available and be able to determine that print means PrintController.

As an aside, many containers are opting to use full class names as the identifier, so it is fairly unlikely that there would be a conflict between $container->has(...) and is_callable(...) checks.

@oscarotero
Copy link
Member

What I mean is that $controller->has('print'), can return false (because the class PrintController does not exists, or is misnamed to PrinController, or you forget to add the controller php file before commit, etc...), and the middleware will try to execute the print() function. Or if this happens with a non existing function (let's say $controller->has('listUsers')), you'll get a RuntimeException instead a NotFoundException.

If strings are considered as callables too, there's no way to restrict the requestHandler is resolved only through the container, so if it exists as a global function it will be executed as a fallback. I can consider accept any callable but strings.

Other thing is why you find useful to have a controller with __invoke instead implements the RequestHandlerInterface. In fact, __invoke was suggested but rejected as the method of a RequestHandlerInterface 😃

Anyway, your use case should work perfectly right now, because the default RequestHandlerController should handle this case (as it does in the tests), or please, let me know if this is not happening.

@shadowhand shadowhand force-pushed the fix/callable-handlers branch from f4cdcca to 011b3d4 Compare February 6, 2018 15:20
@shadowhand
Copy link
Member Author

Other thing is why you find useful to have a controller with __invoke instead implements the RequestHandlerInterface.

I don't want to import an interface for all my controllers. There's no benefit to doing that, since the controllers are specific to my application and don't need interop.

@shadowhand
Copy link
Member Author

I updated the PR and removed support for callable strings.

}

if (is_callable($requestHandler)) {
$requestHandler = new CallableHandler($requestHandler);
Copy link
Member

@oscarotero oscarotero Feb 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Middlewares\Utils\CallableHandler implements both interfaces (MiddlewareInterface and RequestHandlerInterface) and is callable too (has an __invoke), so I'd move is_callable() to the end, in order to avoid a CallableHandler inside other CallableHandler.

@@ -30,7 +30,7 @@ class RequestHandler implements MiddlewareInterface
*/
public function __construct(ContainerInterface $container = null)
{
$this->container = $container;
$this->container = $container ?: new RequestHandlerContainer();
Copy link
Member

@oscarotero oscarotero Feb 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this is used only inside if (is_string($requestHandler)), I'd not create the instance in the constructor, but inside that if.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the consistency is valuable. There's no real performance impact of doing this.

Callable handlers, not just closures, should be allowed.
@shadowhand shadowhand force-pushed the fix/callable-handlers branch from 011b3d4 to 2feeebc Compare February 7, 2018 17:08
@oscarotero oscarotero merged commit 287dc0f into master Feb 7, 2018
@oscarotero
Copy link
Member

@shadowhand Thanks 😃

@oscarotero oscarotero deleted the fix/callable-handlers branch February 7, 2018 17:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants