使用 OptionsResolver 组件进行高级类型验证

Advanced type validation with OptionsResolver component

我需要允许实现两个接口(FooBar)的类型,而不是其中之一。

interface Foo {};
interface Bar {};

class Foz implements Foo {};
class Baz implements Bar {};
class Foobar implements Foo, Bar {};

$resolver = new OptionsResolver();
$resolver->setRequired('data');
$resolver->setAllowedTypes('data', ['Foo', 'Bar']);

错误! 也允许 FozBaz 个实例。


我需要 Bar 的允许类型子类,而不是 Bar 个实例。

class Bar {};
class Foobar extends Bar {};
class FoobarBaz extends Foobar {};

$resolver = new OptionsResolver();
$resolver->setRequired('data');
$resolver->setAllowedTypes('data', ['Bar']);

错误! 也允许 Bar 个实例。


我可以重新设计我的 classes/interfaces 但这不是设计问题。 那么,是否可以用这个组件来实现呢?

OptionsResolver::setAllowedTypes()的签名就是this:

setAllowedTypes(string $option, string|string[] $allowedTypes)

参数$allowedTypes可以接受一个字符串列表,并且用作逻辑OR,所以any的这些类型将被允许 - 恐怕你只能做这些......

请注意,为了允许您想要的复杂组合,您需要更多参数或其他方法,否则无法知道您是否需要 "any of these types"、"all of these types at the same time"、"any type but these ones" 或您能想到的任何其他组合...

我猜他们可能会提供一种方法,该方法接受回调函数作为第二个参数,因此您可以进行任何您想要的疯狂检查,但是 AFAIK 不存在(还没有)。

终于找到了一个方法,但我完全不确定这是不是常规方法:

//...
function is_foobar($value) {
    //return true or false;
}

$resolver = new OptionsResolver();
$resolver->setRequired('data');
$resolver->setAllowedTypes('data', 'foobar');
//...

编辑:

嗯,我睡了一觉,我觉得我的做法是错误的,因为已经用setAllowedValuesaddAllowedValues方法做到了:

$resolver->setAllowedValues('foo', function ($value) {
    return $value instanceof Foo && $value instanceof Bar;
});

因此没有必要为此目的使用 setAllowedTypes

您应该为此使用 标准化器

use Symfony\Component\Form\Exception\InvalidConfigurationException;

$resolver->setNormalizer('data', function(Options $options, $data) {
    if (!$data instanceof Foo && !$data instanceof Bar) {
        throw new InvalidConfigurationException('"data" option must implement "Foo" and "Bar" interfaces.');
    }

    return $data;
});