add data from work folder
This commit is contained in:
parent
528ba365b4
commit
7052803761
218 changed files with 16123 additions and 1233 deletions
137
07-dispatching-to-a-class.md
Normal file
137
07-dispatching-to-a-class.md
Normal file
|
@ -0,0 +1,137 @@
|
|||
[<< previous](06-router.md) | [next >>](08-inversion-of-control.md)
|
||||
|
||||
### Dispatching to a Class
|
||||
|
||||
In this tutorial we won't implement [MVC (Model-View-Controller)](http://martinfowler.com/eaaCatalog/modelViewController.html).
|
||||
MVC can't be implemented properly in PHP anyway, at least not in the way it was originally conceived. If you want to
|
||||
learn more about this, read [A Beginner's Guide To MVC](http://blog.ircmaxell.com/2014/11/a-beginners-guide-to-mvc-for-web.html)
|
||||
and the followup posts.
|
||||
|
||||
So forget about MVC and instead let's worry about [separation of concerns](http://en.wikipedia.org/wiki/Separation_of_concerns).
|
||||
|
||||
We will need a descriptive name for the classes that handle the requests. For this tutorial I will use `Handler`, other
|
||||
common names are 'Controllers' or 'Actions'.
|
||||
|
||||
Create a new folder inside the `src/` folder with the name `Handler`.In this folder we will place all our action classes.
|
||||
In there, create a `Hello.php` file.
|
||||
|
||||
```php
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace Lubian\NoFramework\Action;
|
||||
|
||||
final class Hello implements \Psr\Http\Server\RequestHandlerInterface
|
||||
{
|
||||
public function handle(\Psr\Http\Message\ServerRequestInterface $request): \Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
$name = $request->getAttribute('name', 'Stranger');
|
||||
$response = (new \Laminas\Diactoros\Response)->withStatus(200);
|
||||
$response->getBody()->write('Hello ' . $name . '!');
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can see that we implement the [RequestHandlerInterface](https://github.com/php-fig/http-server-handler/blob/master/src/RequestHandlerInterface.php)
|
||||
that has a 'handle'-Method with requires a Requestobject as its parameter and returns a Responseobject. For now this is
|
||||
fine, but we may have to change our approach later. In anyway it is good to know about this interface as we will implement
|
||||
it in some other parts of our application as well. In order to use that Interface we have to require it with composer:
|
||||
'composer require psr/http-server-handler'.
|
||||
|
||||
The autoloader will only work if the namespace of a class matches the file path and the file name equals the class name.
|
||||
At the beginning I defined `Lubian\NoFramework` as the root namespace of the application so this is referring to the `src/` folder.
|
||||
|
||||
Now let's change the hello world route so that it calls your new class method instead of the closure. Change your `routes.php` to this:
|
||||
|
||||
```php
|
||||
return function(\FastRoute\RouteCollector $r) {
|
||||
$r->addRoute('GET', '/hello[/{name}]', \Lubian\NoFramework\Action\Hello::class);
|
||||
$r->addRoute('GET', '/another-route', \Lubian\NoFramework\Action\Another::class);
|
||||
};
|
||||
```
|
||||
|
||||
Instead of a callable we are now passing the fully namespaced class identifier to the route-definition. I also declared
|
||||
the class 'Another' as the target for the second route, you can create it by copying the Hello.php file and changing
|
||||
the response to the one we defined for the second route.
|
||||
|
||||
To make this work, you will also have to do a small refactor to the routing part of the `Bootstrap.php`:
|
||||
|
||||
```php
|
||||
case \FastRoute\Dispatcher::FOUND:
|
||||
$handler = new $routeInfo[1];
|
||||
if (! $handler instanceof \Psr\Http\Server\RequestHandlerInterface) {
|
||||
throw new \Exception('Invalid Requesthandler');
|
||||
}
|
||||
foreach ($routeInfo[2] as $attributeName => $attributeValue) {
|
||||
$request = $request->withAttribute($attributeName, $attributeValue);
|
||||
}
|
||||
$response = $handler->handle($request);
|
||||
assert($response instanceof \Psr\Http\Message\ResponseInterface)
|
||||
break;
|
||||
```
|
||||
|
||||
So instead of just calling a method you are now instantiating an object and then calling the method on it.
|
||||
|
||||
Now if you visit `http://localhost:1234/` everything should work. If not, go back and debug.
|
||||
|
||||
And of course don't forget to commit your changes.
|
||||
|
||||
Something that still bothers me is the fact, that we do have classes for our Handlers, but the Error responses are still
|
||||
generated in the routing-matching section and not in special classes. Also we have still left some cases to chance, for
|
||||
example if there is an error in creating our RequestHandler class or if the call to the 'handle' function fails. We still
|
||||
have our whoopsie error-handler but i like to be more explicit in my control flow.
|
||||
|
||||
In order to do that we need to define some special Exceptions that we can throw and catch explicitly. Lets add a new
|
||||
Folder/Namespace to our src directory called Exceptions. And define the classes NotFound, MethodNotAllowed and
|
||||
InternalServerError. All three should extend phps Base Exception class.
|
||||
|
||||
Here is my NotFound.php for example.
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Exception;
|
||||
|
||||
final class NotFound extends Exception{}
|
||||
```
|
||||
|
||||
Use that example to create a MethodNotAllowedException.php and InternalServerErrorException.php as well.
|
||||
|
||||
After you have created those we update our Routercode to use the new Exceptions.
|
||||
|
||||
```php
|
||||
try {
|
||||
switch ($routeInfo[0]) {
|
||||
case Dispatcher::FOUND:
|
||||
$className = $routeInfo[1];
|
||||
$handler = new $className;
|
||||
assert($handler instanceof RequestHandlerInterface);
|
||||
foreach ($routeInfo[2] as $attributeName => $attributeValue) {
|
||||
$request = $request->withAttribute($attributeName, $attributeValue);
|
||||
}
|
||||
$response = $handler->handle($request);
|
||||
break;
|
||||
case Dispatcher::METHOD_NOT_ALLOWED:
|
||||
throw new MethodNotAllowed;
|
||||
|
||||
case Dispatcher::NOT_FOUND:
|
||||
default:
|
||||
throw new NotFound;
|
||||
}
|
||||
} catch (MethodNotAllowed) {
|
||||
$response = (new Response)->withStatus(405);
|
||||
$response->getBody()->write('Not Allowed');
|
||||
} catch (NotFound) {
|
||||
$response = (new Response)->withStatus(404);
|
||||
$response->getBody()->write('Not Found');
|
||||
} catch (Throwable $t) {
|
||||
throw new InternalServerError($t->getMessage(), $t->getCode(), $t);
|
||||
}
|
||||
```
|
||||
|
||||
Check if our code still works, try to trigger some errors, run phpstan and the fix command
|
||||
and don't forget to commit your changes.
|
||||
|
||||
[<< previous](06-router.md) | [next >>](08-inversion-of-control.md)
|
Loading…
Add table
Add a link
Reference in a new issue