explain implementation of ad-hoc depencency container
This commit is contained in:
parent
2c3901e9f9
commit
5dc8ad38dd
8 changed files with 297 additions and 250 deletions
|
@ -119,6 +119,10 @@ the containerinterface.
|
|||
In this class we will configure all services required for our application and make them accessible via the get($id)
|
||||
method.
|
||||
|
||||
p
|
||||
Before we can implement the interface we need to install its definition with composer `composer require "psr/container:^1.0"`.
|
||||
now we can create a file with a Class that implements that interface.
|
||||
|
||||
`config/container.php`:
|
||||
```php
|
||||
<?php declare(strict_types=1);
|
||||
|
@ -199,214 +203,41 @@ If you now open the `/hello` route in your browser everything should work again!
|
|||
|
||||
#### Using Autowiring
|
||||
|
||||
If you take a critical look at the services array you might see that we need to manually define how our Hello- and
|
||||
Other-Action are getting constructed. This is quite repetitive, as we have already declared what objects to create
|
||||
when asking for the ResponseInterface and the Clock-Interface. We would need to write way less code, if our Container
|
||||
was smart enough to automatically figure our which services to Inject by looking at the constructor of a class.
|
||||
|
||||
PHP provides us with the great Reflection Api that is capable of showing us, [what arguments a constructor of any
|
||||
given class requires](https://www.php.net/manual/de/reflectionclass.getconstructor.php]. We could implement that
|
||||
functionality ourselves, or just try to use a library that takes care of that for us.
|
||||
|
||||
A dependency injector resolves the dependencies of your class and makes sure that the correct objects are injected when
|
||||
the class is instantiated.
|
||||
|
||||
Again the FIG has defined an [interface](https://www.php-fig.org/psr/psr-11/) for dependency injection that we can work
|
||||
with. Almost all common dependency injection containers implement this interface, so it is a good starting point to look
|
||||
for a [suitable solution on packagist](https://packagist.org/providers/psr/container-implementation).
|
||||
You can query the composer database to find all [libraries that implment the container interface](https://packagist.org/providers/psr/container-implementation).
|
||||
|
||||
I choose the [PHP-DI](https://packagist.org/packages/php-di/php-di) container, as it is easy to configure and provides some very [powerfull features](https://php-di.org/#autowiring)
|
||||
out of the box.
|
||||
out of the box, and also solves the autowiring problem.
|
||||
|
||||
After installing the container through composer create a new file with the name `dependencies.php` in your config folder:
|
||||
Lets rewrite our `container.php` file to use the PHP-DI container and only define the Services the Container cannot
|
||||
automatically build.
|
||||
|
||||
```php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
$builder = new \DI\ContainerBuilder();
|
||||
|
||||
$builder = new \DI\ContainerBuilder;
|
||||
|
||||
$builder->addDefinitions([
|
||||
\Psr\Http\Message\ResponseInterface::class => \DI\create(\Laminas\Diactoros\Response::class),
|
||||
\Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(),
|
||||
\Psr\Http\Message\ResponseInterface::class => fn () => new \Laminas\Diactoros\Response(),
|
||||
\FastRoute\Dispatcher::class => fn () => \FastRoute\simpleDispatcher(require __DIR__ . '/routes.php'),
|
||||
\Lubian\NoFramework\Service\Time\Clock::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClock(),
|
||||
]);
|
||||
|
||||
return $builder->build();
|
||||
```
|
||||
|
||||
In this file we create a containerbuilder, add some definitions to it and return the container.
|
||||
As the container supports autowiring we only need to define services where we want to use a specific implementation of
|
||||
an interface.
|
||||
As the PHP-DI container that is return by the `$builder->build()` method implements the same container interface as our
|
||||
previously used ad-hoc container we won't need to update the our Bootstrap file and everything still works.
|
||||
|
||||
In the example i used two different ways of defining the service. The first is by using the 'create' method of PHP-DI to
|
||||
tell the container that it should create a Diactoros\Response object when ever I query a ResponseInterface, in the second
|
||||
exampler I choose to write a small factory closure that wraps the Laminas Requestfactory.
|
||||
|
||||
Make sure to read the documentation on definition types on the [PHP-DI website](https://php-di.org/doc/php-definitions.html#definition-types),
|
||||
as we will use that extensively.
|
||||
|
||||
Of course your `Bootstrap.php` will also need to be changed. Before you were setting up `$request` and `$response` with `new` calls. Switch that to the dependency container. We do not need to get the response here, as the container will create and use it internally
|
||||
to create our Handler-Object
|
||||
|
||||
```php
|
||||
$container = require __DIR__ . '/../config/dependencies.php';
|
||||
assert($container instanceof \Psr\Container\ContainerInterface);
|
||||
|
||||
$request = $container->get(\Psr\Http\Message\ServerRequestInterface::class);
|
||||
assert($request instanceof \Psr\Http\Message\ServerRequestInterface);
|
||||
```
|
||||
|
||||
The other part that has to be changed is the dispatching of the route. Before you had the following code:
|
||||
|
||||
```php
|
||||
$className = $routeInfo[1];
|
||||
$handler = new $className($response);
|
||||
assert($handler instanceof \Psr\Http\Server\RequestHandlerInterface)
|
||||
foreach ($routeInfo[2] as $attributeName => $attributeValue) {
|
||||
$request = $request->withAttribute($attributeName, $attributeValue);
|
||||
}
|
||||
$response = $handler->handle($request);
|
||||
```
|
||||
|
||||
Change that to the following:
|
||||
|
||||
```php
|
||||
/** @var RequestHandlerInterface $handler */
|
||||
$className = $routeInfo[1];
|
||||
$handler = $container->get($className);
|
||||
assert($handler instanceof RequestHandlerInterface);
|
||||
foreach ($routeInfo[2] as $attributeName => $attributeValue) {
|
||||
$request = $request->withAttribute($attributeName, $attributeValue);
|
||||
}
|
||||
$response = $handler->handle($request);
|
||||
```
|
||||
|
||||
Make sure to use the container fetch the response object in the catch blocks as well:
|
||||
|
||||
```php
|
||||
} catch (MethodNotAllowed) {
|
||||
$response = $container->get(ResponseInterface::class);
|
||||
assert($response instanceof ResponseInterface);
|
||||
$response = $response->withStatus(405);
|
||||
$response->getBody()->write('Not Allowed');
|
||||
} catch (NotFound) {
|
||||
$response = $container->get(ResponseInterface::class);
|
||||
assert($response instanceof ResponseInterface);
|
||||
$response = $response->withStatus(404);
|
||||
$response->getBody()->write('Not Found');
|
||||
}
|
||||
```
|
||||
|
||||
Now all your controller constructor dependencies will be automatically resolved with PHP-DI.
|
||||
|
||||
We can now use that to inject all kinds of services. Often we need to work with the Current time to do some comparisons
|
||||
in an application. Of course we are writing S.O.L.I.D. and testable code so that we would never be so crazy as to call
|
||||
`$time = new \DateTimeImmutable();` in our Action directly, because then we would need to change the system time of we
|
||||
want to work with a different date in a test.
|
||||
|
||||
Therefore we are creating a new Namespace called 'Service\Time' where we introduce a Now-Interface and an Implementation
|
||||
that creates us a DateTimeImmutable object with the current date and time.
|
||||
|
||||
src/Service/Time/Now.php:
|
||||
```php
|
||||
namespace Lubian\NoFramework\Service\Time;
|
||||
|
||||
interface Now
|
||||
{
|
||||
public function __invoke(): \DateTimeImmutable;
|
||||
}
|
||||
```
|
||||
src/Service/Time/SystemClockNow.php:
|
||||
```php
|
||||
namespace Lubian\NoFramework\Service\Time;
|
||||
|
||||
final class SystemClockNow implements Now
|
||||
{
|
||||
|
||||
public function __invoke(): \DateTimeImmutable
|
||||
{
|
||||
return new \DateTimeImmutable;
|
||||
}
|
||||
}
|
||||
```
|
||||
If we want to use that Service in our HelloAction we just need to add it as another argument for the Constructor and
|
||||
update the handle-method to use the new class property:
|
||||
|
||||
```php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lubian\NoFramework\Action;
|
||||
|
||||
use Lubian\NoFramework\Service\Time\SystemClockNow;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
final class Hello implements RequestHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ResponseInterface $response,
|
||||
private SystemClockNow $now,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$name = $request->getAttribute('name', 'Stranger');
|
||||
$nowAsString = ($this->now)()->format('H:i:s');
|
||||
$body = $this->response->getBody();
|
||||
|
||||
$body->write('Hello ' . $name . '!');
|
||||
$body->write(' The Time is ' . $nowAsString);
|
||||
|
||||
return $this->response
|
||||
->withBody($body)
|
||||
->withStatus(200);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you open the route in your browser you should see that the current time gets displayed. This happens because PHP-DI
|
||||
automatically figures out what classes are requested in the constructor and tries to create the objects needed.
|
||||
|
||||
But we do not want to depend on the SystemClockNow implementation in our class because that would violate our sacred
|
||||
S.O.L.I.D. principles therefore we need to change the Typehint to the Now interface:
|
||||
|
||||
```php
|
||||
public function __construct(
|
||||
private ResponseInterface $response,
|
||||
private Now $now,
|
||||
)
|
||||
```
|
||||
|
||||
When we are now accessing the Handler in the Browser we get an Error because we have not defined which implementation
|
||||
should be use to satisfy dependencies on the Now interface. So lets add that definition to our dependencies file:
|
||||
|
||||
```php
|
||||
\Lubian\NoFramework\Service\Time\Now::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClockNow(),
|
||||
```
|
||||
|
||||
we could also use the PHP-DI create method to delegate the object creation to the container implementation:
|
||||
```php
|
||||
\Lubian\NoFramework\Service\Time\Now::class => DI\create(\Lubian\NoFramework\Service\Time\SystemClockNow::class),
|
||||
```
|
||||
|
||||
this way the container can try to resolve any dependencies that the class might have internally, but prefer the other
|
||||
method because we are not depending on this specific dependency injection implementation.
|
||||
|
||||
Either way the container should now be able to correctly resolve the dependency on the Now interfacen when you are
|
||||
requesting the Hello action.
|
||||
|
||||
If you run phpstan now, you will get some errors, because the get method on the ContainerInterface returns 'mixed'. As
|
||||
we will adress these issues later, lets tell phpstan that we know about the issue and we can ignore it for now. This way
|
||||
we wont get any warnings for this particular issue, but for any other issues we add to our code.
|
||||
|
||||
Update the phpstan.neon file to include a "baseline" file:
|
||||
|
||||
```
|
||||
includes:
|
||||
- phpstan-baseline.neon
|
||||
|
||||
parameters:
|
||||
level: 9
|
||||
paths:
|
||||
- src
|
||||
```
|
||||
|
||||
if we run phpstan with './vendor/bin/phpstan analyse --generate-baseline' it will add all current errors to that file and
|
||||
ignore them in the future. You can also add that command to your composer.json for easier access. I have called it just
|
||||
'baseline'
|
||||
|
||||
[<< previous](08-inversion-of-control.md) | [next >>](10-invoker.md)
|
|
@ -17,7 +17,8 @@
|
|||
"laminas/laminas-diactoros": "^2.11",
|
||||
"nikic/fast-route": "^1.3",
|
||||
"psr/http-server-handler": "^1.0",
|
||||
"psr/container": "^2.0"
|
||||
"psr/container": "^1.0",
|
||||
"php-di/php-di": "^6.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.6",
|
||||
|
|
255
app/composer.lock
generated
255
app/composer.lock
generated
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "1ccaabdd7944ba2f12098b7b2f1c91c2",
|
||||
"content-hash": "0b6833b8fa6869bd212824769648e667",
|
||||
"packages": [
|
||||
{
|
||||
"name": "filp/whoops",
|
||||
|
@ -176,6 +176,65 @@
|
|||
],
|
||||
"time": "2022-05-17T10:57:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/serializable-closure",
|
||||
"version": "v1.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/serializable-closure.git",
|
||||
"reference": "09f0e9fb61829f628205b7c94906c28740ff9540"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/serializable-closure/zipball/09f0e9fb61829f628205b7c94906c28740ff9540",
|
||||
"reference": "09f0e9fb61829f628205b7c94906c28740ff9540",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.3|^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"pestphp/pest": "^1.18",
|
||||
"phpstan/phpstan": "^0.12.98",
|
||||
"symfony/var-dumper": "^5.3"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laravel\\SerializableClosure\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
},
|
||||
{
|
||||
"name": "Nuno Maduro",
|
||||
"email": "nuno@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.",
|
||||
"keywords": [
|
||||
"closure",
|
||||
"laravel",
|
||||
"serializable"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/serializable-closure/issues",
|
||||
"source": "https://github.com/laravel/serializable-closure"
|
||||
},
|
||||
"time": "2022-05-16T17:09:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nikic/fast-route",
|
||||
"version": "v1.3.0",
|
||||
|
@ -227,28 +286,196 @@
|
|||
"time": "2018-02-13T20:26:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
"version": "2.0.2",
|
||||
"name": "php-di/invoker",
|
||||
"version": "2.3.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/container.git",
|
||||
"reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
|
||||
"url": "https://github.com/PHP-DI/Invoker.git",
|
||||
"reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
|
||||
"reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
|
||||
"url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/cd6d9f267d1a3474bdddf1be1da079f01b942786",
|
||||
"reference": "cd6d9f267d1a3474bdddf1be1da079f01b942786",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.3",
|
||||
"psr/container": "^1.0|^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"athletic/athletic": "~0.1.8",
|
||||
"mnapoli/hard-mode": "~0.3.0",
|
||||
"phpunit/phpunit": "^9.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Invoker\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Generic and extensible callable invoker",
|
||||
"homepage": "https://github.com/PHP-DI/Invoker",
|
||||
"keywords": [
|
||||
"callable",
|
||||
"dependency",
|
||||
"dependency-injection",
|
||||
"injection",
|
||||
"invoke",
|
||||
"invoker"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHP-DI/Invoker/issues",
|
||||
"source": "https://github.com/PHP-DI/Invoker/tree/2.3.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/mnapoli",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2021-12-13T09:22:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "php-di/php-di",
|
||||
"version": "6.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHP-DI/PHP-DI.git",
|
||||
"reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/ae0f1b3b03d8b29dff81747063cbfd6276246cc4",
|
||||
"reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"laravel/serializable-closure": "^1.0",
|
||||
"php": ">=7.4.0",
|
||||
"php-di/invoker": "^2.0",
|
||||
"php-di/phpdoc-reader": "^2.0.1",
|
||||
"psr/container": "^1.0"
|
||||
},
|
||||
"provide": {
|
||||
"psr/container-implementation": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/annotations": "~1.10",
|
||||
"friendsofphp/php-cs-fixer": "^2.4",
|
||||
"mnapoli/phpunit-easymock": "^1.2",
|
||||
"ocramius/proxy-manager": "^2.11.2",
|
||||
"phpstan/phpstan": "^0.12",
|
||||
"phpunit/phpunit": "^9.5"
|
||||
},
|
||||
"suggest": {
|
||||
"doctrine/annotations": "Install it if you want to use annotations (version ~1.2)",
|
||||
"ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/functions.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"DI\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "The dependency injection container for humans",
|
||||
"homepage": "https://php-di.org/",
|
||||
"keywords": [
|
||||
"PSR-11",
|
||||
"container",
|
||||
"container-interop",
|
||||
"dependency injection",
|
||||
"di",
|
||||
"ioc",
|
||||
"psr11"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHP-DI/PHP-DI/issues",
|
||||
"source": "https://github.com/PHP-DI/PHP-DI/tree/6.4.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/mnapoli",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/php-di/php-di",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-04-09T16:46:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "php-di/phpdoc-reader",
|
||||
"version": "2.2.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHP-DI/PhpDocReader.git",
|
||||
"reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c",
|
||||
"reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mnapoli/hard-mode": "~0.3.0",
|
||||
"phpunit/phpunit": "^8.5|^9.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PhpDocReader\\": "src/PhpDocReader"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)",
|
||||
"keywords": [
|
||||
"phpdoc",
|
||||
"reflection"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHP-DI/PhpDocReader/issues",
|
||||
"source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1"
|
||||
},
|
||||
"time": "2020-10-12T12:39:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
"version": "1.1.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/container.git",
|
||||
"reference": "513e0666f7216c7459170d56df27dfcefe1689ea"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea",
|
||||
"reference": "513e0666f7216c7459170d56df27dfcefe1689ea",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.4.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Container\\": "src/"
|
||||
|
@ -275,9 +502,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/php-fig/container/issues",
|
||||
"source": "https://github.com/php-fig/container/tree/2.0.2"
|
||||
"source": "https://github.com/php-fig/container/tree/1.1.2"
|
||||
},
|
||||
"time": "2021-11-05T16:47:00+00:00"
|
||||
"time": "2021-11-05T16:50:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-factory",
|
||||
|
|
|
@ -1,37 +1,23 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
return new class () implements \Psr\Container\ContainerInterface {
|
||||
use DI\ContainerBuilder;
|
||||
use FastRoute\Dispatcher;
|
||||
use Laminas\Diactoros\Response;
|
||||
use Laminas\Diactoros\ServerRequestFactory;
|
||||
use Lubian\NoFramework\Service\Time\Clock;
|
||||
use Lubian\NoFramework\Service\Time\SystemClock;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
private readonly array $services;
|
||||
use function FastRoute\simpleDispatcher;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->services = [
|
||||
\Psr\Http\Message\ServerRequestInterface::class => fn () => \Laminas\Diactoros\ServerRequestFactory::fromGlobals(),
|
||||
\Psr\Http\Message\ResponseInterface::class => fn () => new \Laminas\Diactoros\Response(),
|
||||
\FastRoute\Dispatcher::class => fn () => \FastRoute\simpleDispatcher(require __DIR__ . '/routes.php'),
|
||||
\Lubian\NoFramework\Service\Time\Clock::class => fn () => new \Lubian\NoFramework\Service\Time\SystemClock(),
|
||||
\Lubian\NoFramework\Action\Hello::class => fn () => new \Lubian\NoFramework\Action\Hello(
|
||||
$this->get(\Psr\Http\Message\ResponseInterface::class),
|
||||
$this->get(\Lubian\NoFramework\Service\Time\Clock::class)
|
||||
),
|
||||
\Lubian\NoFramework\Action\Other::class => fn () => new \Lubian\NoFramework\Action\Other(
|
||||
$this->get(\Psr\Http\Message\ResponseInterface::class)
|
||||
),
|
||||
];
|
||||
}
|
||||
$builder = new ContainerBuilder;
|
||||
|
||||
public function get(string $id)
|
||||
{
|
||||
if (! $this->has($id)) {
|
||||
throw new class () extends \Exception implements \Psr\Container\NotFoundExceptionInterface {
|
||||
};
|
||||
}
|
||||
return $this->services[$id]();
|
||||
}
|
||||
$builder->addDefinitions([
|
||||
ServerRequestInterface::class => fn () => ServerRequestFactory::fromGlobals(),
|
||||
ResponseInterface::class => fn () => new Response,
|
||||
Dispatcher::class => fn () => simpleDispatcher(require __DIR__ . '/routes.php'),
|
||||
Clock::class => fn () => new SystemClock,
|
||||
]);
|
||||
|
||||
public function has(string $id): bool
|
||||
{
|
||||
return array_key_exists($id, $this->services);
|
||||
}
|
||||
};
|
||||
return $builder->build();
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Lubian\NoFramework\Action;
|
||||
|
||||
|
||||
use Lubian\NoFramework\Service\Time\Clock;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
@ -13,8 +12,7 @@ final class Hello implements RequestHandlerInterface
|
|||
public function __construct(
|
||||
private readonly ResponseInterface $response,
|
||||
private readonly Clock $clock
|
||||
)
|
||||
{
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
|
@ -22,7 +20,8 @@ final class Hello implements RequestHandlerInterface
|
|||
$name = $request->getAttribute('name', 'Stranger');
|
||||
$body = $this->response->getBody();
|
||||
|
||||
$time = $this->clock->now()->format('H:i:s');
|
||||
$time = $this->clock->now()
|
||||
->format('H:i:s');
|
||||
|
||||
$body->write('Hello ' . $name . '!<br />');
|
||||
$body->write('The Time is: ' . $time);
|
||||
|
|
|
@ -59,7 +59,7 @@ try {
|
|||
switch ($routeInfo[0]) {
|
||||
case Dispatcher::FOUND:
|
||||
$className = $routeInfo[1];
|
||||
$handler = new $className($response);
|
||||
$handler = $container->get($className);
|
||||
assert($handler instanceof RequestHandlerInterface);
|
||||
foreach ($routeInfo[2] as $attributeName => $attributeValue) {
|
||||
$request = $request->withAttribute($attributeName, $attributeValue);
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
|
||||
namespace Lubian\NoFramework\Service\Time;
|
||||
|
||||
use DateTimeImmutable;
|
||||
|
||||
interface Clock
|
||||
{
|
||||
public function now(): \DateTimeImmutable;
|
||||
public function now(): DateTimeImmutable;
|
||||
}
|
|
@ -2,11 +2,12 @@
|
|||
|
||||
namespace Lubian\NoFramework\Service\Time;
|
||||
|
||||
use DateTimeImmutable;
|
||||
|
||||
final class SystemClock implements Clock
|
||||
{
|
||||
public function now(): \DateTimeImmutable
|
||||
public function now(): DateTimeImmutable
|
||||
{
|
||||
return new \DateTimeImmutable();
|
||||
return new DateTimeImmutable;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue