asdf
This commit is contained in:
parent
7052803761
commit
bb13950966
107 changed files with 8372 additions and 186 deletions
102
app/data/pages/10-invoker.md
Normal file
102
app/data/pages/10-invoker.md
Normal file
|
@ -0,0 +1,102 @@
|
|||
[<< previous](09-dependency-injector.md) | [next >>](11-templating.md)
|
||||
|
||||
### Invoker
|
||||
|
||||
Currently all our Actions need to implement the RequestHandlerInterface, which forces us to accept the Request as the
|
||||
one and only argument to our handle function, but most of the time we only need a few attributes in our Action a long
|
||||
with some services and not the whole Requestobject with all its various properties.
|
||||
|
||||
If we take our Hello action for example we only need a response object, the time service and the 'name' information from
|
||||
the request-uri. And as that class only provides one simple method we could easily make that invokable as we alreay named
|
||||
the class hello and it would be redundant to also call the the method hello. So an updated version of that class could
|
||||
look like this:
|
||||
|
||||
```php
|
||||
final class Hello
|
||||
{
|
||||
public function __invoke(
|
||||
ResponseInterface $response,
|
||||
Now $now,
|
||||
string $name = 'Stranger',
|
||||
): ResponseInterface
|
||||
{
|
||||
$body = $this->response->getBody();
|
||||
$nowString = $now->get()->format('H:i:s');
|
||||
|
||||
$body->write('Hello ' . $name . '!');
|
||||
$body->write(' The Time is ' . $nowString);
|
||||
return $response
|
||||
->withBody($body)
|
||||
->withStatus(200);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
It would also be neat if we could define a classname plus a method as target handler in our routes, or even a short
|
||||
closure function if we want to redirect all requests from '/' to '/hello' because we have not defined a handler for the
|
||||
rootpath of our application yet.
|
||||
|
||||
```php
|
||||
$r->addRoute('GET', '/hello[/{name}]', Hello::class);
|
||||
$r->addRoute('GET', '/other-route', [Other::class, 'someFunctionName']);
|
||||
$r->addRoute('GET', '/', fn (Response $r) => $r->withStatus(302)->withHeader('Location', '/hello'));
|
||||
```
|
||||
|
||||
In order to support this crazy route definitions we would need to write a lot of for actually calling the result of the
|
||||
route dispatcher. If the result is a name of an invokable class we would use the container to create an instance of that
|
||||
class for us and then use the [reflection api](https://www.php.net/manual/en/book.reflection.php) to figure out what
|
||||
arguments the __invoke function has, try to fetch all arguments from the container and then add some more from the router
|
||||
if they are needed and available. The same if we have an array of a class name with a function to call, and for a simple
|
||||
callable we would need to manually use reflection as well to resolve all the arguments.
|
||||
|
||||
But we are quite lucky as the PHP-DI container provides us with a [great 'call' method](https://php-di.org/doc/container.html#call)
|
||||
which handles all of that for us.
|
||||
|
||||
After you added the described changes to your routes file you can modify the Dispatcher::FOUND case of you $routeInfo
|
||||
switch section in the Bootstrap.php file to use the container->call() method:
|
||||
|
||||
```php
|
||||
$handler = $routeInfo[1];
|
||||
$args = $routeInfo[2];
|
||||
foreach ($routeInfo[2] as $attributeName => $attributeValue) {
|
||||
$request = $request->withAttribute($attributeName, $attributeValue);
|
||||
}
|
||||
$args['request'] = $request;
|
||||
$response = $container->call($handler, $args);
|
||||
```
|
||||
|
||||
Try to open [localhost:1234/](http://localhost:1234/) in your browser and check if you are getting redirected to '/hello'.
|
||||
|
||||
But by now you should know that I do not like to depend on specific implementations and the call method is not defined in
|
||||
the psr/container interface. Therefore we would not be able to use that if we are ever switching to the symfony container
|
||||
or any other implementation.
|
||||
|
||||
Fortunately for us (or me) the PHP-CI container ships that function as its own class that is independent of the specific
|
||||
container implementation so we could use it with any container that implements the ContainerInterface. And best of all
|
||||
the class ships with its own [Interface](https://github.com/PHP-DI/Invoker/blob/master/src/InvokerInterface.php) that
|
||||
we could implement if we ever want to write our own implementation or we could write an adapter that uses a different
|
||||
class that solves the same problem.
|
||||
|
||||
But for now we are using the solution provided by PHP-DI.
|
||||
So lets request a Service implementing the InvokerInterface from the container and use that inside of the switch-case block
|
||||
|
||||
```php
|
||||
$handler = $routeInfo[1];
|
||||
$args = $routeInfo[2] ?? [];
|
||||
foreach ($routeInfo[2] as $attributeName => $attributeValue) {
|
||||
$request = $request->withAttribute($attributeName, $attributeValue);
|
||||
}
|
||||
$args['request'] = $request;
|
||||
$invoker = $container->get(InvokerInterface::class);
|
||||
assert($invoker instanceof InvokerInterface);
|
||||
$response = $invoker->call($handler, $args);
|
||||
assert($response instanceof ResponseInterface);
|
||||
```
|
||||
|
||||
Now we are able to define absolutely everything in routes that is considered a [callable](https://www.php.net/manual/de/language.types.callable.php)
|
||||
by php, and even some more.
|
||||
|
||||
But let us move on to something more fun and add some templating functionality to our application as we are trying to build
|
||||
a website in the end.
|
||||
|
||||
[<< previous](09-dependency-injector.md) | [next >>](11-templating.md)
|
Loading…
Add table
Add a link
Reference in a new issue