Skip to content
This repository has been archived by the owner on Jan 29, 2020. It is now read-only.

Commit

Permalink
Merge pull request #262 from samsonasik/cookbook/middleware-as-contro…
Browse files Browse the repository at this point in the history
…ller

add cookbook for routed middleware class as controller with multi actions
  • Loading branch information
weierophinney committed Jan 7, 2016
2 parents d146c06 + 1beb501 commit 509e63b
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 1 deletion.
3 changes: 2 additions & 1 deletion doc/book/cookbook/bookdown.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
]
}
121 changes: 121 additions & 0 deletions doc/book/cookbook/using-routed-middleware-class-as-controller.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# 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, 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.

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;
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.

0 comments on commit 509e63b

Please sign in to comment.