Skip to content

Commit

Permalink
Merge pull request #2779 from l0gicgate/MiddlewareDispatcherCallableR…
Browse files Browse the repository at this point in the history
…esolver

4.x - Support For Slim Callables in MiddlewareDispatcher & Container Closure Autobinding
  • Loading branch information
l0gicgate authored Aug 6, 2019
2 parents 7ee5627 + 65c9325 commit e034220
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 21 deletions.
14 changes: 9 additions & 5 deletions Slim/CallableResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
*/
final class CallableResolver implements CallableResolverInterface
{
/**
* @var string
*/
public static $callablePattern = '!^([^\:]+)\:([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$!';

/**
* @var ContainerInterface|null
*/
Expand Down Expand Up @@ -55,14 +60,13 @@ public function resolve($toResolve): callable
$instance = null;
$method = null;

// check for slim callable as "class:method"
$callablePattern = '!^([^\:]+)\:([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$!';
if (preg_match($callablePattern, $toResolve, $matches)) {
// Check for Slim callable as `class:method`
if (preg_match(self::$callablePattern, $toResolve, $matches)) {
$class = $matches[1];
$method = $matches[2];
}

if ($this->container instanceof ContainerInterface && $this->container->has($class)) {
if ($this->container && $this->container->has($class)) {
$instance = $this->container->get($class);
} else {
if (!class_exists($class)) {
Expand Down Expand Up @@ -91,7 +95,7 @@ public function resolve($toResolve): callable
));
}

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

Expand Down
58 changes: 44 additions & 14 deletions Slim/MiddlewareDispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

namespace Slim;

use Closure;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
Expand Down Expand Up @@ -159,24 +160,48 @@ public function __construct(
public function handle(ServerRequestInterface $request): ResponseInterface
{
$resolved = $this->middleware;
if ($this->container && $this->container->has($this->middleware)) {
$resolved = $this->container->get($this->middleware);
if ($resolved instanceof MiddlewareInterface) {
return $resolved->process($request, $this->next);
$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);
}
} elseif (!function_exists($resolved)) {
if (!class_exists($resolved)) {
throw new RuntimeException(sprintf('Middleware %s does not exist', $resolved));
}
$instance = new $resolved($this->container);
}

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

$callable = $instance ?? $resolved;
if ($instance && $method) {
$callable = [$instance, $method];
}
if (is_subclass_of($resolved, MiddlewareInterface::class)) {
/** @var MiddlewareInterface $resolved */
$resolved = new $resolved($this->container);
return $resolved->process($request, $this->next);

if (!is_callable($callable)) {
throw new RuntimeException(sprintf(
'Middleware %s is not resolvable',
$this->middleware
));
}
if (is_callable($resolved)) {
return $resolved($request, $this->next);

if ($this->container && $callable instanceof Closure) {
$callable = $callable->bindTo($this->container);
}
throw new RuntimeException(sprintf(
'%s is not resolvable',
$this->middleware
));

return $callable($request, $this->next);
}
};

Expand All @@ -196,6 +221,11 @@ public function handle(ServerRequestInterface $request): ResponseInterface
public function addCallable(callable $middleware): self
{
$next = $this->tip;

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

$this->tip = new class($middleware, $next) implements RequestHandlerInterface
{
private $middleware;
Expand Down
68 changes: 66 additions & 2 deletions tests/MiddlewareDispatcherTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\MiddlewareDispatcher;
use Slim\Tests\Mocks\MockMiddlewareSlimCallable;
use Slim\Tests\Mocks\MockMiddlewareWithConstructor;
use Slim\Tests\Mocks\MockMiddlewareWithoutConstructor;
use Slim\Tests\Mocks\MockRequestHandler;
Expand Down Expand Up @@ -74,6 +75,69 @@ public function testDeferredResolvedCallable()
$this->assertEquals(1, $handler->getCalledCount());
}

public function testDeferredResolvedSlimCallable()
{
$handler = new MockRequestHandler();
$middlewareDispatcher = new MiddlewareDispatcher($handler, null);
$middlewareDispatcher->addDeferred(MockMiddlewareSlimCallable::class . ':custom');

$request = $this->createServerRequest('/');
$middlewareDispatcher->handle($request);

$this->assertEquals(1, $handler->getCalledCount());
}

public function testDeferredResolvedClosureIsBoundToContainer()
{
$containerProphecy = $this->prophesize(ContainerInterface::class);

$self = $this;
$callable = function (
ServerRequestInterface $request,
RequestHandlerInterface $handler
) use (
$self,
$containerProphecy
) {
$self->assertSame($containerProphecy->reveal(), $this);
return $handler->handle($request);
};

$containerProphecy->has('callable')->willReturn(true);
$containerProphecy->get('callable')->willReturn($callable);

$handler = new MockRequestHandler();
$middlewareDispatcher = new MiddlewareDispatcher($handler, $containerProphecy->reveal());
$middlewareDispatcher->addDeferred('callable');

$request = $this->createServerRequest('/');
$middlewareDispatcher->handle($request);
}

public function testAddCallableBindsClosureToContainer()
{
$containerProphecy = $this->prophesize(ContainerInterface::class);

$self = $this;
$callable = function (
ServerRequestInterface $request,
RequestHandlerInterface $handler
) use (
$self,
$containerProphecy
) {
$self->assertSame($containerProphecy->reveal(), $this);
return $handler->handle($request);
};

$handler = new MockRequestHandler();
$middlewareDispatcher = new MiddlewareDispatcher($handler, $containerProphecy->reveal());
$middlewareDispatcher->addCallable($callable);

$request = $this->createServerRequest('/');
$middlewareDispatcher->handle($request);
}

public function testResolvableReturnsInstantiatedObject()
{
MockMiddlewareWithoutConstructor::$CalledCount = 0;
Expand All @@ -91,7 +155,7 @@ public function testResolvableReturnsInstantiatedObject()

/**
* @expectedException \RuntimeException
* @expectedExceptionMessage MiddlewareInterfaceNotImplemented is not resolvable
* @expectedExceptionMessage Middleware MiddlewareInterfaceNotImplemented is not resolvable
*/
public function testResolveThrowsExceptionWhenResolvableDoesNotImplementMiddlewareInterface()
{
Expand All @@ -109,7 +173,7 @@ public function testResolveThrowsExceptionWhenResolvableDoesNotImplementMiddlewa

/**
* @expectedException \RuntimeException
* @expectedExceptionMessage Unresolvable::class is not resolvable
* @expectedExceptionMessage Middleware Unresolvable::class does not exist
*/
public function testResolveThrowsExceptionWithoutContainerAndUnresolvableClass()
{
Expand Down
27 changes: 27 additions & 0 deletions tests/Mocks/MockMiddlewareSlimCallable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?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\Tests\Mocks;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

class MockMiddlewareSlimCallable
{
/**
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
*/
public function custom(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
return $handler->handle($request);
}
}
6 changes: 6 additions & 0 deletions tests/Mocks/MockMiddlewareWithConstructor.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
<?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\Tests\Mocks;
Expand Down

0 comments on commit e034220

Please sign in to comment.