Skip to content

Commit

Permalink
merged branch bschussek/issue3354 (PR symfony#3789)
Browse files Browse the repository at this point in the history
Commits
-------

8329087 [Form] Moved calculation of ChoiceType options to closures
5adec19 [Form] Fixed typos
cb87ccb [Form] Failing test for empty_data option BC break
b733045 [Form] Fixed option support in Form component

Discussion
----------

[Form] Fixed option support in Form component

Bug fix: yes
Feature addition: no
Backwards compatibility break: yes
Symfony2 tests pass: yes
Fixes the following tickets: symfony#3354, symfony#3512, symfony#3685, symfony#3694
Todo: -

![Travis Build Status](https://secure.travis-ci.org/bschussek/symfony.png?branch=issue3354)

This PR also introduces a new helper `DefaultOptions` for solving option graphs. It accepts default options to be defined on various layers of your class hierarchy. These options can then be merged with the options passed by the user. This is called *resolving*.

The important feature of this utility is that it lets you define *lazy options*. Lazy options are specified using closures that are evaluated when resolving and thus have access to the resolved values of other (potentially lazy) options. The class detects cyclic option dependencies and fails with an exception in this case.

For more information, check the inline documentation of the `DefaultOptions` class and the UPGRADE file.

@fabpot: Might this be worth a separate component? (in total the utility consists of five classes with two associated tests)

---------------------------------------------------------------------------

by beberlei at 2012-04-05T08:54:10Z

"The important feature of this utility is that it lets you define lazy options. Lazy options are specified using closures"

What about options that are closures? are those differentiated?

---------------------------------------------------------------------------

by bschussek at 2012-04-05T08:57:35Z

@beberlei Yes. Closures for lazy options receive a Symfony\Component\Form\Options instance as first argument. All other closures are interpreted as normal values.

---------------------------------------------------------------------------

by stof at 2012-04-05T11:09:49Z

I'm wondering if these classes should go in the Config component. My issue with it is that it would add a required dependency to the Config component and that the Config component mixes many different things in it already (the loader part, the resource part, the definition part...)

---------------------------------------------------------------------------

by sstok at 2012-04-06T13:36:36Z

Sharing the Options class would be great, and its more then one class so why not give it its own Component folder?
Filesystem is just one class, and that has its own folder.

Great job on the class bschussek :clap:

---------------------------------------------------------------------------

by bschussek at 2012-04-10T12:32:34Z

@fabpot Any input?

---------------------------------------------------------------------------

by bschussek at 2012-04-10T13:54:13Z

@fabpot Apart from the decision about the final location of DefaultOptions et al., could you merge this soon? This would make my work a bit easier since this one is a blocker.

---------------------------------------------------------------------------

by fabpot at 2012-04-10T18:08:18Z

@bschussek: Can you rebase on master? I will merge afterwards. Thanks.
  • Loading branch information
fabpot committed Apr 11, 2012
2 parents d167fd4 + 8329087 commit 05842c5
Show file tree
Hide file tree
Showing 53 changed files with 1,313 additions and 245 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG-2.1.md
Expand Up @@ -256,7 +256,6 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c
* forms now don't create an empty object anymore if they are completely
empty and not required. The empty value for such forms is null.
* added constant Guess::VERY_HIGH_CONFIDENCE
* FormType::getDefaultOptions() now sees default options defined by parent types
* [BC BREAK] FormType::getParent() does not see default options anymore
* [BC BREAK] The methods `add`, `remove`, `setParent`, `bind` and `setData`
in class Form now throw an exception if the form is already bound
Expand All @@ -266,6 +265,8 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c
"single_text" unless "with_seconds" is set to true
* checkboxes of in an expanded multiple-choice field don't include the choice
in their name anymore. Their names terminate with "[]" now.
* [BC BREAK] FormType::getDefaultOptions() and FormType::getAllowedOptionValues()
don't receive an options array anymore.

### HttpFoundation

Expand Down
38 changes: 37 additions & 1 deletion UPGRADE-2.1.md
Expand Up @@ -326,7 +326,7 @@
```

* The options passed to the `getParent()` method of form types no longer
contain default options.
contain default options. They only contain the options passed by the user.

You should check if options exist before attempting to read their value.

Expand All @@ -347,6 +347,42 @@
return isset($options['widget']) && 'single_text' === $options['widget'] ? 'text' : 'choice';
}
```

* 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.

Before:

```
public function getDefaultOptions(array $options)
{
$defaultOptions = array();
if ($options['multiple']) {
$defaultOptions['empty_data'] = array();
}
return $defaultOptions;
}
```

After:

```
public function getDefaultOptions()
{
return array(
'empty_data' => function (Options $options, $previousValue) {
return $options['multiple'] ? array() : $previousValue;
}
);
}
```

The second argument `$previousValue` 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.
Expand Down
46 changes: 27 additions & 19 deletions src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php
Expand Up @@ -19,6 +19,7 @@
use Symfony\Bridge\Doctrine\Form\EventListener\MergeDoctrineCollectionListener;
use Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Options;

abstract class DoctrineType extends AbstractType
{
Expand All @@ -42,37 +43,44 @@ public function buildForm(FormBuilder $builder, array $options)
}
}

public function getDefaultOptions(array $options)
public function getDefaultOptions()
{
$defaultOptions = array(
'em' => null,
'class' => null,
'property' => null,
'query_builder' => null,
'loader' => null,
'group_by' => null,
);
$registry = $this->registry;
$type = $this;

$options = array_replace($defaultOptions, $options);
$loader = function (Options $options) use ($type, $registry) {
if (null !== $options['query_builder']) {
$manager = $registry->getManager($options['em']);

if (!isset($options['choice_list'])) {
$manager = $this->registry->getManager($options['em']);

if (isset($options['query_builder'])) {
$options['loader'] = $this->getLoader($manager, $options);
return $type->getLoader($manager, $options['query_builder'], $options['class']);
}

$defaultOptions['choice_list'] = new EntityChoiceList(
return null;
};

$choiceList = function (Options $options) use ($registry) {
$manager = $registry->getManager($options['em']);

return new EntityChoiceList(
$manager,
$options['class'],
$options['property'],
$options['loader'],
$options['choices'],
$options['group_by']
);
}
};

return $defaultOptions;
return array(
'em' => null,
'class' => null,
'property' => null,
'query_builder' => null,
'loader' => $loader,
'choices' => null,
'choice_list' => $choiceList,
'group_by' => null,
);
}

/**
Expand All @@ -82,7 +90,7 @@ public function getDefaultOptions(array $options)
* @param array $options
* @return EntityLoaderInterface
*/
abstract protected function getLoader(ObjectManager $manager, array $options);
abstract public function getLoader(ObjectManager $manager, $queryBuilder, $class);

public function getParent(array $options)
{
Expand Down
6 changes: 3 additions & 3 deletions src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php
Expand Up @@ -23,12 +23,12 @@ class EntityType extends DoctrineType
* @param array $options
* @return ORMQueryBuilderLoader
*/
protected function getLoader(ObjectManager $manager, array $options)
public function getLoader(ObjectManager $manager, $queryBuilder, $class)
{
return new ORMQueryBuilderLoader(
$options['query_builder'],
$queryBuilder,
$manager,
$options['class']
$class
);
}

Expand Down
30 changes: 14 additions & 16 deletions src/Symfony/Bridge/Propel1/Form/Type/ModelType.php
Expand Up @@ -14,6 +14,7 @@
use Symfony\Bridge\Propel1\Form\ChoiceList\ModelChoiceList;
use Symfony\Bridge\Propel1\Form\DataTransformer\CollectionToArrayTransformer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Options;
use Symfony\Component\Form\FormBuilder;

/**
Expand All @@ -30,33 +31,30 @@ public function buildForm(FormBuilder $builder, array $options)
}
}

public function getDefaultOptions(array $options)
public function getDefaultOptions()
{
$defaultOptions = array(
$choiceList = function (Options $options) {
return new ModelChoiceList(
$options['class'],
$options['property'],
$options['choices'],
$options['query'],
$options['group_by']
);
};

return array(
'template' => 'choice',
'multiple' => false,
'expanded' => false,
'class' => null,
'property' => null,
'query' => null,
'choices' => null,
'choice_list' => $choiceList,
'group_by' => null,
'by_reference' => false,
);

$options = array_replace($defaultOptions, $options);

if (!isset($options['choice_list'])) {
$defaultOptions['choice_list'] = new ModelChoiceList(
$options['class'],
$options['property'],
$options['choices'],
$options['query'],
$options['group_by']
);
}

return $defaultOptions;
}

public function getParent(array $options)
Expand Down
Expand Up @@ -76,7 +76,7 @@ public function buildForm(FormBuilder $builder, array $options)
/**
* @see Symfony\Component\Form\AbstractType::getDefaultOptions()
*/
public function getDefaultOptions(array $options)
public function getDefaultOptions()
{
/* Note: the form's intention must correspond to that for the form login
* listener in order for the CSRF token to validate successfully.
Expand Down
4 changes: 2 additions & 2 deletions src/Symfony/Component/Form/AbstractType.php
Expand Up @@ -96,7 +96,7 @@ public function createBuilder($name, FormFactoryInterface $factory, array $optio
*
* @return array The default options
*/
public function getDefaultOptions(array $options)
public function getDefaultOptions()
{
return array();
}
Expand All @@ -108,7 +108,7 @@ public function getDefaultOptions(array $options)
*
* @return array The allowed option values
*/
public function getAllowedOptionValues(array $options)
public function getAllowedOptionValues()
{
return array();
}
Expand Down
4 changes: 2 additions & 2 deletions src/Symfony/Component/Form/AbstractTypeExtension.php
Expand Up @@ -65,7 +65,7 @@ public function buildViewBottomUp(FormView $view, FormInterface $form)
*
* @return array
*/
public function getDefaultOptions(array $options)
public function getDefaultOptions()
{
return array();
}
Expand All @@ -77,7 +77,7 @@ public function getDefaultOptions(array $options)
*
* @return array The allowed option values
*/
public function getAllowedOptionValues(array $options)
public function getAllowedOptionValues()
{
return array();
}
Expand Down

0 comments on commit 05842c5

Please sign in to comment.