Resolve structured arrays (e.g. configuration) according to the specified set of options.
Contents
- type validation
- typed lists
- nullable options
- choices
- default values
- lazy defaults (that may depend on other options)
- custom validators and normalizers
- nested options (multi-dimensional arrays)
- custom resolver context
- PHP 7.1+
Use Resolver
to resolve arrays according to the specified options.
The resolve()
method returns an instance of Node
, which can be
accessed as an array. See Working with Node instances.
If the passed value is invalid, ResolverException
will be thrown.
See Handling validation errors.
<?php
use Kuria\Options\Resolver;
use Kuria\Options\Option;
// create a resolver
$resolver = new Resolver();
// define options
$resolver->addOption(
Option::string('path'),
Option::int('interval')->default(null)
);
// resolve an array
$node = $resolver->resolve([
'path' => 'file.txt',
]);
var_dump($node['path'], $node['interval']);
Output:
string(8) "file.txt" NULL
By default, Resolver->resolve()
returns a Node
instance with the resolved options.
Node
implementsArrayAccess
, so the individual options can be acessed using array syntax:$node['option']
lazy default values are resolved once that option is read (or when
toArray()
is called)nested node options are also returned as
Node
instances(if you need to work exclusively with arrays, use
$node->toArray()
)
Resolver->resolve()
accepts a second argument, which may be an array of additional arguments
to pass to all validators, normalizers and lazy default closures. The values may be of any type.
use Kuria\Options\Node;
use Kuria\Options\Option;
use Kuria\Options\Resolver;
$resolver = new Resolver();
$resolver->addOption(
Option::string('option')
->normalize(function (string $value, $foo, $bar) {
echo 'NORMALIZE: ', $foo, ', ', $bar, "\n";
return $value;
})
->validate(function (string $value, $foo, $bar) {
echo 'VALIDATE: ', $foo, ', ', $bar, "\n";
}),
Option::string('optionWithLazyDefault')
->default(function (Node $node, $foo, $bar) {
echo 'DEFAULT: ', $foo, ', ', $bar, "\n";
return 'value';
})
);
$options = $resolver->resolve(
['option' => 'value'],
['context argument 1', 'context argument 2']
)->toArray();
Output:
NORMALIZE: context argument 1, context argument 2 VALIDATE: context argument 1, context argument 2 DEFAULT: context argument 1, context argument 2
- leaf option
- An option in the option tree that does not contain children.
- node option
- An option defined via
Option::node()
orOption::nodeList()
. They are branches in the option tree. - child option
- Any option nested inside a node option. It can be either leaf or a node option.
The Option
class provides a number of static factories to create option instances.
Factory | Description |
---|---|
Option::any($name) |
Mixed option that accepts all value types.
NULL is accepted only if the option is nullable. |
Option::bool($name) |
Boolean option. |
Option::int($name) |
Integer option. |
Option::float($name) |
Float option. |
Option::number($name) |
Number option that accepts integers and floats. |
Option::numeric($name) |
Numeric option that accepts integers, floats and numeric strings. |
Option::string($name) |
String option. |
Option::array($name) |
Array option. The individual values are not validated. |
Option::list($name, $type) |
List option that accepts an array with values of the
specified type. Each value is validated and must not
be NULL . See Supported types. |
Option::iterable($name) |
Iterable option that accepts both arrays and Traversable
instances. The individual values are not validated. |
Option::object($name) |
Object option. |
Option::object($name, $className) |
Object option that only accepts instances of the given class or interface (or their descendants). |
Option::resource($name) |
Resource option. |
Option::scalar($name) |
Scalar option that accepts integers, floats, strings and booleans. |
Option::choice($name, ...$choices) |
Choice option that accepts one of the listed values only (compared in strict mode). |
Option::choiceList($name, ...$choices) |
Choice list option that accepts an array consisting of
any of the listed values (compared in strict mode).
Duplicates are allowed. NULL values are not allowed. |
Option::node($name, ...$options) |
Node option that accepts an array of the specified options. See Node options. |
Option::nodeList($name, ...$options) |
Node list option that accepts an array of arrays of the specified options. See Node options. |
Option instances can be configured further by using the following methods.
All methods implement a fluent interface, for example:
<?php
use Kuria\Options\Option;
Option::string('name')
->default('foo')
->nullable();
Makes the option required (and removes any previously set default value).
- a leaf option is required by default
- a node option is not required by default, but having
a required child option will make it required
(unless the node option itself defaults to
NULL
).
Makes the option optional and specifies a default value.
- specifying
NULL
as the default value also makes the option nullable - default value of a leaf option is not subject to validation or normalization and is used as-is
- default value of a node option must be an array or
NULL
and is validated and normalized according to the specified child options
To specify a lazy default value, pass a closure with the following signature:
<?php
use Kuria\Options\Node;
use Kuria\Options\Option;
Option::string('foo')->default(function (Node $node) {
// return value can also depend on other options
return 'default';
});
Once the default value is needed, the closure will be called and its return value stored for later use (so it will not be called more than once).
Note
The typehinted Node
parameter is required. A closure with incompatible
signature will be considered a default value itself and returned as-is.
Note
Node options do not support lazy default values.
Tip
It is possible to pass additional arguments to all lazy default closures. See Resolver context.
Make the option nullable, accepting NULL
in addition to the specified type.
Make the option non-nullable, not accepting NULL
.
Note
Options are non-nullable by default.
Allow empty values to be passed to this option.
Note
Options accept empty values by default.
Make the option reject empty values.
A value is considered empty if PHP's empty()
returns TRUE
.
Append a normalizer to the option. The normalizer should accept a value
and return the normalized value or throw Kuria\Options\Exception\NormalizerException
on failure.
See Normalizer and validator value types.
- normalizers are called before validators defined by
validate()
- normalizers are called in the order they were appended
- normalizers are not called if the type of the value is not valid
- the order in which options are normalized is undefined (but node options are normalized in child-first order)
<?php
use Kuria\Options\Resolver;
use Kuria\Options\Option;
$resolver = new Resolver();
$resolver->addOption(
Option::string('name')->normalize('trim')
);
var_dump($resolver->resolve(['name' => ' foo bar ']));
Output:
object(Kuria\Options\Node)#7 (1) { ["name"]=> string(7) "foo bar" }
Note
To normalize all options at the root level, define one or more normalizers
using $resolver->addNormalizer()
.
Tip
It is possible to use normalizers to convert nodes into custom objects,
so you don't have to work with anonymous Node
objects.
Tip
It is possible to pass additional arguments to all normalizers. See Resolver context.
Append a validator to the option. The validator should accept and validate a value.
- validators are called after normalizers defined by
normalize()
- validators are called in the order they were appended
- validators are not called if the type of the value is not valid or its normalization has failed
- if a validator returns one or more errors, no other validators of that option will be called
- the order in which options are validated is undefined (but node options are validated in child-first order)
The validator should return one of the following:
NULL
or an empty array if there no errors- errors as a
string
, an array of strings or Error instances
<?php
use Kuria\Options\Exception\ResolverException;
use Kuria\Options\Resolver;
use Kuria\Options\Option;
$resolver = new Resolver();
$resolver->addOption(
Option::string('email')->validate(function (string $email) {
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
return 'must be a valid email address';
}
})
);
try {
var_dump($resolver->resolve(['email' => 'this is not an email']));
} catch (ResolverException $e) {
echo $e->getMessage(), "\n";
}
Output:
Failed to resolve options due to following errors: 1) email: must be a valid email address
Note
To validate all options at the root level, define one or more validators
using $resolver->addValidator()
.
Tip
It is possible to pass additional arguments to all validators. See Resolver context.
NULL
- any type"bool"
"int"
"float"
"number"
- integer or float"numeric"
- integer, float or a numeric string"string"
"array"
"iterable"
- array or an instance ofTraversable
"object"
"resource"
"scalar"
- integer, float, string or a boolean"callable"
Any other type is considered to be a class name, accepting instances of the given class or interface (or their descendants).
An option defined as nullable will also accept a NULL
value. See nullable().
The type of the value passed to normalizers and validators depend on the type of the option.
Option::list()
,Option::choiceList()
- an array of valuesOption::node()
- aNode
instanceOption::nodeList()
- an array ofNode
instances- other - depends on the type of the option (
string
,int
, etc.)
Note
A normalizer may modify or replace the value (including its type) before it is passed to subsequent normalizers and validators.
Node options accept an array of the specified options. With them it is possible to resolve more complex structures.
- node options are resolved iteratively (without recursion)
- certain configuration behaves differently with node options, see Option configuration
<?php
use Kuria\Options\Option;
use Kuria\Options\Resolver;
$resolver = new Resolver();
$resolver->addOption(
Option::string('username'),
Option::node(
'personalInformation',
Option::int('birthYear'),
Option::int('height')->default(null),
Option::float('weight')->default(null)
),
Option::nodeList(
'securityLog',
Option::string('action'),
Option::int('timestamp'),
Option::node(
'client',
Option::string('ip'),
Option::string('userAgent')
)
)
);
The Resolver->resolve()
method throws Kuria\Options\Exception\ResolverException
on failure.
The specific errors can be retrieved by calling getErrors()
on the exception object.
<?php
use Kuria\Options\Resolver;
use Kuria\Options\Exception\ResolverException;
use Kuria\Options\Option;
$resolver = new Resolver();
$resolver->addOption(
Option::string('name'),
Option::int('level'),
Option::int('score')
);
try {
$resolver->resolve([
'name' => null,
'level' => 'not_a_string',
'foo' => 'bar',
]);
} catch (ResolverException $e) {
foreach ($e->getErrors() as $error) {
echo $error->getFormattedPath(), "\t", $error->getMessage(), "\n";
}
}
Output:
name string expected, but got NULL instead level int expected, but got "not_a_string" instead score this option is required foo unknown option
The Resolver
can be configured to ignore unknown keys by calling
$resolver->setIgnoreUnknown(true)
.
UnknownOptionError
will no longer be raised for unknown keys- this applies to nested options as well
- the unknown keys will be present among the resolved options
The StaticOptionsTrait
can be used to easily add static option support
to a class.
It has the added benefit of caching and reusing the resolver in multiple
instances of the class. If needed, the cache can be cleared by calling
Foo::clearOptionsResolverCache()
.
<?php
use Kuria\Options\Integration\StaticOptionsTrait;
use Kuria\Options\Node;
use Kuria\Options\Option;
use Kuria\Options\Resolver;
class Foo
{
use StaticOptionsTrait;
/** @var Node */
private $config;
function __construct(array $options)
{
$this->config = static::resolveOptions($options);
}
protected static function defineOptions(Resolver $resolver): void
{
$resolver->addOption(
Option::string('path'),
Option::bool('enableCache')->default(false)
);
}
function dumpConfig(): void
{
var_dump($this->config);
}
}
Instantiation example:
<?php
$foo = new Foo(['path' => 'file.txt']);
$foo->dumpConfig();
Output:
object(Kuria\Options\Node)#8 (2) { ["path"]=> string(8) "file.txt" ["enableCache"]=> bool(false) }