4

In Symfony 2.8, in a custom form type, is it possible to make setAllowedValues return a result that depends on the value of another option? There isn't an obvious way to access an Options object in the closure as far as I can see.

public function configureOptions(OptionsResolver $resolver) {
    $resolver->setRequired('option1');
    $resolver->setRequired('option2');

    $resolver->setAllowedValues('option2', function ($value) {
        return $based_on_set_restricted_by_option1; // <-- option2 values are allowed or denied depending on what option1 says
    }
}

The closest idea to a solution that I have is to have an option that is a dictionary that encapsulates both option1 and option2 and do setAllowedValues on it instead, but it's not easy to restructure the options right now.

Daniel Widdis
  • 8,424
  • 13
  • 41
  • 63
Radu C
  • 1,340
  • 13
  • 31
  • Have you tried using `$resolver->offsetGet('option1');` within that function to get the resolved value of `option1` so that you could base `option2` on that? I believe you can get the value that way. – Jason Roman Feb 04 '16 at 15:38
  • "Array access is only supported within closures of lazy options and normalizers." says Symfony. – Radu C Feb 05 '16 at 14:09
  • Okay looks like you have to do it the way Heah suggested then – Jason Roman Feb 05 '16 at 15:02

1 Answers1

7

The method configureOptions() of symfony form type can do many things as it provides an OptionsResolver object (relying on its own component see) to tune your configuration.

What you need in that case is to call $resolver->setNormalizer() (see) which does exactly what you want, it takes an option to define once the others are set and a callable.

When the resolver normalizes your option it executes the callable by passing an array of all options as the first argument, and the set value of the option to normalize as a second argument :

// class CustomFormType extends \Symfony\Component\Form\Extension\Core\Type\AbstractType

use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;

public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefault('some_option', 'default value');

    // or many at once
    $resolver->setDefaults(array(
        'some_option' => 0,
        'range_condition' => null,
    );

    // what you need
    $someOptionNormalizer = function (Options $options, $someOption) {
        $rangeCondition = $options['range_condition'];

        if (in_array($rangeCondition, range(2, 5))) {
            if (in_array($someOption, range(6, 9))) {

                return $someOption;
            }

            throw new InvalidOptionsException(sprintf('When "range_condition" is inferior or equal to 5, "some_option" should be between 6 and 9, but "%d" given.', $someOption));
        }

        if (in_array($rangeCondition, range(10, 13))) {
            if (in_array($someOption, range(1000, 1005))) {

                return $someOption;
            }

            throw new InvalidOptionsException(sprintf('When "range_condition" is between 10 and 13, "some_option" should be between 1000 and 1005, but "%d" given.', $someOption));
        }
    };

    $resolver->setNormalizer('some_option', $someOptionNormalizer);

    // you can also do
    $resolver->setRequired('some_option');
    $resolver->setAllowedTypes('some_option', array('integer'));
    $resolver->setAllowedTypes('range_condition', array('null', 'integer'));
    $resolver->setAllowedValues('some_option', array_merge(range(6, 9), range(1000, 1005)));
    $resolver->setAllowedValues('range_condition', array_merge(range(2, 5), range(10, 13)));

    // ...

}
Heah
  • 2,349
  • 14
  • 25
  • Your solution doesn't help me. I don't need to toggle a boolean and go on my merry way if another option meets or not some constraint. I need: if other_option is in range [2..5], I need some_option to be allowed only range [6..9], but if other_option is in range [10..13], then some_option is allowed [1000...1005] instead. Should the compound restrictions not be met I want an exception. - EDIT: I guess you want to make some_option to throw the exception when the normaliser attempts to set it to an invalid value (but you example doesn't do that). – Radu C Feb 05 '16 at 14:02
  • Of course, I could throw that exception myself in the normalizer, but I wonder what else can break if I do that. – Radu C Feb 05 '16 at 14:21
  • 1
    I was just going through EntityType source code and they do just that: throw an exception in a normaliser. Not my first choice, but if Symfony does it, it's fine by me too. That's the way to go then. – Radu C Feb 05 '16 at 14:27
  • @RaduC it was just an example as you provide not specific logic in your question, I tried to updated to better reflect your method though. – Heah Feb 05 '16 at 15:05