5

I need allow types that implement two interfaces (Foo and Bar), not one of them.

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

Wrong! allows Foz and Baz instances too.


I need allow types subclass of Bar, not Bar instances.

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

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

Wrong! allows Bar instances too.


I can redesign my classes/interfaces but it's not a design problem. So, is it possible achieve it with this component?

oguz ismail
  • 1
  • 16
  • 47
  • 69
yceruto
  • 9,230
  • 5
  • 38
  • 65

3 Answers3

5

See Define a form option allowed values depending on another option value in a FormType.

You should use a normalizer for this:

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;
});
Community
  • 1
  • 1
Heah
  • 2,349
  • 14
  • 25
2

Finally, I found a way but I'm not sure at all that it's a conventional way:

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

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

EDIT:

Well, after I sleep on, I think my approach is wrong, because it's already done by using setAllowedValues or addAllowedValues methods:

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

So it's not necessary to use setAllowedTypes for this purpose.

yceruto
  • 9,230
  • 5
  • 38
  • 65
  • 1
    yeah if you look at symfony's `OptionsResolver` class, the `offsetGet()` function, it just checks for `if (function_exists($isFunction = 'is_'.$type))`, so yeah, surprisingly that should work - nice find! Although it's a horrible workaround let me say :) – MikO Sep 28 '16 at 16:02
  • 1
    Yes "horrible" is the word, but even worse it's the error message when invalid type: `InvalidOptionsException: The option "data" with value Baz is expected to be of type "foobar", but is of type "Baz".` :) – yceruto Sep 28 '16 at 16:08
1

The signature of OptionsResolver::setAllowedTypes() is just this:

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

The argument $allowedTypes can accept a list of strings, and is used as a logic OR, so any of those types will be allowed - that's all you can do I'm afraid...

Note that in order to allow the complex combinations you want, you'd need either more arguments or other methods, otherwise there's no way to know whether you want "any of these types", "all of these types at the same time", "any type but these ones" or any other combination you can imagine...

I guess they may provide a method that accepts a callback function as the second argument, so you can do any crazy checks you want, but AFAIK that doesn't exist (yet).

MikO
  • 18,243
  • 12
  • 77
  • 109