Config: Container PSR-11 and Dependency Injection
A very basic and minimalist PSR-11 implementation for config management and dependency injection.
How it Works?
The container is created based on the configuration you created (dev, homolog, test, live, …) defined in array and .env
files;
See below how to setup:
Setup files
Create in your project root at the same level of the vendor directory a folder called config
.
Inside these folders create files called "config-dev.php", "config-test.php" where dev, test, live, etc are your configuration sets.
Your folder will look like to:
<project root>
|
+-- config
|
+ .env
+ config-dev.php
+ config-dev.env
+ config-homolog.php
+ config-homolog.env
+ config-test.php
+ config-live.php
+-- vendor
+-- composer.json
Select the configuration you will use
Read from the environment variable APP_ENV
When you call:
$container = $definition->build()
The component will try to get the proper configuration set based on the contents of the variable APP_ENV
There are several ways to set the APP_ENV
before start your server:
This can be done using nginx:
fastcgi_param APP_ENV dev;
Apache:
SetEnv APP_ENV dev
Docker-Compose
environment:
APP_ENV: dev
Docker CLI
docker -e APP_ENV=dev image
Read from a different variable
Instead of use APP_ENV
you can set your own variable
$container = $definition
->withConfigVar("MY_ENV_VAR")
->build("live");
Specify directly
Other way to load the configuration set instead of depending on an environment variable is to specifiy directly which configuration you want to get:
$container = $definition->build("live");
Configuration Files
The config-xxxx.php
file
config-homolog.php
<?php
return [
'property1' => 'string',
'property2' => true,
'property3' => function () {
return 'xxxxxx';
},
'propertyWithArgs' => function ($p1, $p2) {
return 'xxxxxx';
},
];
config-live.php
<?php
return [
'property2' => false
];
The config-xxxx.env
file
Alternatively is possible to set an .env file with the contents KEY=VALUE one per line.
live.env
property1=mixed
By default, all properties are parsed as string. You can parse as bool, int or float as this example:
PARAM1=!bool true
PARAM2=!int 20
PARAM3=!float 3.14
Use in your PHP Code
Create the Definition:
<?php
$definition = (new \ByJG\Config\Definition())
->withConfigVar('APP_ENV') // This will setup the environment var to 'APP_ENV' (default)
->addConfig('homolog') // This will setup the HOMOLOG configuration set
->addConfig('live') // This will setup the LIVE environenment inherited HOMOLOG
->inheritFrom('homolog')
->setCache($somePsr16Implementation, 'live'); // This will cache the "live" configuration set.
The code below will get a property from the defined environment:
<?php
$container = $definition->build();
$property = $container->get('property2');
If the property does not exist an error will be thrown.
If the property is a closure, you can call the get method, and you'll get the closure execution result:
<?php
$container = $definition->build();
$property = $container->get('closureProperty');
$property = $container->get('closurePropertyWithArgs', 'value1', 'value2');
$property = $container->get('closurePropertyWithArgs', ['value1', 'value2']);
If you want get the RAW value without parse closure:
<?php
$container = $definition->build();
$property = $container->raw('closureProperty');
Dependency Injection
Basics
It is possible to create a Dependency Injection and set automatically the instances and constructors. Let's get by example the following classes:
<?php
namespace Example;
interface Area
{
public function calculate();
}
class Square implements Area
{
public function __construct($side)
{
// ...
}
//...
}
class RectangleTriangle implements Area
{
public function __construct($base, $height)
{
// ...
}
//...
}
We can create a definition for this classes:
<?php
use ByJG\Config\DependencyInjection as DI;
return [
\Example\Square::class => DI::bind(\Example\Square::class)
->withConstructorArgs([4])
->toInstance(),
\Example\RectangleTriangle::class => DI::bind(\Example\RectangleTriangle::class)
->withConstructorArgs([3, 4])
->toInstance(),
];
and to use in our code we just need to do:
<?php
$config = $definition->build();
$square = $config->get(\Example\Square::class);
Injecting automatically the Objects
Let's figure it out this class:
<?php
class SumAreas implements Area
{
/**
* SumAreas constructor.
* @param \DIClasses\RectangleTriangle $triangle
* @param \DIClasses\Square $square
*/
public function __construct($triangle, $square)
{
$this->triangle = $triangle;
$this->square = $square;
}
//...
Note that this class needs instances of objects previously defined in our container definition. In that case we just need add this:
<?php
return [
// ....
SumAreas::class => DI::bind(SumAreas::class)
->withInjectedConstructor()
->toInstance(),
];
When use use the method withConstructor()
we are expecting that all required classes in the constructor already where defined and inject automatically to get a instance.
This component uses the PHP Document to determine the classed are required.
Get a singleton object
The DependencyInjection
class will return a new instance every time you require a new object. However, you can the same object by adding toSingleton()
instead of toInstance()
.
All options (bind)
<?php
\ByJG\Config\DependencyInjection::bind("classname")
// To create a new instance choose *only* one below:
->withInjectedConstructor() // If you want inject the constructor automatically using reflection
->withInjectedLegacyConstructor() // If you want inject the constructor automatically using PHP annotation
->withNoConstructor() // The class has no constructor
->withConstructorNoArgs() // The constructor's class has no arguments
->withConstructorArgs(array) // The constructor's class arguments
->withFactoryMethod("method", array_of_args) // When the class has a static method to instantiate instead of constructure
// Call methods after you have a instance
->withMethodCall("methodName", array_of_args)
// How will you get a instance?
->toInstance() // get a new instance for every container get
->toSingleton() // get the same instance for every container get
->toEagerSingleton() // same as singleton however get a new instance immediately
;
Use a dependency inject in the config
If you need to use a previously DI created you can use the method use
. This method will return a DI instance and allow you to call a method and return its result.
This differs from Param::get
because DI::use
intends to get a class and return the result of a method call, while Param::get
is intended to use as a argument.
<?php
\ByJG\Config\DependencyInjection::use("classname")
->withMethodCall("methodName", array_of_args)
->toInstance() // get the result of the method call
;
Get the configuration set name is active
<?php
$definition->getCurrentConfig();
Install
composer require "byjg/config=4.1.*"
Tests
phpunit