diff --git a/UPGRADE-2.1.md b/UPGRADE-2.1.md index dad88e9117..39813e9c47 100644 --- a/UPGRADE-2.1.md +++ b/UPGRADE-2.1.md @@ -145,19 +145,330 @@ ### Form +#### BC Breaks in Form Types and Options + + * A third argument `$options` was added to the methods `buildView()` and + `buildViewBottomUp()` in `FormTypeInterface` and `FormTypeExtensionInterface`. + Furthermore, `buildViewBottomUp()` was renamed to `finishView()`. At last, + all methods in these types now receive instances of `FormBuilderInterface` + and `FormViewInterface` where they received instances of `FormBuilder` and + `FormView` before. You need to change the method signatures in your + form types and extensions as shown below. + + Before: + + ``` + use Symfony\Component\Form\FormBuilder; + use Symfony\Component\Form\FormView; + + public function buildForm(FormBuilder $builder, array $options) + public function buildView(FormView $view, FormInterface $form) + public function buildViewBottomUp(FormView $view, FormInterface $form) + ``` + + After: + + ``` + use Symfony\Component\Form\FormBuilderInterface; + use Symfony\Component\Form\FormViewInterface; + + public function buildForm(FormBuilderInterface $builder, array $options) + public function buildView(FormViewInterface $view, FormInterface $form, array $options) + public function finishView(FormViewInterface $view, FormInterface $form, array $options) + ``` + + * No options are passed to `getParent()` of `FormTypeInterface` anymore. If + you previously dynamically inherited from `FormType` or `FieldType`, you can now + dynamically set the "compound" option instead. + + Before: + + ``` + public function getParent(array $options) + { + return $options['expanded'] ? 'form' : 'field'; + } + ``` + + After: + + ``` + use Symfony\Component\OptionsResolver\OptionsResolverInterface; + use Symfony\Component\OptionsResolver\Options; + + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + $compound = function (Options $options) { + return $options['expanded']; + }; + + $resolver->setDefaults(array( + 'compound' => $compound, + )); + } + + public function getParent() + { + return 'form'; + } + ``` + + The new method `setDefaultOptions` is described in the section "Deprecations". + + * The "data_class" option now *must* be set if a form maps to an object. If + you leave it empty, the form will expect an array, an instance of \ArrayAccess + or a scalar value and fail with a corresponding exception. + + Likewise, if a form maps to an array or an instance of \ArrayAccess, the option + *must* be left null now. + + Form mapped to an instance of `Person`: + + ``` + use Symfony\Component\OptionsResolver\OptionsResolverInterface; + + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + $resolver->setDefaults(array( + 'data_class' => 'Acme\Demo\Person', + )); + } + ``` + + The new method `setDefaultOptions` is described in the section "Deprecations". + + * The mapping of property paths to arrays has changed. + + Previously, a property path "street" mapped to both a field `$street` of + a class (or its accessors `getStreet()` and `setStreet()`) and an index + `['street']` of an array or an object implementing `\ArrayAccess`. + + Now, the property path "street" only maps to a class field (or accessors), + while the property path "[street]" only maps to indices. + + If you defined property paths manually in the "property_path" option, you + should revise them and adjust them if necessary. + + Before: + + ``` + $builder->add('name', 'text', array( + 'property_path' => 'address.street', + )); + ``` + + After (if the address object is an array): + + ``` + $builder->add('name', 'text', array( + 'property_path' => 'address[street]', + )); + ``` + + If address is an object in this case, the code given in "Before" + works without changes. + + * The methods in class `FormView` were renamed to match the naming used in + `Form` and `FormBuilder`. The following list shows the old names on the + left and the new names on the right: + + * `set`: `setVar` + * `has`: `hasVar` + * `get`: `getVar` + * `all`: `getVars` + * `addChild`: `add` + * `getChild`: `get` + * `getChildren`: `all` + * `removeChild`: `remove` + * `hasChild`: `has` + + The new method `addVars` was added to make the definition of multiple + variables at once more convenient. + + The method `hasChildren` was deprecated. You should use `count` instead. + + Before: + + ``` + $view->set('help', 'A text longer than six characters'); + $view->set('error_class', 'max_length_error'); + ``` + + After: + + ``` + $view->addVars(array( + 'help' => 'A text longer than six characters', + 'error_class' => 'max_length_error', + )); + ``` + + * Form and field names must now start with a letter, digit or underscore + and only contain letters, digits, underscores, hyphens and colons. + + * In the collection type's template, the default name of the prototype field + has changed from `$$name$$` to `__name__`. + + For custom names, dollar signs are no longer prepended and appended. You are + advised to prepend and append two underscores wherever you specify a value + for the field's "prototype_name" option. + + Before: + + ``` + $builder->add('tags', 'collection', array('prototype' => 'proto')); + + // results in the name "$$proto$$" in the template + ``` + + After: + + ``` + $builder->add('tags', 'collection', array('prototype' => '__proto__')); + + // results in the name "__proto__" in the template + ``` + + * The "read_only" option now renders as `readonly="readonly"`, use + "disabled" instead for `disabled="disabled"`. + * Child forms are no longer automatically validated. That means that you must explicitly set the `Valid` constraint in your model if you want to validate - associated objects. + objects modified by child forms. If you don't want to set the `Valid` constraint, or if there is no reference from the data of the parent form to the data of the child form, you can - enable BC behavior by setting the `cascade_validation` form option to `true` + enable BC behavior by setting the "cascade_validation" option to `true` on the parent form. - * Changed implementation of choice lists +#### BC Breaks in Themes and HTML - ArrayChoiceList was replaced. If you have custom classes that extend this - class, you must now extend SimpleChoiceList. + * FormType and FieldType were merged and require you to adapt your form + themes. + + The block `field_widget` and all references to it should be renamed to + `form_widget_simple`: + + Before: + + ``` + {% block url_widget %} + {% spaceless %} + {% set type = type|default('url') %} + {{ block('field_widget') }} + {% endspaceless %} + {% endblock url_widget %} + ``` + + After: + + ``` + {% block url_widget %} + {% spaceless %} + {% set type = type|default('url') %} + {{ block('form_widget_simple') }} + {% endspaceless %} + {% endblock url_widget %} + ``` + + All other `field_*` blocks and references to them should be renamed to + `form_*`. If you previously defined both a `field_*` and a `form_*` + block, you can merge them into a single `form_*` block and check the new + Boolean variable `compound` instead: + + Before: + + ``` + {% block form_errors %} + {% spaceless %} + ... form code ... + {% endspaceless %} + {% endblock form_errors %} + + {% block field_errors %} + {% spaceless %} + ... field code ... + {% endspaceless %} + {% endblock field_errors %} + ``` + + After: + + ``` + {% block form_errors %} + {% spaceless %} + {% if compound %} + ... form code ... + {% else %} + ... field code ... + {% endif %} + {% endspaceless %} + {% endblock form_errors %} + ``` + + Furthermore, the block `generic_label` was merged into `form_label`. You + should now override `form_label` in order to customize labels. + + Last but not least, the block `widget_choice_options` was renamed to + `choice_widget_options` to be consistent with the rest of the default + theme. + + * The strategy for generating the `id` and `name` HTML attributes for + checkboxes and radio buttons in a choice field has changed. + + Instead of appending the choice value, a generated integer is now appended + by default. Take care if your JavaScript relies on that. If you want to + read the actual choice value, read the `value` attribute instead. + + * In the choice field type's template, the structure of the `choices` variable + has changed. + + The `choices` variable now contains `ChoiceView` objects with two getters, + `getValue()` and `getLabel()`, to access the choice data. + + Before: + + ``` + {% for choice, label in choices %} + + {% endfor %} + ``` + + After: + + ``` + {% for choice in choices %} + + {% endfor %} + ``` + +#### Other BC Breaks + + * The order of the first two arguments of the methods `createNamed` and + `createNamedBuilder` in `FormFactoryInterface` was reversed to be + consistent with the rest of the component. You should scan your code + for occurrences of these methods and reverse the parameters. + + Before: + + ``` + $form = $factory->createNamed('text', 'firstName'); + ``` + + After: + + ``` + $form = $factory->createNamed('firstName', 'text'); + ``` + + * The implementation of `ChoiceList` was changed heavily. As a result, + `ArrayChoiceList` was replaced. If you have custom classes that extend + this class, you must now extend `SimpleChoiceList` and pass choices + to the parent constructor. Before: @@ -190,7 +501,8 @@ ``` If you need to load the choices lazily -- that is, as soon as they are - accessed for the first time -- you can extend LazyChoiceList instead. + accessed for the first time -- you can extend `LazyChoiceList` instead + and load the choices by overriding `loadChoiceList()`. ``` class MyChoiceList extends LazyChoiceList @@ -204,81 +516,20 @@ } ``` - PaddedChoiceList, MonthChoiceList and TimezoneChoiceList were removed. - Their functionality was merged into DateType, TimeType and TimezoneType. + `PaddedChoiceList`, `MonthChoiceList` and `TimezoneChoiceList` were removed. + Their functionality was merged into `DateType`, `TimeType` and `TimezoneType`. - EntityChoiceList was adapted. The methods `getEntities()`, + `EntityChoiceList` was adapted. The methods `getEntities()`, `getEntitiesByKeys()`, `getIdentifier()` and `getIdentifierValues()` were removed or made private. Instead of the first two, you can now use `getChoices()` and `getChoicesByValues()`. For the latter two, no replacement exists. - * The strategy for generating the `id` and `name` HTML attributes for - checkboxes and radio buttons in a choice field has changed. - - Instead of appending the choice value, a generated integer is now appended - by default. Take care if your JavaScript relies on that. - - * In the choice field type's template, the structure of the `choices` variable - has changed. - - The `choices` variable now contains ChoiceView objects with two getters, - `getValue()` and `getLabel()`, to access the choice data. - - Before: - - ``` - {% for choice, label in choices %} - - {% endfor %} - ``` - - After: - - ``` - {% for choice in choices %} - - {% endfor %} - ``` - - * In the collection type's template, the default name of the prototype field - has changed from `$$name$$` to `__name__`. - - For custom names, dollar signs are no longer prepended and appended. You are - advised to prepend and append two underscores wherever you specify a value - for the field's `prototype_name` option. - - Before: - - ``` - $builder->add('tags', 'collection', array('prototype' => 'proto')); - - // results in the name "$$proto$$" in the template - ``` - - After: - - ``` - $builder->add('tags', 'collection', array('prototype' => '__proto__')); - - // results in the name "__proto__" in the template - ``` - - * The `read_only` field attribute now renders as `readonly="readonly"`, use - `disabled` instead for `disabled="disabled"`. - - * Form and field names must now start with a letter, digit or underscore - and only contain letters, digits, underscores, hyphens and colons - - * `EntitiesToArrayTransformer` and `EntityToIdTransformer` have been removed. - The former has been replaced by `CollectionToArrayTransformer` in combination + * `EntitiesToArrayTransformer` and `EntityToIdTransformer` were removed. + The former was replaced by `CollectionToArrayTransformer` in combination with `EntityChoiceList`, the latter is not required in the core anymore. - * The following transformers have been renamed: + * The following transformers were renamed: * `ArrayToBooleanChoicesTransformer` to `ChoicesToBooleanArrayTransformer` * `ScalarToBooleanChoicesTransformer` to `ChoiceToBooleanArrayTransformer` @@ -287,36 +538,61 @@ to be consistent with the naming in `ChoiceListInterface`. - * `FormUtil::toArrayKey()` and `FormUtil::toArrayKeys()` have been removed. + * `FormUtil::toArrayKey()` and `FormUtil::toArrayKeys()` were removed. They were merged into ChoiceList and have no public equivalent anymore. - * The options passed to the `getParent()` method of form types no longer - contain default options. They only contain the options passed by the user. + * The `add()`, `remove()`, `setParent()`, `bind()` and `setData()` methods in + the Form class now throw an exception if the form is already bound. - You should check if options exist before attempting to read their value. + If you used these methods on bound forms, you should consider moving your + logic to an event listener that observes `FormEvents::PRE_BIND` or + `FormEvents::BIND`. + +#### Deprecations + + * The following methods of `FormTypeInterface` and `FormTypeExtensionInterface` + are deprecated and will be removed in Symfony 2.3: + + * `getDefaultOptions` + * `getAllowedOptionValues` + + You should use the newly added `setDefaultOptions` instead, which gives you + access to the OptionsResolverInterface instance and with that a lot more power. Before: ``` - public function getParent(array $options) + public function getDefaultOptions(array $options) { - return 'single_text' === $options['widget'] ? 'text' : 'choice'; + return array( + 'gender' => 'male', + ); + } + + public function getAllowedOptionValues(array $options) + { + return array( + 'gender' => array('male', 'female'), + ); } ``` After: ``` - public function getParent(array $options) + public function setDefaultOptions(OptionsResolverInterface $resolver) { - return isset($options['widget']) && 'single_text' === $options['widget'] ? 'text' : 'choice'; + $resolver->setDefaults(array( + 'gender' => 'male', + )); + + $resolver->setAllowedValues(array( + 'gender' => array('male', 'female'), + )); } ``` - * The methods `getDefaultOptions()` and `getAllowedOptionValues()` of form - types no longer receive an option array. - - You can specify options that depend on other options using closures instead. + You can specify options that depend on other options using closures. Before: @@ -336,109 +612,87 @@ After: ``` - public function getDefaultOptions() + public function setDefaultOptions(OptionsResolverInterface $resolver) { - return array( - 'empty_data' => function (Options $options, $previousValue) { - return $options['multiple'] ? array() : $previousValue; + $resolver->setDefaults(array( + 'empty_data' => function (Options $options, $value) { + return $options['multiple'] ? array() : $value; } - ); + )); } ``` - The second argument `$previousValue` does not have to be specified if not - needed. + The second argument `$value` contains the current default value and + does not have to be specified if not needed. - * The `add()`, `remove()`, `setParent()`, `bind()` and `setData()` methods in - the Form class now throw an exception if the form is already bound. + * The following methods in `FormBuilder` were deprecated and have a new + equivalent: - If you used these methods on bound forms, you should consider moving your - logic to an event listener that observes one of the following events: - `FormEvents::PRE_BIND`, `FormEvents::BIND_CLIENT_DATA` or - `FormEvents::BIND_NORM_DATA`. + * `prependClientTransformer`: `addViewTransformer` + * `appendClientTransformer`: no new equivalent, consider using `addViewTransformer` + * `getClientTransformers`: `getViewTransformers` + * `resetClientTransformers`: `resetViewTransformers` + * `prependNormTransformer`: no new equivalent, consider using `addModelTransformer` + * `appendNormTransformer`: `addModelTransformer` + * `getNormTransformers`: `getModelTransformers` + * `resetNormTransformers`: `resetModelTransformers` - * The interface FormValidatorInterface was deprecated and will be removed + The deprecated methods will be removed in Symfony 2.3. You are advised to + update your application. + + Before: + + ``` + $builder->prependClientTransformer(new MyTransformer()); + ``` + + After: + + ``` + $builder->addViewTransformer(new MyTransformer()); + ``` + + * The following events were deprecated and have a new equivalent: + + * `FormEvents::SET_DATA`: `FormEvents::PRE_SET_DATA` + * `FormEvents::BIND_CLIENT_DATA`: `FormEvents::PRE_BIND` + * `FormEvents::BIND_NORM_DATA`: `FormEvents::BIND` + + The deprecated events will be removed in Symfony 2.3. + + Furthermore, the event classes `DataEvent` and `FilterDataEvent` were + deprecated and replaced by the generic `FormEvent`. You are advised to + code your listeners against the new event now. The deprecated events will + be removed in Symfony 2.3. + + Before: + + ``` + $builder->addListener(FormEvents::BIND_CLIENT_DATA, function (FilterDataEvent $event) { + // ... + }); + ``` + + After: + + ``` + $builder->addListener(FormEvents::PRE_BIND, function (FormEvent $event) { + // ... + }); + ``` + + * The interface `FormValidatorInterface` was deprecated and will be removed in Symfony 2.3. If you implemented custom validators using this interface, you can - substitute them by event listeners listening to the FormEvents::POST_BIND - (or any other of the BIND events). In case you used the CallbackValidator + substitute them by event listeners listening to the `FormEvents::POST_BIND` + (or any other of the `*BIND` events). In case you used the CallbackValidator class, you should now pass the callback directly to `addEventListener`. - * Since FormType and FieldType were merged, you need to adapt your form - themes. - - The "field_widget" and all references to it should be renamed to - "form_widget_single_control": - - Before: - - ``` - {% block url_widget %} - {% spaceless %} - {% set type = type|default('url') %} - {{ block('field_widget') }} - {% endspaceless %} - {% endblock url_widget %} - ``` - - After: - - ``` - {% block url_widget %} - {% spaceless %} - {% set type = type|default('url') %} - {{ block('form_widget_single_control') }} - {% endspaceless %} - {% endblock url_widget %} - ``` - - All other "field_*" blocks and references to them should be renamed to - "form_*". If you previously defined both a "field_*" and a "form_*" - block, you can merge them into a single "form_*" block and check the new - Boolean variable "single_control": - - Before: - - ``` - {% block form_errors %} - {% spaceless %} - ... form code ... - {% endspaceless %} - {% endblock form_errors %} - - {% block field_errors %} - {% spaceless %} - ... field code ... - {% endspaceless %} - {% endblock field_errors %} - ``` - - After: - - ``` - {% block form_errors %} - {% spaceless %} - {% if single_control %} - ... field code ... - {% else %} - ... form code ... - {% endif %} - {% endspaceless %} - {% endblock form_errors %} - ``` - - Furthermore, the block "generic_label" was merged into "form_label". You - should now override "form_label" in order to customize labels. - - Last but not least, the block "widget_choice_options" was renamed to - "choice_widget_options" to be consistent with the rest of the default - theme. - - * The method `guessMinLength()` of FormTypeGuesserInterface was deprecated + * The method `guessMinLength()` of `FormTypeGuesserInterface` was deprecated and will be removed in Symfony 2.3. You should use the new method `guessPattern()` instead which may return any regular expression that - is inserted in the HTML5 attribute "pattern". + is inserted in the HTML5 attribute `pattern`. Before: @@ -480,52 +734,18 @@ )); ``` - * The "data_class" option now *must* be set if a form maps to an object. If - you leave it empty, the form will expect an array or a scalar value and - fail with a corresponding exception. - - Likewise, if a form maps to an array, the option *must* be left empty now. - - * The mapping of property paths to arrays has changed. - - Previously, a property path "street" mapped to both a field `$street` of - a class (or its accessors `getStreet()` and `setStreet()`) and an index - `['street']` of an array or an object implementing `\ArrayAccess`. - - Now, the property path "street" only maps to a class field (or accessors), - while the property path "[street]" only maps to indices. - - If you defined property paths manually in the "property_path" option, you - should revise them and adjust them if necessary. - - Before: - - ``` - $builder->add('name', 'text', array( - 'property_path' => 'address.street', - )); - ``` - - After (if the address object is an array): - - ``` - $builder->add('name', 'text', array( - 'property_path' => 'address[street]', - )); - ``` - - If address is an object in this case, the code given in "Before" - works without changes. - - * The following methods in `Form` are deprecated and will be removed in + * The following methods in `Form` were deprecated and will be removed in Symfony 2.3: * `getTypes` * `getErrorBubbling` * `getNormTransformers` * `getClientTransformers` - - You can access these methods on the `FormConfigInterface` object instead. + * `getAttribute` + * `hasAttribute` + * `getClientData` + * `getChildren` + * `hasChildren` Before: @@ -539,7 +759,25 @@ $form->getConfig()->getErrorBubbling(); ``` - * The option "validation_constraint" is deprecated and will be removed + The method `getClientData` has a new equivalent that is named `getViewData`. + You can access all other methods on the `FormConfigInterface` object instead. + + Instead of `getChildren` and `hasChildren`, you should now use `all` and + `count`. + + Before: + + ``` + if ($form->hasChildren()) { + ``` + + After: + + ``` + if (count($form) > 0) { + ``` + + * The option "validation_constraint" was deprecated and will be removed in Symfony 2.3. You should use the option "constraints" instead, where you can pass one or more constraints for a form. diff --git a/src/Symfony/Bridge/Doctrine/Form/EventListener/MergeDoctrineCollectionListener.php b/src/Symfony/Bridge/Doctrine/Form/EventListener/MergeDoctrineCollectionListener.php index d3e567d69a..d3201d210b 100644 --- a/src/Symfony/Bridge/Doctrine/Form/EventListener/MergeDoctrineCollectionListener.php +++ b/src/Symfony/Bridge/Doctrine/Form/EventListener/MergeDoctrineCollectionListener.php @@ -12,7 +12,7 @@ namespace Symfony\Bridge\Doctrine\Form\EventListener; use Symfony\Component\Form\FormEvents; -use Symfony\Component\Form\Event\FilterDataEvent; +use Symfony\Component\Form\FormEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** @@ -30,10 +30,10 @@ class MergeDoctrineCollectionListener implements EventSubscriberInterface { // Higher priority than core MergeCollectionListener so that this one // is called before - return array(FormEvents::BIND_NORM_DATA => array('onBindNormData', 10)); + return array(FormEvents::BIND => array('onBind', 10)); } - public function onBindNormData(FilterDataEvent $event) + public function onBind(FormEvent $event) { $collection = $event->getForm()->getData(); $data = $event->getData(); diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php index da3ce52964..cdec0e710b 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php @@ -13,13 +13,14 @@ namespace Symfony\Bridge\Doctrine\Form\Type; use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\Common\Persistence\ObjectManager; -use Symfony\Component\Form\FormBuilder; +use Symfony\Component\Form\FormBuilderInterface; use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface; use Symfony\Bridge\Doctrine\Form\EventListener\MergeDoctrineCollectionListener; use Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer; use Symfony\Component\Form\AbstractType; use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolverInterface; abstract class DoctrineType extends AbstractType { @@ -33,7 +34,7 @@ abstract class DoctrineType extends AbstractType $this->registry = $registry; } - public function buildForm(FormBuilder $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options) { if ($options['multiple']) { $builder @@ -43,7 +44,7 @@ abstract class DoctrineType extends AbstractType } } - public function getDefaultOptions() + public function setDefaultOptions(OptionsResolverInterface $resolver) { $registry = $this->registry; $type = $this; @@ -71,7 +72,7 @@ abstract class DoctrineType extends AbstractType ); }; - return array( + $resolver->setDefaults(array( 'em' => null, 'class' => null, 'property' => null, @@ -80,7 +81,7 @@ abstract class DoctrineType extends AbstractType 'choices' => null, 'choice_list' => $choiceList, 'group_by' => null, - ); + )); } /** @@ -93,7 +94,7 @@ abstract class DoctrineType extends AbstractType */ abstract public function getLoader(ObjectManager $manager, $queryBuilder, $class); - public function getParent(array $options) + public function getParent() { return 'choice'; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php index d7a2f11069..38f4dc6695 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php @@ -108,14 +108,14 @@ class EntityTypeTest extends TypeTestCase $this->persist(array($entity1, $entity2)); - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'required' => false, 'property' => 'name' )); - $this->assertEquals(array(1 => new ChoiceView('1', 'Foo'), 2 => new ChoiceView('2', 'Bar')), $field->createView()->get('choices')); + $this->assertEquals(array(1 => new ChoiceView('1', 'Foo'), 2 => new ChoiceView('2', 'Bar')), $field->createView()->getVar('choices')); } public function testSetDataToUninitializedEntityWithNonRequiredToString() @@ -125,13 +125,13 @@ class EntityTypeTest extends TypeTestCase $this->persist(array($entity1, $entity2)); - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'required' => false, )); - $this->assertEquals(array(1 => new ChoiceView('1', 'Foo'), 2 => new ChoiceView('2', 'Bar')), $field->createView()->get('choices')); + $this->assertEquals(array(1 => new ChoiceView('1', 'Foo'), 2 => new ChoiceView('2', 'Bar')), $field->createView()->getVar('choices')); } public function testSetDataToUninitializedEntityWithNonRequiredQueryBuilder() @@ -142,7 +142,7 @@ class EntityTypeTest extends TypeTestCase $this->persist(array($entity1, $entity2)); $qb = $this->em->createQueryBuilder()->select('e')->from(self::SINGLE_IDENT_CLASS, 'e'); - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'required' => false, @@ -150,7 +150,7 @@ class EntityTypeTest extends TypeTestCase 'query_builder' => $qb )); - $this->assertEquals(array(1 => new ChoiceView('1', 'Foo'), 2 => new ChoiceView('2', 'Bar')), $field->createView()->get('choices')); + $this->assertEquals(array(1 => new ChoiceView('1', 'Foo'), 2 => new ChoiceView('2', 'Bar')), $field->createView()->getVar('choices')); } /** @@ -158,7 +158,7 @@ class EntityTypeTest extends TypeTestCase */ public function testConfigureQueryBuilderWithNonQueryBuilderAndNonClosure() { - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'query_builder' => new \stdClass(), @@ -170,7 +170,7 @@ class EntityTypeTest extends TypeTestCase */ public function testConfigureQueryBuilderWithClosureReturningNonQueryBuilder() { - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'query_builder' => function () { @@ -183,7 +183,7 @@ class EntityTypeTest extends TypeTestCase public function testSetDataSingleNull() { - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'multiple' => false, 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, @@ -196,7 +196,7 @@ class EntityTypeTest extends TypeTestCase public function testSetDataMultipleExpandedNull() { - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'multiple' => true, 'expanded' => true, 'em' => 'default', @@ -210,7 +210,7 @@ class EntityTypeTest extends TypeTestCase public function testSetDataMultipleNonExpandedNull() { - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'multiple' => true, 'expanded' => false, 'em' => 'default', @@ -224,7 +224,7 @@ class EntityTypeTest extends TypeTestCase public function testSubmitSingleExpandedNull() { - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'multiple' => false, 'expanded' => true, 'em' => 'default', @@ -238,7 +238,7 @@ class EntityTypeTest extends TypeTestCase public function testSubmitSingleNonExpandedNull() { - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'multiple' => false, 'expanded' => false, 'em' => 'default', @@ -252,7 +252,7 @@ class EntityTypeTest extends TypeTestCase public function testSubmitMultipleNull() { - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'multiple' => true, 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, @@ -270,7 +270,7 @@ class EntityTypeTest extends TypeTestCase $this->persist(array($entity1, $entity2)); - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'multiple' => false, 'expanded' => false, 'em' => 'default', @@ -292,7 +292,7 @@ class EntityTypeTest extends TypeTestCase $this->persist(array($entity1, $entity2)); - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'multiple' => false, 'expanded' => false, 'em' => 'default', @@ -316,7 +316,7 @@ class EntityTypeTest extends TypeTestCase $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'multiple' => true, 'expanded' => false, 'em' => 'default', @@ -341,7 +341,7 @@ class EntityTypeTest extends TypeTestCase $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'multiple' => true, 'expanded' => false, 'em' => 'default', @@ -372,7 +372,7 @@ class EntityTypeTest extends TypeTestCase $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'multiple' => true, 'expanded' => false, 'em' => 'default', @@ -398,7 +398,7 @@ class EntityTypeTest extends TypeTestCase $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'multiple' => true, 'expanded' => false, 'em' => 'default', @@ -428,7 +428,7 @@ class EntityTypeTest extends TypeTestCase $this->persist(array($entity1, $entity2)); - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'multiple' => false, 'expanded' => true, 'em' => 'default', @@ -454,7 +454,7 @@ class EntityTypeTest extends TypeTestCase $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'multiple' => true, 'expanded' => true, 'em' => 'default', @@ -484,7 +484,7 @@ class EntityTypeTest extends TypeTestCase $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, // not all persisted entities should be displayed @@ -494,7 +494,7 @@ class EntityTypeTest extends TypeTestCase $field->bind('2'); - $this->assertEquals(array(1 => new ChoiceView('1', 'Foo'), 2 => new ChoiceView('2', 'Bar')), $field->createView()->get('choices')); + $this->assertEquals(array(1 => new ChoiceView('1', 'Foo'), 2 => new ChoiceView('2', 'Bar')), $field->createView()->getVar('choices')); $this->assertTrue($field->isSynchronized()); $this->assertSame($entity2, $field->getData()); $this->assertSame('2', $field->getClientData()); @@ -509,7 +509,7 @@ class EntityTypeTest extends TypeTestCase $this->persist(array($item1, $item2, $item3, $item4)); - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'em' => 'default', 'class' => self::ITEM_GROUP_CLASS, 'choices' => array($item1, $item2, $item3, $item4), @@ -524,7 +524,7 @@ class EntityTypeTest extends TypeTestCase 'Group1' => array(1 => new ChoiceView('1', 'Foo'), 2 => new ChoiceView('2', 'Bar')), 'Group2' => array(3 => new ChoiceView('3', 'Baz')), '4' => new ChoiceView('4', 'Boo!') - ), $field->createView()->get('choices')); + ), $field->createView()->getVar('choices')); } public function testDisallowChoicesThatAreNotIncluded_choicesSingleIdentifier() @@ -535,7 +535,7 @@ class EntityTypeTest extends TypeTestCase $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'choices' => array($entity1, $entity2), @@ -556,7 +556,7 @@ class EntityTypeTest extends TypeTestCase $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'em' => 'default', 'class' => self::COMPOSITE_IDENT_CLASS, 'choices' => array($entity1, $entity2), @@ -579,7 +579,7 @@ class EntityTypeTest extends TypeTestCase $repository = $this->em->getRepository(self::SINGLE_IDENT_CLASS); - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'query_builder' => $repository->createQueryBuilder('e') @@ -601,7 +601,7 @@ class EntityTypeTest extends TypeTestCase $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'query_builder' => function ($repository) { @@ -625,7 +625,7 @@ class EntityTypeTest extends TypeTestCase $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'em' => 'default', 'class' => self::COMPOSITE_IDENT_CLASS, 'query_builder' => function ($repository) { @@ -647,7 +647,7 @@ class EntityTypeTest extends TypeTestCase $this->persist(array($entity1)); - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'multiple' => false, 'expanded' => false, 'em' => 'default', @@ -668,7 +668,7 @@ class EntityTypeTest extends TypeTestCase $this->persist(array($entity1)); - $field = $this->factory->createNamed('entity', 'name', null, array( + $field = $this->factory->createNamed('name', 'entity', null, array( 'multiple' => false, 'expanded' => false, 'em' => 'default', diff --git a/src/Symfony/Bridge/Propel1/Form/Type/ModelType.php b/src/Symfony/Bridge/Propel1/Form/Type/ModelType.php index 3e2c7b4f78..8e5c151f66 100644 --- a/src/Symfony/Bridge/Propel1/Form/Type/ModelType.php +++ b/src/Symfony/Bridge/Propel1/Form/Type/ModelType.php @@ -14,8 +14,9 @@ namespace Symfony\Bridge\Propel1\Form\Type; use Symfony\Bridge\Propel1\Form\ChoiceList\ModelChoiceList; use Symfony\Bridge\Propel1\Form\DataTransformer\CollectionToArrayTransformer; use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\FormBuilder; +use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolverInterface; /** * ModelType class. @@ -24,14 +25,14 @@ use Symfony\Component\OptionsResolver\Options; */ class ModelType extends AbstractType { - public function buildForm(FormBuilder $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options) { if ($options['multiple']) { $builder->prependClientTransformer(new CollectionToArrayTransformer()); } } - public function getDefaultOptions() + public function setDefaultOptions(OptionsResolverInterface $resolver) { $choiceList = function (Options $options) { return new ModelChoiceList( @@ -43,7 +44,7 @@ class ModelType extends AbstractType ); }; - return array( + $resolver->setDefaults(array( 'template' => 'choice', 'multiple' => false, 'expanded' => false, @@ -54,10 +55,10 @@ class ModelType extends AbstractType 'choice_list' => $choiceList, 'group_by' => null, 'by_reference' => false, - ); + )); } - public function getParent(array $options) + public function getParent() { return 'choice'; } diff --git a/src/Symfony/Bridge/Twig/Extension/FormExtension.php b/src/Symfony/Bridge/Twig/Extension/FormExtension.php index dfe4414d71..3a4c7ce926 100644 --- a/src/Symfony/Bridge/Twig/Extension/FormExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/FormExtension.php @@ -98,7 +98,7 @@ class FormExtension extends \Twig_Extension public function isChoiceSelected(FormView $view, ChoiceView $choice) { - return FormUtil::isChoiceSelected($choice->getValue(), $view->get('value')); + return FormUtil::isChoiceSelected($choice->getValue(), $view->getVar('value')); } /** @@ -228,7 +228,7 @@ class FormExtension extends \Twig_Extension } } - $custom = '_'.$view->get('id'); + $custom = '_'.$view->getVar('id'); $rendering = $custom.$section; $blocks = $this->getBlocks($view); @@ -237,11 +237,11 @@ class FormExtension extends \Twig_Extension $types = $this->varStack[$rendering]['types']; $this->varStack[$rendering]['variables'] = array_replace_recursive($this->varStack[$rendering]['variables'], $variables); } else { - $types = $view->get('types'); + $types = $view->getVar('types'); $types[] = $custom; $typeIndex = count($types) - 1; $this->varStack[$rendering] = array( - 'variables' => array_replace_recursive($view->all(), $variables), + 'variables' => array_replace_recursive($view->getVars(), $variables), 'types' => $types, ); } diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig index e26610f5d3..bff18c6e1a 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig @@ -2,20 +2,20 @@ {% block form_widget %} {% spaceless %} - {% if single_control %} - {{ block('form_widget_single_control') }} - {% else %} + {% if compound %} {{ block('form_widget_compound') }} + {% else %} + {{ block('form_widget_simple') }} {% endif %} {% endspaceless %} {% endblock form_widget %} -{% block form_widget_single_control %} +{% block form_widget_simple %} {% spaceless %} {% set type = type|default('text') %} {% endspaceless %} -{% endblock form_widget_single_control %} +{% endblock form_widget_simple %} {% block form_widget_compound %} {% spaceless %} @@ -112,7 +112,7 @@ {% block datetime_widget %} {% spaceless %} {% if widget == 'single_text' %} - {{ block('form_widget_single_control') }} + {{ block('form_widget_simple') }} {% else %}
- * function (Options $options, $previousValue)
+ * function (Options $options, $value)
*
*
- * The second parameter passed to the closure is the previous default
+ * The second parameter passed to the closure is the current default
* value of the option.
*
* @param string $option The option name.
diff --git a/src/Symfony/Component/OptionsResolver/OptionsResolver.php b/src/Symfony/Component/OptionsResolver/OptionsResolver.php
index 0feeeddfa7..1ead9c2820 100644
--- a/src/Symfony/Component/OptionsResolver/OptionsResolver.php
+++ b/src/Symfony/Component/OptionsResolver/OptionsResolver.php
@@ -21,7 +21,7 @@ use Symfony\Component\OptionsResolver\Exception\MissingOptionsException;
* @author Bernhard Schussek
+ * function (Options $options)
+ * function (Options $options, $value)
+ *
+ *
+ * The second parameter passed to the closure is the previously
+ * set default value, in case you are overwriting an existing
+ * default value.
+ *
+ * The closures should return the lazily created option value.
+ *
+ * @param array $defaultValues A list of option names as keys and default
+ * values or closures as values.
+ *
+ * @return OptionsResolverInterface The resolver instance.
+ */
+ function setDefaults(array $defaultValues);
+
+ /**
+ * Replaces default option values.
+ *
+ * Old defaults are erased, which means that closures passed here cannot
+ * access the previous default value. This may be useful to improve
+ * performance if the previous default value is calculated by an expensive
+ * closure.
+ *
+ * @param array $defaultValues A list of option names as keys and default
+ * values or closures as values.
+ *
+ * @return OptionsResolverInterface The resolver instance.
+ */
+ function replaceDefaults(array $defaultValues);
+
+ /**
+ * Sets optional options.
+ *
+ * This method is identical to {@ĺink setDefaults()}, only that no default values
+ * are configured for the options. If these options are not passed to
+ * {@link resolve()}, they will be missing in the final options array. This can be
+ * helpful if you want to determine whether an option has been set or not.
+ *
+ * @param array $optionNames A list of option names.
+ *
+ * @return OptionsResolverInterface The resolver instance.
+ *
+ * @throws Exception\OptionDefinitionException When trying to pass default values.
+ */
+ function setOptional(array $optionNames);
+
+ /**
+ * Sets required options.
+ *
+ * If these options are not passed to {@link resolve()} and no default has been set for
+ * them, an exception will be thrown.
+ *
+ * @param array $optionNames A list of option names.
+ *
+ * @return OptionsResolverInterface The resolver instance.
+ *
+ * @throws Exception\OptionDefinitionException When trying to pass default values.
+ */
+ function setRequired(array $optionNames);
+
+ /**
+ * Sets allowed values for a list of options.
+ *
+ * @param array $allowedValues A list of option names as keys and arrays
+ * with values acceptable for that option as
+ * values.
+ *
+ * @return OptionsResolverInterface The resolver instance.
+ *
+ * @throws Exception\InvalidOptionsException If an option has not been defined for
+ * which an allowed value is set.
+ */
+ function setAllowedValues(array $allowedValues);
+
+ /**
+ * Adds allowed values for a list of options.
+ *
+ * The values are merged with the allowed values defined previously.
+ *
+ * @param array $allowedValues A list of option names as keys and arrays
+ * with values acceptable for that option as
+ * values.
+ *
+ * @return OptionsResolverInterface The resolver instance.
+ *
+ * @throws Exception\InvalidOptionsException If an option has not been defined
+ * (see {@link isKnown()}) for which
+ * an allowed value is set.
+ */
+ function addAllowedValues(array $allowedValues);
+
+ /**
+ * Sets allowed types for a list of options.
+ *
+ * @param array $allowedTypes A list of option names as keys and type
+ * names passed as string or array as values.
+ *
+ * @return OptionsResolverInterface The resolver instance.
+ *
+ * @throws Exception\InvalidOptionsException If an option has not been defined for
+ * which an allowed type is set.
+ */
+ function setAllowedTypes(array $allowedTypes);
+
+ /**
+ * Adds allowed types for a list of options.
+ *
+ * The types are merged with the allowed types defined previously.
+ *
+ * @param array $allowedTypes A list of option names as keys and type
+ * names passed as string or array as values.
+ *
+ * @return OptionsResolverInterface The resolver instance.
+ *
+ * @throws Exception\InvalidOptionsException If an option has not been defined for
+ * which an allowed type is set.
+ */
+ function addAllowedTypes(array $allowedTypes);
+
+ /**
+ * Sets filters that are applied on resolved options.
+ *
+ * The filters should be closures with the following signature:
+ *
+ *
+ * function (Options $options, $value)
+ *
+ *
+ * The second parameter passed to the closure is the value of
+ * the option.
+ *
+ * The closure should return the filtered value.
+ *
+ * @param array $filters An array of filter closures.
+ *
+ * @return OptionsResolverInterface The resolver instance.
+ */
+ function setFilters(array $filters);
+
+ /**
+ * Returns whether an option is known.
+ *
+ * An option is known if it has been passed to either {@link setDefaults()},
+ * {@link setRequired()} or {@link setOptional()} before.
+ *
+ * @param string $option The name of the option.
+ *
+ * @return Boolean Whether the option is known.
+ */
+ function isKnown($option);
+
+ /**
+ * Returns whether an option is required.
+ *
+ * An option is required if it has been passed to {@link setRequired()},
+ * but not to {@link setDefaults()}. That is, the option has been declared
+ * as required and no default value has been set.
+ *
+ * @param string $option The name of the option.
+ *
+ * @return Boolean Whether the option is required.
+ */
+ function isRequired($option);
+
+ /**
+ * Returns the combination of the default and the passed options.
+ *
+ * @param array $options The custom option values.
+ *
+ * @return array A list of options and their values.
+ *
+ * @throws Exception\InvalidOptionsException If any of the passed options has not
+ * been defined or does not contain an
+ * allowed value.
+ * @throws Exception\MissingOptionsException If a required option is missing.
+ * @throws Exception\OptionDefinitionException If a cyclic dependency is detected
+ * between two lazy options.
+ */
+ function resolve(array $options);
+}
diff --git a/src/Symfony/Component/OptionsResolver/README.md b/src/Symfony/Component/OptionsResolver/README.md
index c2280011e4..84a87787b5 100644
--- a/src/Symfony/Component/OptionsResolver/README.md
+++ b/src/Symfony/Component/OptionsResolver/README.md
@@ -22,12 +22,12 @@ possible, and may only be one of "male" and "female".
public function __construct(array $options = array())
{
$resolver = new OptionsResolver();
- $this->configure($resolver);
+ $this->setDefaultOptions($resolver);
$this->options = $resolver->resolve($options);
}
- protected function configure(OptionsResolver $resolver)
+ protected function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setRequired(array(
'firstName',
@@ -68,7 +68,7 @@ We can also override the default values of the optional options:
'age' => 30,
));
-Options can be added or changed in subclasses by overriding the `configure`
+Options can be added or changed in subclasses by overriding the `setDefaultOptions`
method:
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -76,9 +76,9 @@ method:
class Employee extends Person
{
- protected function configure(OptionsResolver $resolver)
+ protected function setDefaultOptions(OptionsResolverInterface $resolver)
{
- parent::configure($resolver);
+ parent::setDefaultOptions($resolver);
$resolver->setRequired(array(
'birthDate',
diff --git a/src/Symfony/Component/OptionsResolver/Tests/OptionResolverTest.php b/src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php
similarity index 67%
rename from src/Symfony/Component/OptionsResolver/Tests/OptionResolverTest.php
rename to src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php
index ef68c7f1e1..d112dd30f8 100644
--- a/src/Symfony/Component/OptionsResolver/Tests/OptionResolverTest.php
+++ b/src/Symfony/Component/OptionsResolver/Tests/OptionsResolverTest.php
@@ -238,13 +238,13 @@ class OptionsResolverTest extends \PHPUnit_Framework_TestCase
'two' => '2',
));
- $this->resolver->addAllowedValues(array(
- 'one' => array('1'),
- 'two' => array('2'),
+ $this->resolver->setAllowedValues(array(
+ 'one' => '1',
+ 'two' => '2',
));
$this->resolver->addAllowedValues(array(
- 'one' => array('one'),
- 'two' => array('two'),
+ 'one' => 'one',
+ 'two' => 'two',
));
$options = array(
@@ -276,6 +276,171 @@ class OptionsResolverTest extends \PHPUnit_Framework_TestCase
));
}
+ public function testResolveSucceedsIfOptionTypeAllowed()
+ {
+ $this->resolver->setDefaults(array(
+ 'one' => '1',
+ ));
+
+ $this->resolver->setAllowedTypes(array(
+ 'one' => 'string',
+ ));
+
+ $options = array(
+ 'one' => 'one',
+ );
+
+ $this->assertEquals(array(
+ 'one' => 'one',
+ ), $this->resolver->resolve($options));
+ }
+
+ public function testResolveSucceedsIfOptionTypeAllowedPassArray()
+ {
+ $this->resolver->setDefaults(array(
+ 'one' => '1',
+ ));
+
+ $this->resolver->setAllowedTypes(array(
+ 'one' => array('string', 'bool'),
+ ));
+
+ $options = array(
+ 'one' => true,
+ );
+
+ $this->assertEquals(array(
+ 'one' => true,
+ ), $this->resolver->resolve($options));
+ }
+
+ public function testResolveSucceedsIfOptionTypeAllowedPassObject()
+ {
+ $this->resolver->setDefaults(array(
+ 'one' => '1',
+ ));
+
+ $this->resolver->setAllowedTypes(array(
+ 'one' => 'object',
+ ));
+
+ $object = new \stdClass();
+ $options = array(
+ 'one' => $object,
+ );
+
+ $this->assertEquals(array(
+ 'one' => $object,
+ ), $this->resolver->resolve($options));
+ }
+
+ public function testResolveSucceedsIfOptionTypeAllowedPassClass()
+ {
+ $this->resolver->setDefaults(array(
+ 'one' => '1',
+ ));
+
+ $this->resolver->setAllowedTypes(array(
+ 'one' => '\stdClass',
+ ));
+
+ $object = new \stdClass();
+ $options = array(
+ 'one' => $object,
+ );
+
+ $this->assertEquals(array(
+ 'one' => $object,
+ ), $this->resolver->resolve($options));
+ }
+
+ public function testResolveSucceedsIfOptionTypeAllowedAddTypes()
+ {
+ $this->resolver->setDefaults(array(
+ 'one' => '1',
+ 'two' => '2',
+ ));
+
+ $this->resolver->setAllowedTypes(array(
+ 'one' => 'string',
+ 'two' => 'bool',
+ ));
+ $this->resolver->addAllowedTypes(array(
+ 'one' => 'float',
+ 'two' => 'integer',
+ ));
+
+ $options = array(
+ 'one' => 1.23,
+ 'two' => false,
+ );
+
+ $this->assertEquals(array(
+ 'one' => 1.23,
+ 'two' => false,
+ ), $this->resolver->resolve($options));
+ }
+
+ /**
+ * @expectedException Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
+ */
+ public function testResolveFailsIfOptionTypeNotAllowed()
+ {
+ $this->resolver->setDefaults(array(
+ 'one' => '1',
+ ));
+
+ $this->resolver->setAllowedTypes(array(
+ 'one' => array('string', 'bool'),
+ ));
+
+ $this->resolver->resolve(array(
+ 'one' => 1.23,
+ ));
+ }
+
+ /**
+ * @expectedException Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
+ */
+ public function testResolveFailsIfOptionTypeNotAllowedMultipleOptions()
+ {
+ $this->resolver->setDefaults(array(
+ 'one' => '1',
+ 'two' => '2',
+ ));
+
+ $this->resolver->setAllowedTypes(array(
+ 'one' => 'string',
+ 'two' => 'bool',
+ ));
+
+ $this->resolver->resolve(array(
+ 'one' => 'foo',
+ 'two' => 1.23,
+ ));
+ }
+
+ /**
+ * @expectedException Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
+ */
+ public function testResolveFailsIfOptionTypeNotAllowedAddTypes()
+ {
+ $this->resolver->setDefaults(array(
+ 'one' => '1',
+ ));
+
+ $this->resolver->setAllowedTypes(array(
+ 'one' => 'string',
+ ));
+ $this->resolver->addAllowedTypes(array(
+ 'one' => 'bool',
+ ));
+
+ $this->resolver->resolve(array(
+ 'one' => 1.23,
+ ));
+ }
+
/**
* @expectedException Symfony\Component\OptionsResolver\Exception\OptionDefinitionException
*/
@@ -373,6 +538,36 @@ class OptionsResolverTest extends \PHPUnit_Framework_TestCase
$this->assertFalse($this->resolver->isRequired('foo'));
}
+ public function testFiltersTransformFinalOptions()
+ {
+ $this->resolver->setDefaults(array(
+ 'foo' => 'bar',
+ 'bam' => 'baz',
+ ));
+ $this->resolver->setFilters(array(
+ 'foo' => function (Options $options, $value) {
+ return $options['bam'] . '[' . $value . ']';
+ },
+ ));
+
+ $expected = array(
+ 'foo' => 'baz[bar]',
+ 'bam' => 'baz',
+ );
+
+ $this->assertEquals($expected, $this->resolver->resolve(array()));
+
+ $expected = array(
+ 'foo' => 'boo[custom]',
+ 'bam' => 'boo',
+ );
+
+ $this->assertEquals($expected, $this->resolver->resolve(array(
+ 'foo' => 'custom',
+ 'bam' => 'boo',
+ )));
+ }
+
public function testResolveWithoutOptionSucceedsIfRequiredAndDefaultValue()
{
$this->resolver->setRequired(array(