Skip to content

Commit

Permalink
update to slim 4.2, closes #5
Browse files Browse the repository at this point in the history
  • Loading branch information
juliangut committed Aug 21, 2019
1 parent eb6bcd4 commit 99ebbdf
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 27 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@ use Jgut\Slim\PHPDI\Configuration;
use Jgut\Slim\PHPDI\ContainerBuilder;
use Psr\Container\ContainerInterface;
use Slim\App;
use Slim\Factory\AppFactory;

$settings = [
'definitions' => '/path/to/definitions/files',
];
$container = ContainerBuilder::build(new Configuration($settings));

$app = $container->get(App::class);
// or $app = AppFactory::createFromContainer($container);

// Register your services if not provided as definitions
$container->set('service_one', function (ContainerInterface $container) {
Expand Down Expand Up @@ -159,7 +161,7 @@ return [

## Migration from 2.x

* Minimum Slim version is now 4.0
* Minimum Slim version is now 4.2
* PHP-DI container now provides only the Configuration object used on building the container itself and implementations of the interfaces needed to instantiate an App. Refer to [Slim's documentation](http://www.slimframework.com/docs/v4/)
* You can extract Slim's App directly from container or seed AppFactory from container
* Slim's App is not extended any more
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@
"prefer-stable": true,
"require": {
"php": "^7.1",
"ext-json": "*",
"php-di/php-di": "^6.0.5",
"slim/slim": "^4.0"
"slim/slim": "^4.2"
},
"require-dev": {
"brainmaestro/composer-git-hooks": "^2.1",
Expand Down
108 changes: 97 additions & 11 deletions src/CallableResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,21 @@
namespace Jgut\Slim\PHPDI;

use Invoker\CallableResolver as InvokerResolver;
use Slim\Interfaces\CallableResolverInterface;
use Invoker\Exception\NotCallableException;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Interfaces\AdvancedCallableResolverInterface;

/**
* Resolve middleware and route callables using PHP-DI.
*/
class CallableResolver implements CallableResolverInterface
class CallableResolver implements AdvancedCallableResolverInterface
{
/**
* @var string
*/
protected const CALLABLE_PATTERN = '!^([^\:]+)\:{1,2}([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$!';

/**
* @var InvokerResolver
*/
Expand All @@ -38,19 +46,97 @@ public function __construct(InvokerResolver $callableResolver)

/**
* {@inheritdoc}
*
* @throws \RuntimeException
*/
public function resolve($toResolve): callable
{
/** @var mixed $resolved */
$resolved = $this->callableResolver->resolve($toResolve);

if (!\is_callable($resolved)) {
throw new \RuntimeException(\sprintf(
'"%s" is not resolvable',
\is_string($toResolve) ? $toResolve : \gettype($toResolve)
));
$resolvable = $toResolve;

if (\is_string($resolvable)) {
$resolvable = $this->callableFromStringNotation($resolvable, '__invoke');
}

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

/**
* {@inheritdoc}
*
* @throws \RuntimeException
*/
public function resolveRoute($toResolve): callable
{
$resolvable = $toResolve;

if (\is_string($resolvable)) {
$resolvable = $this->callableFromStringNotation($resolvable, 'handle');
}
if ($resolvable instanceof RequestHandlerInterface) {
$resolvable = [$resolvable, 'handle'];
}

return $this->resolveCallable($resolvable, $toResolve);
}

/**
* {@inheritdoc}
*
* @throws \RuntimeException
*/
public function resolveMiddleware($toResolve): callable
{
$resolvable = $toResolve;

if (\is_string($resolvable)) {
$resolvable = $this->callableFromStringNotation($resolvable, 'process');
}
if ($resolvable instanceof MiddlewareInterface) {
$resolvable = [$resolvable, 'process'];
}

return $this->resolveCallable($resolvable, $toResolve);
}

/**
* Get resolved callable.
*
* @param mixed $resolvable
* @param mixed $toResolve
*
* @throws \RuntimeException
*
* @return callable
*/
protected function resolveCallable($resolvable, $toResolve): callable
{
try {
return $this->callableResolver->resolve($resolvable);
} catch (NotCallableException $exception) {
if (\is_callable($toResolve) || \is_array($toResolve)) {
$callable = \json_encode($toResolve);
} elseif (\is_object($toResolve)) {
$callable = \get_class($toResolve);
} else {
$callable = \is_string($toResolve) ? $toResolve : \gettype($toResolve);
}

throw new \RuntimeException(\sprintf('"%s" is not resolvable', $callable), 0, $exception);
}
}

/**
* Get callable from string callable notation.
*
* @param string $toResolve
* @param string $defaultMethod
*
* @return string[]
*/
private function callableFromStringNotation(string $toResolve, string $defaultMethod): array
{
\preg_match(static::CALLABLE_PATTERN, $toResolve, $matches);

return $matches ? [$matches[1], $matches[2]] : [$toResolve, $defaultMethod];
}
}
29 changes: 28 additions & 1 deletion src/definitions.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,22 @@
use Jgut\Slim\PHPDI\Configuration;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\App;
use Slim\Factory\AppFactory;
use Slim\Interfaces\CallableResolverInterface;
use Slim\Interfaces\DispatcherInterface;
use Slim\Interfaces\InvocationStrategyInterface;
use Slim\Interfaces\MiddlewareDispatcherInterface;
use Slim\Interfaces\RouteCollectorInterface;
use Slim\Interfaces\RouteResolverInterface;
use Slim\MiddlewareDispatcher;
use Slim\Routing\Dispatcher;
use Slim\Routing\RouteCollector;
use Slim\Routing\RouteResolver;
use Slim\Routing\RouteRunner;

return [
// Replaced by used configuration
Expand Down Expand Up @@ -81,13 +87,34 @@
);
},

MiddlewareDispatcherInterface::class => function (ContainerInterface $container): MiddlewareDispatcherInterface {
$requestHandler = new class() implements RequestHandlerInterface {
/**
* {@inheritdoc}
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
// @codeCoverageIgnoreStart
throw new \RuntimeException('This RequestHandler is replaced by ' . RouteRunner::class);
// @codeCoverageIgnoreEnd
}
};

return new MiddlewareDispatcher(
$requestHandler,
$container->get(CallableResolverInterface::class),
$container
);
},

App::class => function (ContainerInterface $container): App {
return new App(
$container->get(ResponseFactoryInterface::class),
$container,
$container->get(CallableResolverInterface::class),
$container->get(RouteCollectorInterface::class),
$container->get(RouteResolverInterface::class)
$container->get(RouteResolverInterface::class),
$container->get(MiddlewareDispatcherInterface::class)
);
},
];
2 changes: 2 additions & 0 deletions tests/PHPDI/AppTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Slim\App;
use Slim\Interfaces\CallableResolverInterface;
use Slim\Interfaces\InvocationStrategyInterface;
use Slim\Interfaces\MiddlewareDispatcherInterface;
use Slim\Interfaces\RouteCollectorInterface;
use Slim\Interfaces\RouteResolverInterface;

Expand All @@ -44,5 +45,6 @@ public function testCreation(): void
$app->getRouteCollector()->getDefaultInvocationStrategy()
);
static::assertSame($container->get(RouteResolverInterface::class), $app->getRouteResolver());
static::assertSame($container->get(MiddlewareDispatcherInterface::class), $app->getMiddlewareDispatcher());
}
}
98 changes: 85 additions & 13 deletions tests/PHPDI/CallableResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,50 +14,122 @@
namespace Jgut\Slim\PHPDI\Tests;

use Invoker\CallableResolver as InvokerResolver;
use Invoker\Exception\NotCallableException;
use Jgut\Slim\PHPDI\CallableResolver;
use PHPUnit\Framework\TestCase;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

/**
* CallableResolver tests.
*/
class CallableResolverTest extends TestCase
{
public function testInvocable(): void
{
/**
* @dataProvider getResolvableList
*
* @param string $resolveMethod
* @param string|mixed[]|object $toResolve
* @param mixed[] $expectedResolvable
*/
public function testResolveFromString(
string $resolveMethod,
$toResolve,
array $expectedResolvable
): void {
$invoker = $this->getMockBuilder(InvokerResolver::class)
->disableOriginalConstructor()
->getMock();
$invoker->expects(static::once())
->method('resolve')
->with('Controller::method')
->with($expectedResolvable)
->will(self::returnValue(function () {
return 'ok';
}));
/* @var InvokerResolver $invoker */

$resolver = new CallableResolver($invoker);

$invocable = $resolver->resolve('Controller::method');

static::assertEquals('ok', $invocable());
$resolver->{$resolveMethod}($toResolve);
}

public function testNotInvocable(): void
/**
* @return mixed[]
*/
public function getResolvableList(): array
{
$controller = $this->getMockBuilder(RequestHandlerInterface::class)->getMock();
$middleware = $this->getMockBuilder(MiddlewareInterface::class)->getMock();

return [
['resolve', 'Service', ['Service', '__invoke']],
['resolve', 'Service:method', ['Service', 'method']],
['resolve', 'Service::method', ['Service', 'method']],
['resolve', ['Service', 'method'], ['Service', 'method']],

['resolveRoute', 'Controller', ['Controller', 'handle']],
['resolveRoute', $controller, [$controller, 'handle']],
['resolveRoute', [$controller, 'method'], [$controller, 'method']],

['resolveMiddleware', 'Middleware', ['Middleware', 'process']],
['resolveMiddleware', $middleware, [$middleware, 'process']],
['resolveMiddleware', [$middleware, 'method'], [$middleware, 'method']],
];
}

/**
* @dataProvider getNotResolvableList
*
* @param string $resolveMethod
* @param string|mixed[]|object $toResolve
* @param mixed[] $expectedResolvable
*/
public function testNotResolvable(
string $resolveMethod,
$toResolve,
array $expectedResolvable,
string $expectedEsceptionType
): void {
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage(\sprintf('"%s" is not resolvable', $expectedEsceptionType));

$invoker = $this->getMockBuilder(InvokerResolver::class)
->disableOriginalConstructor()
->getMock();
$invoker->expects(static::once())
->method('resolve')
->with('Controller::method');
->with($expectedResolvable)
->will(self::throwException(new NotCallableException()));
/* @var InvokerResolver $invoker */

$resolver = new CallableResolver($invoker);

try {
$resolver->resolve('Controller::method');
} catch (\RuntimeException $exception) {
static::assertEquals('"Controller::method" is not resolvable', $exception->getMessage());
}
$resolver->{$resolveMethod}($toResolve);
}

public function getNotResolvableList(): array
{
$controller = $this->getMockBuilder(RequestHandlerInterface::class)->getMock();
$middleware = $this->getMockBuilder(MiddlewareInterface::class)->getMock();

return [
['resolve', 'Service', ['Service', '__invoke'], 'Service'],
['resolve', 'Service:method', ['Service', 'method'], 'Service:method'],
['resolve', 'Service::method', ['Service', 'method'], 'Service::method'],
['resolve', ['Service', 'method'], ['Service', 'method'], \json_encode(['Service', 'method'])],

['resolveRoute', 'Controller', ['Controller', 'handle'], 'Controller'],
['resolveRoute', $controller, [$controller, 'handle'], \get_class($controller)],
['resolveRoute', [$controller, 'method'], [$controller, 'method'], \json_encode([$controller, 'method'])],

['resolveMiddleware', 'Middleware', ['Middleware', 'process'], 'Middleware'],
['resolveMiddleware', $middleware, [$middleware, 'process'], \get_class($middleware)],
[
'resolveMiddleware',
[$middleware, 'method'],
[$middleware, 'method'],
\json_encode([$middleware, 'method']),
],
];
}
}

0 comments on commit 99ebbdf

Please sign in to comment.