simplify chapter 12
This commit is contained in:
parent
350f8e18b9
commit
f857fa4752
1 changed files with 59 additions and 157 deletions
|
@ -4,197 +4,99 @@
|
||||||
|
|
||||||
In the last chapter we added some more definitions to our dependencies.php in that definitions
|
In the last chapter we added some more definitions to our dependencies.php in that definitions
|
||||||
we needed to pass quite a few configuration settings and filesystem strings to the constructors
|
we needed to pass quite a few configuration settings and filesystem strings to the constructors
|
||||||
of the classes. This might work for a small projects, but if we are growing we want to source that out to a more explicit file that holds all the configuration valuse for our project.
|
of the classes. This might work for a small projects, but if we are growing we want to source that out to a more
|
||||||
|
explicit file that holds all the configuration values for our project.
|
||||||
|
|
||||||
As this is not a problem unique to our project there are already a some options available. Some projects use [.env](https://github.com/vlucas/phpdotenv) files, others use [.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is [yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex Readers for many configuration file formats that can be used, take a look at the [laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example.
|
As this is not a problem unique to our project there are already a some options available. Some projects use
|
||||||
|
[.env](https://github.com/vlucas/phpdotenv) files, others use
|
||||||
|
[.ini](https://www.php.net/manual/de/function.parse-ini-file.php), there is
|
||||||
|
[yaml](https://www.php.net/manual/de/function.yaml-parse-file.php) as well some frameworks have implemented complex
|
||||||
|
Readers for many configuration file formats that can be used, take a look at the
|
||||||
|
[laminas config component](https://docs.laminas.dev/laminas-config/reader/) for example.
|
||||||
|
|
||||||
As i am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am quite happy the PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at [this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic before moving on.
|
As I am a big fan of writing everything in php, which gives our IDE the chance to autocomplete our code better I am
|
||||||
|
quite happy that PHP8 gives us some tools to achieve easy to use configuration via php. You can take a look at
|
||||||
|
[this blogpost](https://stitcher.io/blog/what-about-config-builders) to read about some considerations on that topic
|
||||||
|
before moving on.
|
||||||
|
|
||||||
Lets create a 'Settings' class in our './src' Folder:
|
For the purpose of this Tutorial I will use a simple ValueObject that has all our configuration values as properties.
|
||||||
|
create a `Configuration.php` class in the `./src` folder:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace Lubian\NoFramework;
|
namespace Lubian\NoFramework;
|
||||||
|
|
||||||
final class Settings
|
final class Configuration
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public readonly string $environment,
|
public readonly string $environment = 'dev',
|
||||||
public readonly string $dependenciesFile,
|
public readonly string $routesFile = __DIR__ . '/../config/routes.php',
|
||||||
public readonly string $templateDir,
|
public readonly string $templateDir = __DIR__ . '/../templates',
|
||||||
public readonly string $templateExtension,
|
public readonly string $templateExtension = '.html',
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
I am using a new Feature from PHP 8.1 here called [readonly properties](https://stitcher.io/blog/php-81-readonly-properties) to write a small valueobject without the need to write complex getters and setters. The linked article gives a great explanation on how they work.
|
I am using a new Feature from PHP 8.1 here called
|
||||||
|
[readonly properties](https://stitcher.io/blog/php-81-readonly-properties) to write a small valueobject without the need
|
||||||
|
to write complex getters and setters. The linked article gives a great explanation on how they work. You can see, that
|
||||||
|
I have added working default values for every configuration parameter. In my personal opinion, project should always
|
||||||
|
have working default values without you needing to set up anything. This greatly improves usability and reduces errors.
|
||||||
|
|
||||||
When creating an instance of the setting class with my project specific values i will use another
|
We can now update our `container.php` file to use the configuration. Currently, the Mustache_Loader, as well as the
|
||||||
new feature called [named arguments](https://stitcher.io/blog/php-8-named-arguments). There is
|
Fastroute-Dispatcher use values that we have defined in our Configuration, lets update those definitions:
|
||||||
a lot of discussion on the topic of named arguments as some argue it creates unclean and
|
|
||||||
unmaintainable code, but vor simple valueobjects i would argue that they are ok.
|
|
||||||
|
|
||||||
here is a small example of creating a settings object using named arguments that I placed in the config folder
|
```php
|
||||||
under the name settings.php
|
Dispatcher::class => fn (Configuration $c) => simpleDispatcher(require $c->routesFile),
|
||||||
|
Mustache_Loader_FilesystemLoader::class => fn (Configuration $c) => new Mustache_Loader_FilesystemLoader(
|
||||||
|
$c->templateDir,
|
||||||
|
[
|
||||||
|
'extension' => $c->templateExtension,
|
||||||
|
]
|
||||||
|
),
|
||||||
|
```
|
||||||
|
|
||||||
|
Magically this is all we need to do, as the PHP-DI container knows that all constructor parameters of our configuration
|
||||||
|
class have default values and can create the needed object on its own.
|
||||||
|
|
||||||
|
There is one small problem: If we want to change environment from `dev` to `prod` we would need to update the
|
||||||
|
configuration class in the src directory. This is something we don't want to do on every deployment. So lets add a file
|
||||||
|
in our `./config` directory called `settings.php` that returns a Configuration object.
|
||||||
```php
|
```php
|
||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
use Lubian\NoFramework\Settings;
|
return new \Lubian\NoFramework\Configuration(
|
||||||
|
environment: 'prod',
|
||||||
return new Settings(
|
|
||||||
environment: 'dev',
|
|
||||||
dependenciesFile: __DIR__ . '/dependencies.php',
|
|
||||||
templateDir: __DIR__ . '/../templates',
|
|
||||||
templateExtension: '.html',
|
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
here I am using a new feature called [named arguments](https://stitcher.io/blog/php-8-named-arguments). There is
|
||||||
|
a lot of discussion on the topic of named arguments as some argue it creates unclean and
|
||||||
|
unmaintainable code, but for simple value-objects I would argue that they are ok.
|
||||||
|
|
||||||
But now we need some more code to include that settings Object and make it available in our container. As I don't want to use requires and includes too much in the dependencies configuration we are going to create a Factory class that gives us an Instance of the config file.
|
We now need to add a line to our container to use the `settings.php` file to create the Configuration-object:
|
||||||
|
|
||||||
Lets define our Interface first. That way we can later switch to another implementation that creates our Settings object.
|
|
||||||
|
|
||||||
src/Factory/SettingsProvider.php:
|
|
||||||
|
|
||||||
```php
|
```php
|
||||||
<?php declare(strict_types=1);
|
\Lubian\NoFramework\Configuration::class => fn () => require __DIR__ . '/settings.php',
|
||||||
|
|
||||||
namespace Lubian\NoFramework\Factory;
|
|
||||||
|
|
||||||
use Lubian\NoFramework\Settings;
|
|
||||||
|
|
||||||
interface SettingsProvider
|
|
||||||
{
|
|
||||||
public function getSettings(): Settings;
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
And write a simple implementation that uses our settings.php to provide our App with the Settingsobject:
|
One small oversight to fix is in the registration of our error-handler in the bootstrap-file. There we read the
|
||||||
|
environment with the getenv-method. Lets change the line:
|
||||||
```php
|
```php
|
||||||
<?php declare(strict_types=1);
|
$environment = getenv('ENVIRONMENT') ?: 'dev';
|
||||||
|
|
||||||
namespace Lubian\NoFramework\Factory;
|
|
||||||
|
|
||||||
use Lubian\NoFramework\Settings;
|
|
||||||
|
|
||||||
final class FileSystemSettingsProvider implements SettingsProvider
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private string $filePath
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSettings(): Settings
|
|
||||||
{
|
|
||||||
return require $this->filePath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
If we later want to use yaml or ini files for our Settings we can easily write a different provider to read those files
|
to:
|
||||||
and craft a settings object from them.
|
|
||||||
|
|
||||||
As we have now created a completely new Namespace and Folder and our SettingsProvider is all alone we could add another
|
|
||||||
factory for our Container because everyone should have a Friend :)
|
|
||||||
|
|
||||||
```php
|
```php
|
||||||
<?php declare(strict_types=1);
|
$config = require __DIR__ . '/../config/settings.php';
|
||||||
|
assert($config instanceof \Lubian\NoFramework\Configuration);
|
||||||
namespace Lubian\NoFramework\Factory;
|
$environment = $config->environment;
|
||||||
|
|
||||||
use Psr\Container\ContainerInterface;
|
|
||||||
|
|
||||||
interface ContainerProvider
|
|
||||||
{
|
|
||||||
public function getContainer(): ContainerInterface;
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
And a simple implementation that uses our new Settingsprovider to build the container:
|
Check if everything still works, run your code quality checks and commit the changes before moving on the next chapter.
|
||||||
|
You might notice that phpstan throws an error as there is a documented violation missing. You can either regenerate the
|
||||||
```php
|
baseline, or simply remove that line from the `phpstan-baseline.neon` file.
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Lubian\NoFramework\Factory;
|
|
||||||
|
|
||||||
use DI\ContainerBuilder;
|
|
||||||
use Lubian\NoFramework\Settings;
|
|
||||||
use Psr\Container\ContainerInterface;
|
|
||||||
|
|
||||||
final class SettingsContainerProvider implements ContainerProvider
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private SettingsProvider $settingsProvider,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getContainer(): ContainerInterface
|
|
||||||
{
|
|
||||||
$builder = new ContainerBuilder;
|
|
||||||
$settings = $this->settingsProvider->getSettings();
|
|
||||||
$dependencies = require $settings->dependenciesFile;
|
|
||||||
$dependencies[Settings::class] = fn () => $settings;
|
|
||||||
$builder->addDefinitions($dependencies);
|
|
||||||
return $builder->build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
For this to work we need to change our dependencies.php file to just return the array of definitions:
|
|
||||||
And here we can instantly use the Settings object to create our template engine.
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
use Laminas\Diactoros\ResponseFactory;
|
|
||||||
use Laminas\Diactoros\ServerRequestFactory;
|
|
||||||
use Lubian\NoFramework\Service\Time\Now;
|
|
||||||
use Lubian\NoFramework\Service\Time\SystemClockNow;
|
|
||||||
use Lubian\NoFramework\Settings;
|
|
||||||
use Lubian\NoFramework\Template\MustacheRenderer;
|
|
||||||
use Lubian\NoFramework\Template\Renderer;
|
|
||||||
use Mustache_Engine as ME;
|
|
||||||
use Mustache_Loader_FilesystemLoader as MLF;
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
|
||||||
|
|
||||||
return [
|
|
||||||
ResponseInterface::class => fn (ResponseFactory $rf) => $rf->createResponse(),
|
|
||||||
ServerRequestInterface::class => fn (ServerRequestFactory $rf) => $rf::fromGlobals(),
|
|
||||||
Now::class => fn (SystemClockNow $n) => $n,
|
|
||||||
Renderer::class => fn (Mustache_Engine $e) => new MustacheRenderer($e),
|
|
||||||
MLF::class => fn (Settings $s) => new MLF($s->templateDir, ['extension' => $s->templateExtension]),
|
|
||||||
ME::class => fn (MLF $mfl) => new ME(['loader' => $mfl]),
|
|
||||||
];
|
|
||||||
```
|
|
||||||
|
|
||||||
Now we can change our Bootstrap.php file to use the new Factories for the creation of the Initial Objects:
|
|
||||||
|
|
||||||
```php
|
|
||||||
...
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
|
|
||||||
$settingsProvider = new FileSystemSettingsProvider(__DIR__ . '/../config/settings.php');
|
|
||||||
$container = (new SettingsContainerProvider($settingsProvider))->getContainer();
|
|
||||||
|
|
||||||
$settings = $settingsProvider->getSettings();
|
|
||||||
|
|
||||||
$whoops = new Run;
|
|
||||||
if ($settings->environment === 'dev') {
|
|
||||||
$whoops->pushHandler(new PrettyPageHandler);
|
|
||||||
} else {
|
|
||||||
$whoops->pushHandler(function (Throwable $e): void {
|
|
||||||
error_log('Error: ' . $e->getMessage(), $e->getCode());
|
|
||||||
echo 'An Error happened';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
$whoops->register();
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
Check if everything still works, run your code quality checks and commit the changes before moving on the the next chapter.
|
|
||||||
|
|
||||||
[<< previous](11-templating.md) | [next >>](13-refactoring.md)
|
[<< previous](11-templating.md) | [next >>](13-refactoring.md)
|
||||||
|
|
Loading…
Reference in a new issue