arnapou/appcfg

Library - Application config system.

v1.9 2024-08-29 11:50 UTC

This package is auto-updated.

Last update: 2024-11-15 09:10:11 UTC


README

pipeline coverage

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 % :

stringwhereescapedexplanation
%%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.

Parser schema

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

DateRef8.3
01/01/20241.x, main×