arnapou / appcfg
Library - Application config system.
Requires
- php: ~8.3.0 || ~8.4.0 || ~8.5.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: ^12.0
- phpunit/phpunit: ^12.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 (intin 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.5 | 8.4 | 8.3 | 
|---|---|---|---|---|
| 25/10/2025 | 1.11.x, main | × | × | × | 
| 25/11/2024 | 1.10.x | × | × | |
| 01/01/2024 | 1.0 - 1.9 | × |