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

4.x - Add an advanced callable resolver #2787

Merged
merged 23 commits into from Aug 11, 2019
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
54dfbac
Add advanced callable resolver
adriansuter Aug 9, 2019
80c4f07
Add AdvancedCallableResolver Support
adriansuter Aug 9, 2019
f5bbb86
Fix test case
adriansuter Aug 9, 2019
cc9f713
Add AdvancedCallableResolver Support
adriansuter Aug 9, 2019
d8983be
Initialize `$callable`
adriansuter Aug 9, 2019
5a1622a
Add test cases for the advanced callable resolver
adriansuter Aug 9, 2019
70ae8c8
Refactor to avoid duplicate code sections
adriansuter Aug 9, 2019
56db54b
Add docblock param types
adriansuter Aug 9, 2019
7af7b02
Convert long line to multi line
adriansuter Aug 9, 2019
580b524
Fix types
adriansuter Aug 9, 2019
19004ee
Fix types again
adriansuter Aug 9, 2019
900fb97
Fix types
adriansuter Aug 9, 2019
4547a78
Rename method to `assertCallableAndBindClosureToContainer`
adriansuter Aug 10, 2019
58cc59d
Rearrange if-conditions
adriansuter Aug 10, 2019
0e485b3
Merge branch '4.x' into patch-callable-resolver
l0gicgate Aug 10, 2019
16b24c9
Change signature for `\Slim\MiddlewareDispatcher` constructor
adriansuter Aug 10, 2019
8f5943c
Merge branch 'patch-callable-resolver' of https://github.com/adriansu…
adriansuter Aug 10, 2019
957578f
Remove the test skips
adriansuter Aug 10, 2019
a9178ba
remove regular CallableResolver, rename AdvancedCallableResolver and …
l0gicgate Aug 11, 2019
5d4a7b7
fix code style errors
l0gicgate Aug 11, 2019
4331ab1
Merge pull request #1 from l0gicgate/2787-FixTests
adriansuter Aug 11, 2019
ebab2db
Convert long line to multi line
adriansuter Aug 11, 2019
1386f93
Merge branch '4.x' into patch-callable-resolver
l0gicgate Aug 11, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
157 changes: 157 additions & 0 deletions Slim/AdvancedCallableResolver.php
@@ -0,0 +1,157 @@
<?php
/**
* Slim Framework (https://slimframework.com)
*
* @license https://github.com/slimphp/Slim/blob/4.x/LICENSE.md (MIT License)
*/

declare(strict_types=1);

namespace Slim;

use Closure;
use Psr\Container\ContainerInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use RuntimeException;
use Slim\Interfaces\AdvancedCallableResolverInterface;

final class AdvancedCallableResolver implements AdvancedCallableResolverInterface
{
/**
* @var ContainerInterface|null
*/
private $container;

/**
* @param ContainerInterface|null $container
*/
public function __construct(?ContainerInterface $container = null)
{
$this->container = $container;
}

/**
* {@inheritdoc}
*/
public function resolve($toResolve): callable
{
$resolved = $toResolve;

if (!is_callable($toResolve) && is_string($toResolve)) {
$resolved = $this->resolveInstanceAndMethod($toResolve);
if ($resolved[1] === null) {
$resolved[1] = '__invoke';
}
}

return $this->checkResolvedAndBindContainerIfClosure($resolved, $toResolve);
}

/**
* {@inheritdoc}
*/
public function resolveRoute($toResolve): callable
{
$resolved = $toResolve;

if (!is_callable($toResolve) && is_string($toResolve)) {
[$instance, $method] = $this->resolveInstanceAndMethod($toResolve);

// For a class that implements RequestHandlerInterface, we will call handle()
// if no method has been specified explicitly
if ($instance instanceof RequestHandlerInterface && $method === null) {
$method = 'handle';
}

$resolved = [$instance, $method ?? '__invoke'];
}

if ($resolved instanceof RequestHandlerInterface) {
$resolved = [$resolved, 'handle'];
}

return $this->checkResolvedAndBindContainerIfClosure($resolved, $toResolve);
}

/**
* {@inheritdoc}
*/
public function resolveMiddleware($toResolve): callable
{
$resolved = $toResolve;

if (!is_callable($toResolve) && is_string($toResolve)) {
[$instance, $method] = $this->resolveInstanceAndMethod($toResolve);

// For a class that implements MiddlewareInterface, we will call process()
// if no method has been specified explicitly
if ($instance instanceof MiddlewareInterface && $method === null) {
$method = 'process';
}

$resolved = [$instance, $method ?? '__invoke'];
}

if ($resolved instanceof MiddlewareInterface) {
$resolved = [$resolved, 'process'];
}

return $this->checkResolvedAndBindContainerIfClosure($resolved, $toResolve);
}

/**
* Resolves the given param and if successful returns an instance as well
* as a method name.
*
* @param string $toResolve
*
* @return array [Instance, Method Name]
*/
private function resolveInstanceAndMethod(string $toResolve): array
{
$class = $toResolve;
$instance = null;
$method = null;

// Check for Slim callable as `class:method`
if (preg_match(CallableResolver::$callablePattern, $toResolve, $matches)) {
$class = $matches[1];
$method = $matches[2];
}

if ($this->container && $this->container->has($class)) {
$instance = $this->container->get($class);
} else {
if (!class_exists($class)) {
throw new RuntimeException(sprintf('Callable %s does not exist', $class));
}
$instance = new $class($this->container);
}

return [$instance, $method];
}

/**
* @param mixed $resolved
* @param string|object|array|callable $toResolve
*
* @return callable
*/
private function checkResolvedAndBindContainerIfClosure($resolved, $toResolve): callable
l0gicgate marked this conversation as resolved.
Show resolved Hide resolved
{
if (!is_callable($resolved)) {
throw new RuntimeException(sprintf(
'%s is not resolvable',
is_callable($toResolve) || is_object($toResolve) || is_array($toResolve) ?
json_encode($toResolve) : $toResolve
));
}

if ($this->container && $resolved instanceof Closure) {
$resolved = $resolved->bindTo($this->container);
}

return $resolved;
}
}
4 changes: 2 additions & 2 deletions Slim/App.php
Expand Up @@ -60,15 +60,15 @@ public function __construct(
) {
parent::__construct(
$responseFactory,
$callableResolver ?? new CallableResolver($container),
$callableResolver ?? new AdvancedCallableResolver($container),
$container,
$routeCollector
);

$this->routeResolver = $routeResolver ?? new RouteResolver($this->routeCollector);
$routeRunner = new RouteRunner($this->routeResolver, $this->routeCollector->getRouteParser());

$this->middlewareDispatcher = new MiddlewareDispatcher($routeRunner, $container);
$this->middlewareDispatcher = new MiddlewareDispatcher($routeRunner, $container, $this->callableResolver);
}

/**
Expand Down
31 changes: 31 additions & 0 deletions Slim/Interfaces/AdvancedCallableResolverInterface.php
@@ -0,0 +1,31 @@
<?php
/**
* Slim Framework (https://slimframework.com)
*
* @license https://github.com/slimphp/Slim/blob/4.x/LICENSE.md (MIT License)
*/

declare(strict_types=1);

namespace Slim\Interfaces;

interface AdvancedCallableResolverInterface extends CallableResolverInterface
{
/**
* Resolve $toResolve into a callable
*
* @param string|callable $toResolve
*
* @return callable
*/
public function resolveRoute($toResolve): callable;

/**
* Resolve $toResolve into a callable
*
* @param string|callable $toResolve
*
* @return callable
*/
public function resolveMiddleware($toResolve): callable;
}
83 changes: 53 additions & 30 deletions Slim/MiddlewareDispatcher.php
Expand Up @@ -16,6 +16,8 @@
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use RuntimeException;
use Slim\Interfaces\AdvancedCallableResolverInterface;
use Slim\Interfaces\CallableResolverInterface;

class MiddlewareDispatcher implements RequestHandlerInterface
{
Expand All @@ -32,15 +34,23 @@ class MiddlewareDispatcher implements RequestHandlerInterface
protected $container;

/**
* @param RequestHandlerInterface $kernel
* @param ContainerInterface|null $container
* @var CallableResolverInterface|null
*/
protected $callableResolver;

/**
* @param RequestHandlerInterface $kernel
* @param ContainerInterface|null $container
* @param CallableResolverInterface|null $callableResolver
*/
public function __construct(
RequestHandlerInterface $kernel,
?ContainerInterface $container = null
?ContainerInterface $container = null,
?CallableResolverInterface $callableResolver = null
l0gicgate marked this conversation as resolved.
Show resolved Hide resolved
) {
$this->seedMiddlewareStack($kernel);
$this->container = $container;
$this->callableResolver = $callableResolver;
}

/**
Expand Down Expand Up @@ -141,53 +151,66 @@ public function handle(ServerRequestInterface $request): ResponseInterface
public function addDeferred(string $middleware): self
{
$next = $this->tip;
$this->tip = new class($middleware, $next, $this->container) implements RequestHandlerInterface
$this->tip = new class(
$middleware,
$next,
$this->container,
$this->callableResolver
) implements RequestHandlerInterface
{
private $middleware;
private $next;
private $container;
private $callableResolver;

public function __construct(
string $middleware,
RequestHandlerInterface $next,
?ContainerInterface $container = null
?ContainerInterface $container = null,
?CallableResolverInterface $callableResolver = null
) {
$this->middleware = $middleware;
$this->next = $next;
$this->container = $container;
$this->callableResolver = $callableResolver;
}

public function handle(ServerRequestInterface $request): ResponseInterface
{
$resolved = $this->middleware;
$instance = null;
$method = null;

// Check for Slim callable as `class:method`
if (preg_match(CallableResolver::$callablePattern, $resolved, $matches)) {
$resolved = $matches[1];
$method = $matches[2];
}

if ($this->container && $this->container->has($resolved)) {
$instance = $this->container->get($resolved);
if ($instance instanceof MiddlewareInterface) {
return $instance->process($request, $this->next);
$callable = null;
if ($this->callableResolver === null) {
$resolved = $this->middleware;
$instance = null;
$method = null;

// Check for Slim callable as `class:method`
if (preg_match(CallableResolver::$callablePattern, $resolved, $matches)) {
$resolved = $matches[1];
$method = $matches[2];
}
} elseif (!function_exists($resolved)) {
if (!class_exists($resolved)) {
throw new RuntimeException(sprintf('Middleware %s does not exist', $resolved));

if ($this->container && $this->container->has($resolved)) {
$instance = $this->container->get($resolved);
if ($instance instanceof MiddlewareInterface) {
return $instance->process($request, $this->next);
}
} elseif (!function_exists($resolved)) {
if (!class_exists($resolved)) {
throw new RuntimeException(sprintf('Middleware %s does not exist', $resolved));
}
$instance = new $resolved($this->container);
}
$instance = new $resolved($this->container);
}

if ($instance && $instance instanceof MiddlewareInterface) {
return $instance->process($request, $this->next);
}
if ($instance && $instance instanceof MiddlewareInterface) {
return $instance->process($request, $this->next);
}

$callable = $instance ?? $resolved;
if ($instance && $method) {
$callable = [$instance, $method];
$callable = $instance ?? $resolved;
if ($instance && $method) {
$callable = [$instance, $method];
}
} elseif ($this->callableResolver instanceof AdvancedCallableResolverInterface) {
l0gicgate marked this conversation as resolved.
Show resolved Hide resolved
$callable = $this->callableResolver->resolveMiddleware($this->middleware);
}

if (!is_callable($callable)) {
Expand Down
7 changes: 6 additions & 1 deletion Slim/Routing/Route.php
Expand Up @@ -17,6 +17,7 @@
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Handlers\Strategies\RequestHandler;
use Slim\Handlers\Strategies\RequestResponse;
use Slim\Interfaces\AdvancedCallableResolverInterface;
use Slim\Interfaces\CallableResolverInterface;
use Slim\Interfaces\InvocationStrategyInterface;
use Slim\Interfaces\RequestHandlerInvocationStrategyInterface;
Expand Down Expand Up @@ -360,7 +361,11 @@ protected function appendGroupMiddlewareToRoute(): void
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
$callable = $this->callableResolver->resolve($this->callable);
if ($this->callableResolver instanceof AdvancedCallableResolverInterface) {
$callable = $this->callableResolver->resolveRoute($this->callable);
} else {
$callable = $this->callableResolver->resolve($this->callable);
}
$strategy = $this->invocationStrategy;

if (is_array($callable)
Expand Down
7 changes: 6 additions & 1 deletion Slim/Routing/RouteGroup.php
Expand Up @@ -10,6 +10,7 @@
namespace Slim\Routing;

use Psr\Http\Server\MiddlewareInterface;
use Slim\Interfaces\AdvancedCallableResolverInterface;
use Slim\Interfaces\CallableResolverInterface;
use Slim\Interfaces\RouteCollectorProxyInterface;
use Slim\Interfaces\RouteGroupInterface;
Expand Down Expand Up @@ -65,7 +66,11 @@ public function __construct(
*/
public function collectRoutes(): RouteGroupInterface
{
$callable = $this->callableResolver->resolve($this->callable);
if ($this->callableResolver instanceof AdvancedCallableResolverInterface) {
$callable = $this->callableResolver->resolveRoute($this->callable);
} else {
$callable = $this->callableResolver->resolve($this->callable);
}
$callable($this->routeCollectorProxy);
return $this;
}
Expand Down