diff --git a/README.md b/README.md index ca05a3b..fd634f1 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ It provides the following features: * Group your service definitions in service providers classes * Group and configure your controllers in controller providers classes * Group all your application controllers and services in single classes +* Register all your application middleware in a single class ## Tests diff --git a/couscous.yml b/couscous.yml index 245c511..2b5cf00 100644 --- a/couscous.yml +++ b/couscous.yml @@ -24,3 +24,6 @@ menu: grouping_definitions: text: Grouping definitions relativeUrl: docs/grouping-definitions.html + middleware: + text: Application middleware + relativeUrl: docs/middleware.html diff --git a/docs/middleware.md b/docs/middleware.md new file mode 100644 index 0000000..bd5b478 --- /dev/null +++ b/docs/middleware.md @@ -0,0 +1,104 @@ +# Application middleware + +Slim implements a version of the Rack protocol, therefore it can have middleware +that may inspect, or modify the application environment, request, and response +before and/or after the Slim application is invoked. + +You can register all your application middleware in a single place. You only +need to extend the class `ComPHPPuebla\Slim\MiddlewareLayers` and add all your +middleware in the `init` method by calling the method `add`. + +Suppose you have a middleware that logs the requests, matched routes and responses +of your application. You would have to register it the following way. + +```php +namespace Application; + +use Slim\Helper\Set; +use ComPHPPuebla\MiddlewareLayers; + +class ApplicationMiddleware extends MiddlewareLayers +{ + /** + * Add the middleware for your application here + */ + protected function init(Set $container) + { + $this + ->add(new RequestLoggingMiddleware()) + ; + } +} +``` + +## Using the container + +Suppose you want to use dependency injection for your middleware in order to +control what logger your application should use, instead of simply using Slim's +logger. + +You could register a [Monolog][1] logger in a service provider. + +```php +namespace Application; + +use ComPHPPuebla\Slim\Resolver; +use ComPHPPuebla\Slim\ServiceProvider; +use Monolog\Handler\StreamHandler; +use Monolog\Logger; +use Slim\Slim; + +class MonologServiceProvider implements ServiceProvider +{ + public function configure(Slim $app, Resolver $resolver, array $options = []) + { + $app->container->singleton( + 'logger', + function () use ($app, $options) { + $logger = new Logger($options['monolog']['channel']); + $logger->pushHandler(new StreamHandler( + $options['monolog']['path'], Logger::DEBUG + )); + + return $logger; + } + ); + } +} +``` + +And then retrieve the object inside the `init` method through the container. + +```php +protected function init(Set $container) +{ + $this + ->add(new RequestLoggingMiddleware($container->get('logger')) + ; +} +``` + +Alternatively, you could register the middleware class inside a service provider +and simply retrieve it from the container. + +```php +protected function init(Set $container) +{ + $this + ->add($container->get('slim.middleware.request_logger') + ; +} +``` + +Then your `index.php` file would only need: + +```php +$app = new Slim\Slim(); + +$middleware = new Application\ApplicationMiddleware(); +$middleware->configure($app); + +$app->run(); +``` + +[1]: https://github.com/Seldaek/monolog diff --git a/docs/service-providers.md b/docs/service-providers.md index 4d9e6c1..e01d461 100644 --- a/docs/service-providers.md +++ b/docs/service-providers.md @@ -16,7 +16,7 @@ class ProductCatalogServices implements ServiceProvider /** * Register all your module services as usual */ - public function configure(Slim $app, Resolver $resolver, array $parameters = []) + public function configure(Slim $app, Resolver $resolver, array $options = []) { $app->container->singleton( 'catalog.product_repository', @@ -64,13 +64,13 @@ use Twig_Environment as Environment; class TwigServiceProvider implements ServiceProvider { - public function configure(Slim $app, Resolver $resolver, array $parameters = []) + public function configure(Slim $app, Resolver $resolver, array $options = []) { $app->container->singleton('twig.loader', function () { - return new Loader($parameters['loader_paths']); + return new Loader($options['loader_paths']); }); $app->container->singleton('twig.environment', function () use ($app) { - return new Environment($app->container->get('loader'), $parameters['options']); + return new Environment($app->container->get('loader'), $options['options']); }); } } @@ -122,7 +122,7 @@ use Twig_Environment as Environment; class ProductCatalogServices implements ServiceProvider { - public function configure(Slim $app, Resolver $resolver, array $parameters = []) + public function configure(Slim $app, Resolver $resolver, array $options = []) { /* More service definitions... */ $resolver->extend($app, 'twig.environment', function(Environment $twig) { diff --git a/src/MiddlewareLayers.php b/src/MiddlewareLayers.php new file mode 100644 index 0000000..4b1bedc --- /dev/null +++ b/src/MiddlewareLayers.php @@ -0,0 +1,56 @@ +middleware[] = $middleware; + + return $this; + } + + /** + * Configure the middleware layers for your application + * + * @param Slim $app + */ + public function configure(Slim $app) + { + $this->init($app->container); + + /** @var MiddlewareProvider $middleware */ + foreach ($this->middleware as $middleware) { + $app->add($middleware); + } + } +} diff --git a/tests/MiddlewareLayersTest.php b/tests/MiddlewareLayersTest.php new file mode 100644 index 0000000..ada7c03 --- /dev/null +++ b/tests/MiddlewareLayersTest.php @@ -0,0 +1,91 @@ +logger = $logger; + } + + public function call() + { + } +} + +class FakeMiddlewareLayers extends MiddlewareLayers +{ + public function init(Set $container) + { + $this->add(new FakeMiddleware($container->get('logger'))); + } +} + +class MiddlewareLayersTest extends TestCase +{ + /** @test */ + function it_should_register_middleware_to_the_given_slim_application() + { + $middleware1 = $this->getMock(Middleware::class); + $middleware2 = $this->getMock(Middleware::class); + + $builder = $this->getMockBuilder(Slim::class); + $app = $builder->setMethods(['add'])->getMock(); + + $app + ->expects($spy = $this->exactly(2)) + ->method('add') + ->with($this->isInstanceOf(Middleware::class)) + ; + + $middleware = new MiddlewareLayers(); + $middleware + ->add($middleware1) + ->add($middleware2) + ; + + $middleware->configure($app); + + $this->assertEquals( + 2, $spy->getInvocationCount(), 'It should have registered 2 middleware layers' + ); + } + + /** @test */ + function it_should_be_able_to_use_slim_container_to_build_middleware() + { + $app = new Slim(); + $container = $this->getMock(Set::class); + $container + ->expects($spy = $this->once()) + ->method('get') + ->with('logger') + ->willReturn($this->returnValue(new stdClass())) + ; + + $app->container = $container; + + $middleware = new FakeMiddlewareLayers(); + + $middleware->configure($app); + + $this->assertEquals( + 1, $spy->getInvocationCount(), 'It should have retrieved the logger from the container' + ); + } +}