arnapou / appcfg
Library - Application config system.
Requires
- php: ~8.3.0 || ~8.4.0
- arnapou/ensure: ^2.3
Requires (Dev)
- ext-yaml: *
- friendsofphp/php-cs-fixer: ^3.52
- phpstan/extension-installer: ^1.3
- phpstan/phpstan: ^2.0
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/php-code-coverage: ^11.0
- phpunit/phpunit: ^11.0
- psr/clock: ^1.0
README
Application config system.
Installation
composer require arnapou/appcfg
packagist 👉️ arnapou/appcfg
Introduction
The global purpose of this lib is to manage a configuration for your applications.
The main features are :
- Allows a mechanism of override: hostname > environment > generic.
- Expression parser of the form
%<processor>(<label>:<value>)%
to manage dependency values inside your configuration, with the possibility to add custom processors. - Dump of the compiled configuration into proper php typed objects, 100% compliant with your static analysis tools.
Parser
Look at the Parser class.
Parsed characters:
%
is the enclosure character, double it%%
to escape if you have a conflict like a string containing%something(
, then escape to%%something(
.()
parentheses are used to gather parts of an expression%env(VAR)%
:
colon is used to separate the label from the value%env(VAR:value)%
Examples:
%<processor>(<label>)%
: expression without value%<processor>(<label>:<value>)%
: main expression%<processor>(<label>:<string>%<processor>(<label>)%<string>)%
: nested is allowed in value part
Simplified expression:
%label%
is the equivalent of%(label)%
(processor =''
)- works only
- at first level (not nested)
- with these characters
a-zA-Z0-9_.-
Unlikely cases where you want to escape the %
:
string | where | escaped | explanation |
---|---|---|---|
%% | everywhere | %%%% | %% is the escaped version of % |
%word( | everywhere | %%word( | doubling the % avoids the parser to think it is an expression |
)% | nested value | %(:))%% | we isolate the ) from the % using the expression %(:<default>)% where <default> is the ) |
Note: the parser is lax about the %
if there is no doubt of its usage.
Example writing foo%bar baz
is valid because it is obviously not an expression.
But writing foo%%bar baz
is also valid because this is the same string with the %
escaped.
Bundled processors:
- DefaultProcessor with name
''
: used to manage default values where labels are paths of other values (i.e.parameter.database.name
). - EnvProcessor with name
'env'
: used to retrieve environment variables with an optional default.
The parsed string is a full immutable object structure like below.
Note that if you cast to string an Expression, you will get back the original raw string.
Processor
A Processor is an interface which can compute a value into a result.
If a value cannot be computed due to dependencies not already processed, you need to return a ProcessorStatus object.
DefaultProcessor
It is added by default in Processors with the name ''
This is the main processor which allows to retrieve other context values.
parameters:
foo: 'Hello World'
bar: '%(parameters.foo)%'
baz: '%(parameters.foo:default)%'
boo: '%parameters.baz%'
EnvProcessor
It is added by default in Processors with the name 'env'
Replaces environment variables
parameters:
mandatory: '%env(VARIABLE)%'
with.default: '%env(VARIABLE:<default>)%'
FilterProcessor
You need to explicitly add it with the name you want, suggestion: 'filter'
.
Applies some filters
parameters:
# basic cast
cast.to.string: '%filter(string:<value>)%'
cast.to.int: '%filter(int:<value>)%'
cast.to.float: '%filter(float:<value>)%'
cast.to.bool: '%filter(bool:<value>)%'
# null allowed in casting
cast.to.nullable.string: '%filter(?string:<value>)%'
cast.to.nullable.int: '%filter(?int:<value>)%'
cast.to.nullable.float: '%filter(?float:<value>)%'
cast.to.nullable.bool: '%filter(?bool:<value>)%'
# string functions
string.md5: '%filter(md5:<value>)%'
string.sha1: '%filter(sha1:<value>)%'
string.capitalize: '%filter(capitalize:<value>)%'
string.lower: '%filter(lower:<value>)%'
string.upper: '%filter(upper:<value>)%'
# string+array functions
value.length: '%filter(length:<value>)%'
JsonProcessor
You need to explicitly add it with the name you want, suggestion: 'json'
.
Applies some filters
parameters:
json.decode: '%json(decode:<value>)%'
json.encode: '%json(encode:<value>)%'
DateProcessor
You need to explicitly add it with the name you want, suggestion: 'date'
.
Format dates
parameters:
YYYY-MM-DD: '%date(Y-m-d:<value>)%'
RFC3339: '%date(Y-m-d\TH\:i\:sP:<value>)%' # escape the colons of the format
# you can use php date special values
# see https://www.php.net/manual/en/datetime.formats.php
special: '%date(Y-m-d:10 june next year)%'
timestamp: '%date(Y-m-d:1718990615)%'
# or DateTimeInterface constants
ATOM: '%date(ATOM:1718990615)%'
RFC3339: '%date(RFC3339:1718990615)%'
W3C: '%date(W3C:1718990615)%'
Compiler
A Compiler is an interface which can compile an input array into another.
StaticCompiler
The compiled array contains only compiled scalars. This compiler does not explicitly manage cyclic loops but has a max passes security.
Dumper
A Dumper is an interface which can render an input array into php code.
StaticDumper
Produces php typed objects for each associative array of your configuration structure. 100% compliant with analysis tools like PHPStan. The objects rendered can be mutable or immutable (default).
DataSource / Appcfg
A DataSource is an interface which has the responsibility to manage the source of data in order to be used by Appcfg.
Appcfg is a simple concrete class which is basically an orchestra conductor which can do several classic operations with all the previous objects.
Example
Input data
$data = [
'db' => [
'main' => [
'host' => 'localhost',
'port' => 3306,
'name' => 'project_main',
'password' => 'project_password',
],
'data' => [
'host' => '%(db.main.host)%',
'port' => '%(db.main.port)%',
'name' => 'project_data',
'password' => '%(db.main.password)%',
],
],
'db|prod' => [
'main' => [
'password' => '%env(DB_PASSWORD)%',
],
],
];
💡 If the expression is alone like in
db.data.port
, the referenced type is kept when compiled (int
in the above example).
Compile & render
use Arnapou\Appcfg\Compiler\CompileOptions;
use Arnapou\Appcfg\Compiler\StaticCompiler;
use Arnapou\Appcfg\Dumper\DumpOptions;
use Arnapou\Appcfg\Dumper\StaticDumper;
$compiler = new StaticCompiler();
$compiled = $compiler->compile($data, new CompileOptions('prod'));
$dumper = new StaticDumper();
echo $dumper->dump($compiled, new DumpOptions('MyProject\Config'));
Result
Run command : DB_PASSWORD=123456 php example/readme.php
namespace MyProject;
final readonly class ConfigDbMain
{
public function __construct(
public string $host = 'localhost',
public int $port = 3306,
public string $name = 'project_main',
public string $password = '123456',
) {}
}
final readonly class ConfigDbData
{
public function __construct(
public string $host = 'localhost',
public int $port = 3306,
public string $name = 'project_data',
public string $password = '123456',
) {}
}
final readonly class ConfigDb
{
public function __construct(
public ConfigDbMain $main = new ConfigDbMain(),
public ConfigDbData $data = new ConfigDbData(),
) {}
}
final readonly class Config
{
public function __construct(
public ConfigDb $db = new ConfigDb(),
) {}
}
Php versions
Date | Ref | 8.4 | 8.3 |
---|---|---|---|
25/11/2024 | 1.10.x, main | × | × |
01/01/2024 | 1.0 - 1.9 | × |