From 02d648a9e3584db098115971dfd1ed900c1657c1 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Thu, 7 Jan 2016 17:42:19 +0700 Subject: [PATCH 1/3] add cookbook for routed middleware class as controller with multi actions --- doc/book/cookbook/bookdown.json | 3 +- ...g-routed-middleware-class-as-controller.md | 118 ++++++++++++++++++ 2 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 doc/book/cookbook/using-routed-middleware-class-as-controller.md diff --git a/doc/book/cookbook/bookdown.json b/doc/book/cookbook/bookdown.json index 80c3142c..6b5bd318 100644 --- a/doc/book/cookbook/bookdown.json +++ b/doc/book/cookbook/bookdown.json @@ -10,6 +10,7 @@ {"Building modular applications": "modular-layout.md"}, {"Setting a locale based on a routing parameter": "setting-locale-depending-routing-parameter.md"}, {"Setting a locale without a routing parameter": "setting-locale-without-routing-parameter.md"}, - {"Enabling debug toolbars": "debug-toolbars.md"} + {"Enabling debug toolbars": "debug-toolbars.md"}, + {"Using Routed Middleware Class as Controller": "using-routed-middleware-class-as-controller.md"} ] } diff --git a/doc/book/cookbook/using-routed-middleware-class-as-controller.md b/doc/book/cookbook/using-routed-middleware-class-as-controller.md new file mode 100644 index 00000000..8acb190b --- /dev/null +++ b/doc/book/cookbook/using-routed-middleware-class-as-controller.md @@ -0,0 +1,118 @@ +# Using Routed Middleware Class as Controller + +If you are familiar with frameworks with provide controller with multi actions functionality, like in Zend Framework 1 and 2, you may want to apply it when you use Expressive as well. Usually, we need to define 1 routed middleware, 1 __invoke() with 3 parameters ( request, response, next ). If we need another specifics usage, we can create another routed middleware classes, for example on `album` crud, we need following middleware classes: + +- AlbumPageIndex +- AlbumPageEdit +- AlbumPageAdd + +What if we want to use only one middleware class which facilitate 3 pages above? We can with make request attribute with 'action' key via route config, and validate it in `__invoke()` method with ReflectionMethod. + +Let say, we have the following route config: + +```php +// ... + 'routes' => [ + [ + 'name' => 'album', + 'path' => '/album[/:action][/:id]', + 'middleware' => Album\Action\AlbumPage::class, + 'allowed_methods' => ['GET'], + ], + ], +// ... +``` + +To avoid repetitive code for modifying `__invoke()` method, we can create an AbstractPage, like the following: + +```php +namespace App\Action; + +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use ReflectionMethod; + +abstract class AbstractPage +{ + public function __invoke($request, $response, callable $next = null) + { + $action = $request->getAttribute('action', 'index') . 'Action'; + + if (method_exists($this, $action)) { + $r = new ReflectionMethod($this, $action); + $args = $r->getParameters(); + + if (count($args) === 3 + && $args[0]->getType() == ServerRequestInterface::class + && $args[1]->getType() == ResponseInterface::class + && $args[2]->isCallable() + && $args[2]->allowsNull() + ) { + return $this->$action($request, $response, $next); + } + } + + return $next($request, $response->withStatus(404), 'Page Not Found'); + } +} +``` + +> ### Note: For ReflectionMethod::getType() in PHP < 7 +> +> You may need to use Zend\Code\Reflection\MethodReflection as the method getType() is not defined yet. +> + +In above abstract class with modified `__invoke()` method, we check if the action attribute, which default is 'index' if not provided, have 'Action' suffix, and the the method is exists within the middleware class with 3 parameters with parameters with parameter 1 as ServerRequestInterface, parameter 2 as ResponseInterface, and parameter 3 is a callable and allows null, otherwise, it will response 404 page. + +So, what we need to do in out routed middleware class is extends the AbstractPage we created: + +``` +namespace Album\Action; + +use App\Action\AbstractPage; +use Zend\Diactoros\Response\HtmlResponse; +use Zend\Expressive\Template; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; + +class AlbumPage extends AbstractPage +{ + protected $template; + // you need to inject via factory + public function __construct(Template\TemplateRendererInterface $template) + { $this->template = $template; } + + public function indexAction( + ServerRequestInterface $request, + ResponseInterface $response, + callable $next = null + ) { + return new HtmlResponse($this->template->render('album::album-page')); + } + + public function addAction( + ServerRequestInterface $request, + ResponseInterface $response, + callable $next = null + ) { + return new HtmlResponse($this->template->render('album::album-page-add')); + } + + public function editAction( + ServerRequestInterface $request, + ResponseInterface $response, + callable $next = null + ) { + $id = $request->getAttribute('id'); + if ($id === null) { + throw new \InvalidArgumentException('id parameter must be provided'); + } + + return new HtmlResponse( + $this->template->render('album::album-page-edit', ['id' => $id]) + ); + } +} +``` + +The rest is just create the view. From 790ab2beaac5dfaa928cb83f26d8c86a818300cb Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Thu, 7 Jan 2016 18:01:51 +0700 Subject: [PATCH 2/3] require zendframework/zend-code desc --- .../cookbook/using-routed-middleware-class-as-controller.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/book/cookbook/using-routed-middleware-class-as-controller.md b/doc/book/cookbook/using-routed-middleware-class-as-controller.md index 8acb190b..e8ffe5b6 100644 --- a/doc/book/cookbook/using-routed-middleware-class-as-controller.md +++ b/doc/book/cookbook/using-routed-middleware-class-as-controller.md @@ -59,7 +59,10 @@ abstract class AbstractPage > ### Note: For ReflectionMethod::getType() in PHP < 7 > -> You may need to use Zend\Code\Reflection\MethodReflection as the method getType() is not defined yet. +> You may need to use Zend\Code\Reflection\MethodReflection as the method getType() is not defined yet, by requiring via composer: +> ``` +> composer require zendframework/zend-code:~2.5 +``` > In above abstract class with modified `__invoke()` method, we check if the action attribute, which default is 'index' if not provided, have 'Action' suffix, and the the method is exists within the middleware class with 3 parameters with parameters with parameter 1 as ServerRequestInterface, parameter 2 as ResponseInterface, and parameter 3 is a callable and allows null, otherwise, it will response 404 page. From 1beb501cea9cd636ad8b4c6f60dcd4fd5a171296 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Thu, 7 Jan 2016 18:03:47 +0700 Subject: [PATCH 3/3] php head doc --- .../cookbook/using-routed-middleware-class-as-controller.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/book/cookbook/using-routed-middleware-class-as-controller.md b/doc/book/cookbook/using-routed-middleware-class-as-controller.md index e8ffe5b6..f0b3a23a 100644 --- a/doc/book/cookbook/using-routed-middleware-class-as-controller.md +++ b/doc/book/cookbook/using-routed-middleware-class-as-controller.md @@ -69,7 +69,7 @@ In above abstract class with modified `__invoke()` method, we check if the actio So, what we need to do in out routed middleware class is extends the AbstractPage we created: -``` +```php namespace Album\Action; use App\Action\AbstractPage;