diff --git a/.travis.yml b/.travis.yml index feb3d49..2b0aa38 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,23 @@ language: php +notifications: + email: + on_success: never + php: - - 7.0 - 7.1 - 7.2 + - 7.3 matrix: include: - - php: 7.0 + - php: 7.1 env: dependencies=lowest +cache: + directories: + - $HOME/.composer/cache + sudo: false before_script: diff --git a/composer.json b/composer.json index e3d1df3..849b88e 100644 --- a/composer.json +++ b/composer.json @@ -14,12 +14,13 @@ } }, "require": { - "php": "~7.0", + "php": "~7.1", "php-di/php-di": "^6.0.0", "php-di/invoker": "^2.0.0", - "slim/slim": "^3.9.0" + "slim/slim": "^4.0.0" }, "require-dev": { - "phpunit/phpunit": "~6.0" + "phpunit/phpunit": "~6.0", + "zendframework/zend-diactoros": "^2.1" } } diff --git a/src/App.php b/src/App.php deleted file mode 100644 index e1e0a56..0000000 --- a/src/App.php +++ /dev/null @@ -1,38 +0,0 @@ -addDefinitions(__DIR__ . '/config.php'); - $this->configureContainer($containerBuilder); - $container = $containerBuilder->build(); - - parent::__construct($container); - } - - /** - * Override this method to configure the container builder. - * - * For example, to load additional configuration files: - * - * protected function configureContainer(ContainerBuilder $builder) - * { - * $builder->addDefinitions(__DIR__ . 'my-config-file.php'); - * } - */ - protected function configureContainer(ContainerBuilder $builder) - { - } -} diff --git a/src/Bridge.php b/src/Bridge.php new file mode 100644 index 0000000..c36a704 --- /dev/null +++ b/src/Bridge.php @@ -0,0 +1,55 @@ +getRouteCollector()->setDefaultInvocationStrategy($controllerInvoker); + + return $app; + } + + private static function createControllerInvoker(ContainerInterface $container): ControllerInvoker + { + $resolvers = [ + // Inject parameters by name first + new AssociativeArrayResolver(), + // Then inject services by type-hints for those that weren't resolved + new TypeHintContainerResolver($container), + // Then fall back on parameters default values for optional route parameters + new DefaultValueResolver(), + ]; + + $invoker = new Invoker(new ResolverChain($resolvers), $container); + + return new ControllerInvoker($invoker); + } +} diff --git a/src/CallableResolver.php b/src/CallableResolver.php index 40cc861..1321124 100644 --- a/src/CallableResolver.php +++ b/src/CallableResolver.php @@ -18,11 +18,10 @@ public function __construct(\Invoker\CallableResolver $callableResolver) { $this->callableResolver = $callableResolver; } - /** * {@inheritdoc} */ - public function resolve($toResolve) + public function resolve($toResolve): callable { return $this->callableResolver->resolve($toResolve); } diff --git a/src/ControllerInvoker.php b/src/ControllerInvoker.php index e16b8a4..8817b81 100644 --- a/src/ControllerInvoker.php +++ b/src/ControllerInvoker.php @@ -18,7 +18,6 @@ public function __construct(InvokerInterface $invoker) { $this->invoker = $invoker; } - /** * Invoke a route callable. * @@ -34,16 +33,14 @@ public function __invoke( ServerRequestInterface $request, ResponseInterface $response, array $routeArguments - ) { + ): ResponseInterface { // Inject the request and response by parameter name $parameters = [ 'request' => $request, 'response' => $response, ]; - // Inject the route arguments by name $parameters += $routeArguments; - // Inject the attributes defined on the request $parameters += $request->getAttributes(); diff --git a/src/config.php b/src/config.php deleted file mode 100644 index ea2bd61..0000000 --- a/src/config.php +++ /dev/null @@ -1,78 +0,0 @@ - '1.1', - 'settings.responseChunkSize' => 4096, - 'settings.outputBuffering' => 'append', - 'settings.determineRouteBeforeAppMiddleware' => false, - 'settings.displayErrorDetails' => false, - 'settings.addContentLengthHeader' => true, - 'settings.routerCacheFile' => false, - - 'settings' => [ - 'httpVersion' => get('settings.httpVersion'), - 'responseChunkSize' => get('settings.responseChunkSize'), - 'outputBuffering' => get('settings.outputBuffering'), - 'determineRouteBeforeAppMiddleware' => get('settings.determineRouteBeforeAppMiddleware'), - 'displayErrorDetails' => get('settings.displayErrorDetails'), - 'addContentLengthHeader' => get('settings.addContentLengthHeader'), - 'routerCacheFile' => get('settings.routerCacheFile'), - ], - - // Default Slim services - 'router' => create(Slim\Router::class) - ->method('setContainer', get(Container::class)) - ->method('setCacheFile', get('settings.routerCacheFile')), - Slim\Router::class => get('router'), - 'errorHandler' => create(Slim\Handlers\Error::class) - ->constructor(get('settings.displayErrorDetails')), - 'phpErrorHandler' => create(Slim\Handlers\PhpError::class) - ->constructor(get('settings.displayErrorDetails')), - 'notFoundHandler' => create(Slim\Handlers\NotFound::class), - 'notAllowedHandler' => create(Slim\Handlers\NotAllowed::class), - 'environment' => function () { - return new Slim\Http\Environment($_SERVER); - }, - 'request' => function (ContainerInterface $c) { - return Request::createFromEnvironment($c->get('environment')); - }, - 'response' => function (ContainerInterface $c) { - $headers = new Headers(['Content-Type' => 'text/html; charset=UTF-8']); - $response = new Response(200, $headers); - return $response->withProtocolVersion($c->get('settings')['httpVersion']); - }, - 'foundHandler' => create(ControllerInvoker::class) - ->constructor(get('foundHandler.invoker')), - 'foundHandler.invoker' => function (ContainerInterface $c) { - $resolvers = [ - // Inject parameters by name first - new AssociativeArrayResolver, - // Then inject services by type-hints for those that weren't resolved - new TypeHintContainerResolver($c), - // Then fall back on parameters default values for optional route parameters - new DefaultValueResolver(), - ]; - return new Invoker(new ResolverChain($resolvers), $c); - }, - - 'callableResolver' => autowire(CallableResolver::class), - -]; diff --git a/tests/ApplicationTest.php b/tests/ApplicationTest.php index 7b228d2..697f8b6 100644 --- a/tests/ApplicationTest.php +++ b/tests/ApplicationTest.php @@ -2,10 +2,9 @@ namespace DI\Bridge\Slim\Test; -use DI\Bridge\Slim\App; +use DI\Bridge\Slim\Bridge; use DI\Bridge\Slim\Test\Mock\RequestFactory; use PHPUnit\Framework\TestCase; -use Slim\Http\Response; class ApplicationTest extends TestCase { @@ -14,14 +13,15 @@ class ApplicationTest extends TestCase */ public function runs() { - $app = new App; + $app = Bridge::create(); $called = false; - $app->get('/', function () use (&$called) { + $app->get('/', function ($request, $response) use (&$called) { $called = true; + return $response; }); + $app->handle(RequestFactory::create()); - $app->callMiddlewareStack(RequestFactory::create(), new Response); $this->assertTrue($called); } } diff --git a/tests/ContainerTest.php b/tests/ContainerTest.php deleted file mode 100644 index 2b8ea72..0000000 --- a/tests/ContainerTest.php +++ /dev/null @@ -1,59 +0,0 @@ -getContainer(); - /** @var \DI\Container $phpdiContainer */ - $phpdiContainer = $slimPhpDi->getContainer(); - - $expectedEntries = $defaultContainer->keys(); - - foreach ($expectedEntries as $expectedEntry) { - $this->assertTrue($phpdiContainer->has($expectedEntry), "Container entry $expectedEntry is missing"); - // Check that the service is created without exception - $phpdiContainer->get($expectedEntry); - } - } - - /** - * Slim expects some config options to exist. - * - * @test - */ - public function provides_default_config_options() - { - $slimDefault = new \Slim\App; - $slimPhpDi = new App; - - /** @var \Slim\Container $defaultContainer */ - $defaultContainer = $slimDefault->getContainer(); - /** @var \DI\Container $phpdiContainer */ - $phpdiContainer = $slimPhpDi->getContainer(); - - $expectedOptions = $defaultContainer->get('settings'); - $actualOptions = $phpdiContainer->get('settings'); - - foreach ($expectedOptions as $name => $value) { - $this->assertArrayHasKey($name, $actualOptions); - // Has the same default value - $this->assertEquals($value, $actualOptions[$name]); - } - } -} diff --git a/tests/ErrorTest.php b/tests/ErrorTest.php deleted file mode 100644 index d008157..0000000 --- a/tests/ErrorTest.php +++ /dev/null @@ -1,98 +0,0 @@ -getContainer(); - - // Sanity check - default for displayErrorDetails should be false - $displayErrorDetails = $c->get('settings.displayErrorDetails'); - $this->assertFalse($displayErrorDetails); - - /** @var Error $error */ - $error = $c->get('errorHandler'); - $response = $error(RequestFactory::create('/'), new Response(), new \Exception()); - $reasonPhrase = $response->getReasonPhrase(); - $this->assertEquals('Internal Server Error', $reasonPhrase); - - $log = file_get_contents($logFile); - $this->assertNotEmpty($log); - } - - /** - * Test custom errorHandler - * - * @test - */ - public function custom_exception_handling() - { - // Send error_log output to a temp file. - $logFile = tempnam(sys_get_temp_dir(), 'slim-bridge'); - ini_set('error_log', $logFile); - - $app = new BridgeApp( - [ - 'settings.displayErrorDetails' => true, - 'settings.outputBuffering' => 'append' - ]); - $c = $app->getContainer(); - - // Sanity checks - $displayErrorDetails = $c->get('settings.displayErrorDetails'); - $this->assertTrue($displayErrorDetails); - $outputBuffering = $c->get('settings.outputBuffering'); - $this->assertEquals('append', $outputBuffering); - - /** @var Error $error */ - $error = $c->get('errorHandler'); - - $response = $error(RequestFactory::create('/'), new Response(), new \Exception()); - $reasonPhrase = $response->getReasonPhrase(); - $this->assertEquals('Internal Server Error', $reasonPhrase); - - $log = file_get_contents($logFile); - $this->assertEmpty($log); - } -} - -/** - * Class BridgeApp - * - * Override the configuration via the configureContainer() hook. - */ -class BridgeApp extends App -{ - protected $config = []; - - public function __construct(array $config) - { - $this->config = $config; - - parent::__construct(); - } - - public function configureContainer(ContainerBuilder $builder) - { - $builder->addDefinitions($this->config); - } -} diff --git a/tests/Fixture/Middleware.php b/tests/Fixture/Middleware.php deleted file mode 100644 index 132fa84..0000000 --- a/tests/Fixture/Middleware.php +++ /dev/null @@ -1,15 +0,0 @@ -getBody()->write('Hello world!'); - return $response; - } -} diff --git a/tests/Fixture/UserController.php b/tests/Fixture/UserController.php index b0e85d3..85f8322 100644 --- a/tests/Fixture/UserController.php +++ b/tests/Fixture/UserController.php @@ -1,4 +1,4 @@ -add(function (ServerRequestInterface $request, ResponseInterface $response, callable $next) { - $response->getBody()->write('Hello ' . $request->getQueryParams()['foo']); - return $response; + $app = Bridge::create(); + $app->add(function (ServerRequestInterface $request, RequestHandlerInterface $next) { + return new TextResponse('Hello ' . $request->getQueryParams()['foo']); }); $app->get('/', function () {}); - $response = $app->callMiddlewareStack(RequestFactory::create('/', 'foo=matt'), new Response); + $response = $app->handle(RequestFactory::create('/', 'foo=matt')); $this->assertEquals('Hello matt', $response->getBody()->__toString()); } diff --git a/tests/Mock/RequestFactory.php b/tests/Mock/RequestFactory.php index ccacb93..5b13ab8 100644 --- a/tests/Mock/RequestFactory.php +++ b/tests/Mock/RequestFactory.php @@ -1,18 +1,24 @@ - 'index.php', - 'REQUEST_URI' => $uri, - 'QUERY_STRING' => $queryString, - ])); + parse_str($queryString, $queryParams); + return new ServerRequest( + [], + [], + $uri, + 'GET', + 'php://temp', + [], + [], + $queryParams + ); } } diff --git a/tests/RoutingTest.php b/tests/RoutingTest.php index 94bc656..dc55454 100644 --- a/tests/RoutingTest.php +++ b/tests/RoutingTest.php @@ -1,14 +1,14 @@ -get('/', function (ResponseInterface $response, ServerRequestInterface $request) { $response->getBody()->write('Hello ' . $request->getQueryParams()['foo']); return $response; }); - $response = $app->callMiddlewareStack(RequestFactory::create('/', 'foo=matt'), new Response); - $this->assertEquals('Hello matt', $response->getBody()->__toString()); + $response = $app->handle(RequestFactory::create('/', 'foo=matt')); + $this->assertEquals('Hello matt', (string) $response->getBody()); } /** @@ -33,14 +34,14 @@ public function injects_request_and_response() */ public function injects_route_placeholder() { - $app = new App; + $app = Bridge::create(); $app->get('/{name}', function ($name, $response) { $response->getBody()->write('Hello ' . $name); return $response; }); - $response = $app->callMiddlewareStack(RequestFactory::create('/matt'), new Response); - $this->assertEquals('Hello matt', $response->getBody()->__toString()); + $response = $app->handle(RequestFactory::create('/matt')); + $this->assertEquals('Hello matt', (string) $response->getBody()); } /** @@ -48,13 +49,13 @@ public function injects_route_placeholder() */ public function injects_optional_route_placeholder() { - $app = new App; + $app = Bridge::create(); $app->get('/[{name}]', function ($response, $name = null) { $response->getBody()->write('Hello ' . $name); return $response; }); - $response = $app->callMiddlewareStack(RequestFactory::create('/matt'), new Response); + $response = $app->handle(RequestFactory::create('/matt')); $this->assertEquals('Hello matt', (string) $response->getBody()); } @@ -63,13 +64,13 @@ public function injects_optional_route_placeholder() */ public function injects_default_value_in_optional_route_placeholder() { - $app = new App; + $app = Bridge::create(); $app->get('/[{name}]', function ($response, $name = 'john doe') { $response->getBody()->write('Hello ' . $name); return $response; }); - $response = $app->callMiddlewareStack(RequestFactory::create('/'), new Response); + $response = $app->handle(RequestFactory::create()); $this->assertEquals('Hello john doe', (string) $response->getBody()); } @@ -78,18 +79,18 @@ public function injects_default_value_in_optional_route_placeholder() */ public function injects_request_attribute() { - $app = new App; + $app = Bridge::create(); // Let's add a middleware that adds a request attribute - $app->add(function (ServerRequestInterface $request, $response, $next) { - return $next($request->withAttribute('name', 'Bob'), $response); + $app->add(function (ServerRequestInterface $request, RequestHandlerInterface $next) { + return $next->handle($request->withAttribute('name', 'Bob')); }); $app->get('/', function ($name, $response) { $response->getBody()->write('Hello ' . $name); return $response; }); - $response = $app->callMiddlewareStack(RequestFactory::create('/'), new Response); - $this->assertEquals('Hello Bob', $response->getBody()->__toString()); + $response = $app->handle(RequestFactory::create()); + $this->assertEquals('Hello Bob', (string) $response->getBody()); } /** @@ -97,16 +98,16 @@ public function injects_request_attribute() */ public function injects_route_placeholder_over_request_attribute() { - $app = new App; - $app->add(function (ServerRequestInterface $request, $response, $next) { - return $next($request->withAttribute('name', 'Bob'), $response); + $app = Bridge::create(); + $app->add(function (ServerRequestInterface $request, RequestHandlerInterface $next) { + return $next->handle($request->withAttribute('name', 'Bob')); }); $app->get('/{name}', function ($name, $response) { $response->getBody()->write('Hello ' . $name); return $response; }); - $response = $app->callMiddlewareStack(RequestFactory::create('/matt'), new Response); + $response = $app->handle(RequestFactory::create('/matt')); // The route placeholder has priority over the request attribute $this->assertEquals('Hello matt', (string) $response->getBody()); } @@ -116,10 +117,10 @@ public function injects_route_placeholder_over_request_attribute() */ public function resolve_controller_from_container() { - $app = new App; + $app = Bridge::create(); $app->get('/', [UserController::class, 'dashboard']); - $response = $app->callMiddlewareStack(RequestFactory::create(), new Response); - $this->assertEquals('Hello world!', $response->getBody()->__toString()); + $response = $app->handle(RequestFactory::create()); + $this->assertEquals('Hello world!', (string) $response->getBody()); } }