kuria/options

根据指定的选项集解决结构化数组

v2.1.0 2023-02-14 21:46 UTC

This package is auto-updated.

Last update: 2024-09-22 16:39:47 UTC


README

根据指定的选项集解决结构化数组(例如配置)。

https://travis-ci.cn/kuria/options.svg?branch=master

内容

功能

  • 类型验证
  • 类型列表
  • 可空选项
  • 选项
  • 默认值
  • 延迟默认值(可能依赖于其他选项)
  • 自定义验证器和规范器
  • 嵌套选项(多维数组)
  • 自定义解析器上下文

要求

  • PHP 7.1+

使用方法

解决选项

使用 Resolver 根据指定的选项解决数组。

resolve() 方法返回一个 Node 实例,可以将其作为数组访问。见 与节点实例一起工作

如果传入的值无效,将抛出 ResolverException。见 处理验证错误

<?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']);

输出

string(8) "file.txt"
NULL

Node 实例一起工作

默认情况下,Resolver->resolve() 返回一个具有已解决选项的 Node 实例。

  • Node 实现了 ArrayAccess,因此可以使用数组语法访问单个选项:$node['option']

  • 延迟默认值 在读取该选项(或调用 toArray() 时)解决一次

  • 嵌套的 节点选项 也作为 Node 实例返回

    (如果您需要专门使用数组,请使用 $node->toArray()

解析器上下文

Resolver->resolve() 接受第二个参数,该参数可以是传递给所有验证器、规范化和延迟默认闭包的额外参数数组。值可以是任何类型。

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();

输出

NORMALIZE: context argument 1, context argument 2
VALIDATE: context argument 1, context argument 2
DEFAULT: context argument 1, context argument 2

定义选项

术语

叶选项
选项树中不包含子选项的选项。
节点选项
通过 Option::node()Option::nodeList() 定义的选项。它们是选项树中的分支。
子选项
嵌套在节点选项内的任何选项。它可以是叶选项或节点选项。

选项工厂

Option 类提供了一些静态工厂来创建选项实例。

选项配置

可以通过以下方法进一步配置选项实例。

所有方法都实现了流畅的接口,例如

<?php

use Kuria\Options\Option;

Option::string('name')
   ->default('foo')
   ->nullable();
required()

使选项成为必需的(并删除之前设置的任何默认值)。

  • 叶选项 默认为必需
  • 节点选项默认不是必需的,但如果存在必需的子选项,则它将成为必需(除非节点选项本身默认为NULL)。
default($default)

使选项可选并指定默认值。

  • NULL指定为默认值也会使选项可空。
  • 叶选项的默认值不受验证或规范化,直接使用。
  • 节点选项的默认值必须是数组或NULL,并根据指定的子选项进行验证和规范化。
延迟默认值(仅限于叶节点)

要指定延迟默认值,请传递以下签名的闭包

<?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';
});

当需要默认值时,将调用闭包,并将返回值存储以供以后使用(因此它不会调用超过一次)。

注意

必须提供类型提示的Node参数。不兼容签名的闭包将被视为默认值本身,并按原样返回。

注意

节点选项不支持延迟默认值。

提示

可以将额外的参数传递给所有延迟默认闭包。请参阅解析器上下文

nullable()

使选项可空,除了指定的类型外,还可以接受NULL

notNullable()

使选项不可空,不接受NULL

注意

默认情况下,选项不可空。

allowEmpty()

允许将空值传递给此选项。

注意

默认情况下,选项接受空值。

notEmpty()

使选项拒绝空值。

如果PHP的empty()返回TRUE,则认为值是空的。

normalize(callable $normalizer)

将规范器附加到选项。规范器应接受一个值并返回规范化后的值,或者在失败时抛出Kuria\Options\Exception\NormalizerException

请参阅规范器和验证器值类型

  • 规范器在由validate()定义的验证器之前调用。
  • 规范器按附加的顺序调用。
  • 如果值的类型无效,则不调用规范器。
  • 选项规范化的顺序未定义(但节点选项按子节点优先顺序进行规范化)。
<?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  ']));

输出

object(Kuria\Options\Node)#7 (1) {
  ["name"]=>
  string(7) "foo bar"
}

注意

要规范化根级别上的所有选项,请使用$resolver->addNormalizer()定义一个或多个规范器。

提示

可以使用规范器将节点转换为自定义对象,因此您不必与匿名Node对象一起工作。

提示

可以将额外的参数传递给所有规范器。请参阅解析器上下文

validate(callable $validator)

将验证器附加到选项。验证器应接受并验证一个值。

  • 验证器在由normalize()定义的规范器之后调用。
  • 验证器按附加的顺序调用。
  • 如果值的类型无效或其规范化失败,则不调用验证器。
  • 如果验证器返回一个或多个错误,则不会调用该选项的其他验证器。
  • 选项验证的顺序未定义(但节点选项按子节点优先顺序进行验证)。

验证器应返回以下之一

  • NULL或空数组,如果没有错误
  • 错误作为字符串、字符串数组或错误实例
<?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";
}

输出

Failed to resolve options due to following errors:

1) email: must be a valid email address

注意

要在根级别验证所有选项,请使用 $resolver->addValidator() 定义一个或多个验证器。

提示

可以向所有验证器传递额外的参数。请参阅 解析器上下文

支持类型

  • NULL - 任何类型
  • "bool"
  • "int"
  • "float"
  • "number" - 整数或浮点数
  • "numeric" - 整数、浮点数或数值字符串
  • "string"
  • "array"
  • "iterable" - 数组或 Traversable 实例
  • "object"
  • "resource"
  • "scalar" - 整数、浮点数、字符串或布尔值
  • "callable"

其他任何类型都被视为类名,接受给定类或接口(或其子类)的实例。

将选项定义为可空也将接受 NULL 值。请参阅 可空

规范化和验证器值类型

传递给正常化器和验证器的值的类型取决于选项的类型。

  • Option::list()Option::choiceList() - 值数组
  • Option::node() - Node 实例
  • Option::nodeList() - Node 实例数组
  • 其他 - 依赖于选项的类型(stringint 等)

注意

正常化器可以在将值传递给后续的正常化器和验证器之前修改或替换该值(包括其类型)。

节点选项

节点选项接受指定选项的数组。使用它们可以解析更复杂的结构。

  • 节点选项是迭代地解析的(不使用递归)
  • 某些配置与节点选项的行为不同,请参阅 选项配置
<?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')
        )
    )
);

处理验证错误

如果失败,Resolver->resolve() 方法会抛出 Kuria\Options\Exception\ResolverException

可以通过在异常对象上调用 getErrors() 来检索特定错误。

<?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";
    }
}

输出

name    string expected, but got NULL instead
level   int expected, but got "not_a_string" instead
score   this option is required
foo     unknown option

忽略未知键

可以通过调用 $resolver->setIgnoreUnknown(true) 来配置 Resolver 以忽略未知键。

  • 对于未知键将不再抛出 UnknownOptionError
  • 这也适用于嵌套选项
  • 未知键将出现在解析后的选项中

集成选项解析器

可以使用 StaticOptionsTrait 来轻松地向类添加静态选项支持。

它还有一个额外的优点,即在类的多个实例中缓存和重用解析器。如果需要,可以通过调用 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);
    }
}

实例化示例

<?php

$foo = new Foo(['path' => 'file.txt']);

$foo->dumpConfig();

输出

object(Kuria\Options\Node)#8 (2) {
  ["path"]=>
  string(8) "file.txt"
  ["enableCache"]=>
  bool(false)
}