khartir/typed-config

A tool to create valid config objects from typed classes and data-arrays

v0.3.0 2021-04-10 08:11 UTC

This package is not auto-updated.

Last update: 2024-04-20 22:57:01 UTC


README

A package to create a typed configuration in PHP

Installation

composer require khartir/typed-config

Usage

Given some classes like this:

class Config {

    private $foo;

    private $barConfig;

    public function __construct(string $foo, BarConfig $barConfig) {
        $this->foo = $foo;
        $this->barConfig = $barConfig;
    }
}

class BarConfig {

    private $value;

    public function __construct(string $value) {
        $this->value = $value;
    }
}

You can create populated objects like this:

$builder = new \Khartir\TypedConfig\Builder();

$data = [
    ['barConfig' => ['value' => 'dummy']],
    ['foo' => 'bar'],
];

$config = $builder->build(Config::class, $data);

snake_case keys to camelCase config

If your array-keys are snake_case and your properties are camelCase you can add a converter:

$builder = new \Khartir\TypedConfig\Builder(new \Khartir\TypedConfig\Extractor\SnakeCaseExtractor());

Extending

Writing a custom resolver

Resolvers determine the value of a parameter from the list of values given to the builder. You can write custom resolvers to implement special logic. The example contains a resolver to transform strings to \DateTime-Objects.

class DateTimeResolver implements ResolverInterface
{
    public const DATE_FORMAT = 'Y-m-d H:i:s';

    public function canResolve(\ReflectionParameter $parameter): bool
    {
        return \in_array(
            ReflectionHelper::getTypeName($parameter),
            [\DateTimeImmutable::class, \DateTime::class],
            true
        );
    }

    public function resolve(\ReflectionParameter $parameter, array $values): \DateTimeInterface
    {
        $value  = \end($values);
        $class  = ReflectionHelper::getTypeName($parameter);
        $parsed = $class::createFromFormat(self::DATE_FORMAT, $value);

        if (false === $parsed) {
            throw InvalidArgumentException::createForParameter($value, $parameter);
        }

        return $parsed;
    }
}

Testing

One problem with the nested config objects this library allows is testing. Specifically the need to define values for all values even though a specific test only cares about a couple of values.

For this reason, the DummyValueBuilder exists. Simply instantiate it the same as you would your Builder.

The values for your config are then determined by the following rules:

  1. If you pass in a value, it is used, just like with the normal Builder.
  2. If the parameter has a default value, that value is used.

    This can be disabled, by calling $dummyBuilder->useDefaultValue(false).

  3. If the parameter is nullable, null is used.

    This can be disabled, by calling $dummyBuilder->useNullable(false).

  4. If none of the above match, a default value matching the parameter type is used.

    The list of default values as type => value pairs can be:

    • overwritten be calling setDefaultValues
    • added to be calling addDefaultValues

    The following defaults are used, if none are otherwise configured:

    [
     'int' => 0,
     'string' => '',
     'float' => 0.0,
     'array' => [],
     'bool' => false,
    ]