Commits -------8329087
[Form] Moved calculation of ChoiceType options to closures5adec19
[Form] Fixed typoscb87ccb
[Form] Failing test for empty_data option BC breakb733045
[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: #3354, #3512, #3685, #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 👏 --------------------------------------------------------------------------- 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.
520 lines
14 KiB
Markdown
520 lines
14 KiB
Markdown
UPGRADE FROM 2.0 to 2.1
|
|
=======================
|
|
|
|
### General
|
|
|
|
* The merging strategy for `assets_base_urls` and `base_urls` has changed.
|
|
|
|
Unlike most configuration blocks, successive values for `assets_base_urls`
|
|
will overwrite each other instead of being merged. This behavior was chosen
|
|
because developers will typically define base URL's for each environment.
|
|
Given that most projects tend to inherit configurations (e.g.
|
|
`config_test.yml` imports `config_dev.yml`) and/or share a common base
|
|
configuration (i.e. `config.yml`), merging could yield a set of base URL's
|
|
for multiple environments.
|
|
|
|
### HttpFoundation
|
|
|
|
* Locale management was moved from the Session class to the Request class.
|
|
|
|
##### Configuring the default locale
|
|
|
|
Before:
|
|
|
|
```
|
|
framework:
|
|
session:
|
|
default_locale: fr
|
|
```
|
|
|
|
After:
|
|
|
|
```
|
|
framework:
|
|
default_locale: fr
|
|
```
|
|
|
|
* The methods `getPathInfo()`, `getBaseUrl()` and `getBasePath()` of
|
|
a `Request` now all return a raw value (vs a urldecoded value before). Any call
|
|
to one of these methods must be checked and wrapped in a `rawurldecode()` if
|
|
needed.
|
|
|
|
##### Retrieving the locale from a Twig template
|
|
|
|
Before: `{{ app.request.session.locale }}` or `{{ app.session.locale }}`
|
|
|
|
After: `{{ app.request.locale }}`
|
|
|
|
##### Retrieving the locale from a PHP template
|
|
|
|
Before: `$view['session']->getLocale()`
|
|
|
|
After: `$view['request']->getLocale()`
|
|
|
|
##### Retrieving the locale from PHP code
|
|
|
|
Before: `$session->getLocale()`
|
|
|
|
After: `$request->getLocale()`
|
|
|
|
### Security
|
|
|
|
* `Symfony\Component\Security\Core\User\UserInterface::equals()` has moved to
|
|
`Symfony\Component\Security\Core\User\EquatableInterface::isEqualTo()`.
|
|
|
|
You must rename the `equals()` method in your implementation of the `User`
|
|
class to `isEqualTo()` and implement `EquatableInterface`. Apart from that,
|
|
no other changes are required.
|
|
|
|
Alternatively, you may use the default implementation provided by
|
|
`AbstractToken::hasUserChanged()` if you have no need of custom comparison
|
|
logic. In this case, do not implement `EquatableInterface` and remove your
|
|
comparison method.
|
|
|
|
Before:
|
|
|
|
```
|
|
class User implements UserInterface
|
|
{
|
|
// ...
|
|
public function equals(UserInterface $user) { /* ... */ }
|
|
// ...
|
|
}
|
|
```
|
|
|
|
After:
|
|
|
|
```
|
|
class User implements UserInterface, EquatableInterface
|
|
{
|
|
// ...
|
|
public function isEqualTo(UserInterface $user) { /* ... */ }
|
|
// ...
|
|
}
|
|
```
|
|
* The custom factories for the firewall configuration are now
|
|
registered during the build method of bundles instead of being registered
|
|
by the end-user. This means that you will you need to remove the 'factories'
|
|
keys in your security configuration.
|
|
|
|
* The Firewall listener is now registered after the Router listener. This
|
|
means that specific Firewall URLs (like /login_check and /logout) must now
|
|
have proper routes defined in your routing configuration.
|
|
|
|
* The user provider configuration has been refactored. The configuration
|
|
for the chain provider and the memory provider has been changed:
|
|
|
|
Before:
|
|
|
|
``` yaml
|
|
security:
|
|
providers:
|
|
my_chain_provider:
|
|
providers: [my_memory_provider, my_doctrine_provider]
|
|
my_memory_provider:
|
|
users:
|
|
toto: { password: foobar, roles: [ROLE_USER] }
|
|
foo: { password: bar, roles: [ROLE_USER, ROLE_ADMIN] }
|
|
```
|
|
|
|
After:
|
|
|
|
``` yaml
|
|
security:
|
|
providers:
|
|
my_chain_provider:
|
|
chain:
|
|
providers: [my_memory_provider, my_doctrine_provider]
|
|
my_memory_provider:
|
|
memory:
|
|
users:
|
|
toto: { password: foobar, roles: [ROLE_USER] }
|
|
foo: { password: bar, roles: [ROLE_USER, ROLE_ADMIN] }
|
|
```
|
|
|
|
### Form and Validator
|
|
|
|
* 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.
|
|
|
|
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`
|
|
on the parent form.
|
|
|
|
* Changed implementation of choice lists
|
|
|
|
ArrayChoiceList was replaced. If you have custom classes that extend this
|
|
class, you must now extend SimpleChoiceList.
|
|
|
|
Before:
|
|
|
|
```
|
|
class MyChoiceList extends ArrayChoiceList
|
|
{
|
|
protected function load()
|
|
{
|
|
parent::load();
|
|
|
|
// load choices
|
|
|
|
$this->choices = $choices;
|
|
}
|
|
}
|
|
```
|
|
|
|
After:
|
|
|
|
```
|
|
class MyChoiceList extends SimpleChoiceList
|
|
{
|
|
public function __construct()
|
|
{
|
|
// load choices
|
|
|
|
parent::__construct($choices);
|
|
}
|
|
}
|
|
```
|
|
|
|
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.
|
|
|
|
```
|
|
class MyChoiceList extends LazyChoiceList
|
|
{
|
|
protected function loadChoiceList()
|
|
{
|
|
// load choices
|
|
|
|
return new SimpleChoiceList($choices);
|
|
}
|
|
}
|
|
```
|
|
|
|
PaddedChoiceList, MonthChoiceList and TimezoneChoiceList were removed.
|
|
Their functionality was merged into DateType, TimeType and TimezoneType.
|
|
|
|
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. The indices of the
|
|
array are controlled by the choice field's `index_generation` option.
|
|
|
|
Before:
|
|
|
|
```
|
|
{% for choice, label in choices %}
|
|
<option value="{{ choice }}"{% if _form_is_choice_selected(form, choice) %} selected="selected"{% endif %}>
|
|
{{ label }}
|
|
</option>
|
|
{% endfor %}
|
|
```
|
|
|
|
After:
|
|
|
|
```
|
|
{% for choice in choices %}
|
|
<option value="{{ choice.value }}"{% if _form_is_choice_selected(form, choice) %} selected="selected"{% endif %}>
|
|
{{ choice.label }}
|
|
</option>
|
|
{% 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 methods `setMessage()`, `getMessageTemplate()` and
|
|
`getMessageParameters()` in the Constraint class were deprecated.
|
|
|
|
If you have implemented custom validators, you should use the
|
|
`addViolation()` method on the `ExecutionContext` object instead.
|
|
|
|
Before:
|
|
|
|
```
|
|
public function isValid($value, Constraint $constraint)
|
|
{
|
|
// ...
|
|
if (!$valid) {
|
|
$this->setMessage($constraint->message, array(
|
|
'{{ value }}' => $value,
|
|
));
|
|
|
|
return false;
|
|
}
|
|
}
|
|
```
|
|
|
|
After:
|
|
|
|
```
|
|
public function isValid($value, Constraint $constraint)
|
|
{
|
|
// ...
|
|
if (!$valid) {
|
|
$this->context->addViolation($constraint->message, array(
|
|
'{{ value }}' => $value,
|
|
));
|
|
|
|
return false;
|
|
}
|
|
}
|
|
```
|
|
|
|
* The method `setPropertyPath()` in the ExecutionContext class
|
|
was removed.
|
|
|
|
You should use the `addViolationAtSubPath()` method on the
|
|
`ExecutionContext` object instead.
|
|
|
|
Before:
|
|
|
|
```
|
|
public function isPropertyValid(ExecutionContext $context)
|
|
{
|
|
// ...
|
|
$propertyPath = $context->getPropertyPath() . '.property';
|
|
$context->setPropertyPath($propertyPath);
|
|
$context->addViolation('Error Message', array(), null);
|
|
}
|
|
```
|
|
|
|
After:
|
|
|
|
```
|
|
public function isPropertyValid(ExecutionContext $context)
|
|
{
|
|
// ...
|
|
$context->addViolationAtSubPath('property', 'Error Message', array(), null);
|
|
|
|
}
|
|
```
|
|
|
|
* The options passed to the `getParent()` method of form types no longer
|
|
contain default options. They only contain the options passed by the user.
|
|
|
|
You should check if options exist before attempting to read their value.
|
|
|
|
Before:
|
|
|
|
```
|
|
public function getParent(array $options)
|
|
{
|
|
return 'single_text' === $options['widget'] ? 'text' : 'choice';
|
|
}
|
|
```
|
|
|
|
After:
|
|
|
|
```
|
|
public function getParent(array $options)
|
|
{
|
|
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.
|
|
|
|
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`.
|
|
|
|
### Session
|
|
|
|
* Flash messages now return an array based on their type. The old method is
|
|
still available but is now deprecated.
|
|
|
|
##### Retrieving the flash messages from a Twig template
|
|
|
|
Before:
|
|
|
|
```
|
|
{% if app.session.hasFlash('notice') %}
|
|
<div class="flash-notice">
|
|
{{ app.session.getFlash('notice') }}
|
|
</div>
|
|
{% endif %}
|
|
```
|
|
After:
|
|
|
|
```
|
|
{% for flashMessage in app.session.flashbag.get('notice') %}
|
|
<div class="flash-notice">
|
|
{{ flashMessage }}
|
|
</div>
|
|
{% endfor %}
|
|
```
|
|
|
|
You can process all flash messages in a single loop with:
|
|
|
|
```
|
|
{% for type, flashMessages in app.session.flashbag.all() %}
|
|
{% for flashMessage in flashMessages %}
|
|
<div class="flash-{{ type }}">
|
|
{{ flashMessage }}
|
|
</div>
|
|
{% endfor %}
|
|
{% endfor %}
|
|
```
|
|
|
|
* Session handler drivers should implement `\SessionHandlerInterface` or extend from
|
|
`Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeHandlerInterface` base class and renamed
|
|
to `Handler\FooSessionHandler`. E.g. `PdoSessionStorage` becomes `Handler\PdoSessionHandler`.
|
|
|
|
* Refactor code using `$session->*flash*()` methods to use `$session->getFlashBag()->*()`.
|
|
|
|
### Serializer
|
|
|
|
* The key names craeted by the `GetSetMethodNormalizer` have changed from
|
|
from all lowercased to camelCased (e.g. `mypropertyvalue` to `myPropertyValue`).
|
|
|
|
* The `item` element is now converted to an array when deserializing XML.
|
|
|
|
``` xml
|
|
<?xml version="1.0"?>
|
|
<response>
|
|
<item><title><![CDATA[title1]]></title></item><item><title><![CDATA[title2]]></title></item>
|
|
</response>
|
|
```
|
|
|
|
Before:
|
|
|
|
Array()
|
|
|
|
After:
|
|
|
|
Array(
|
|
[item] => Array(
|
|
[0] => Array(
|
|
[title] => title1
|
|
)
|
|
[1] => Array(
|
|
[title] => title2
|
|
)
|
|
)
|
|
)
|
|
|
|
### FrameworkBundle
|
|
|
|
* session options: lifetime, path, domain, secure, httponly were deprecated.
|
|
Prefixed versions should now be used instead: cookie_lifetime, cookie_path, cookie_domain, cookie_secure, cookie_httponly
|
|
|
|
Before:
|
|
|
|
```
|
|
framework:
|
|
session:
|
|
lifetime: 3600
|
|
path: \
|
|
domain: example.com
|
|
secure: true
|
|
httponly: true
|
|
```
|
|
|
|
After:
|
|
|
|
```
|
|
framework:
|
|
session:
|
|
cookie_lifetime: 3600
|
|
cookie_path: \
|
|
cookie_domain: example.com
|
|
cookie_secure: true
|
|
cookie_httponly: true
|
|
```
|
|
|
|
Added `handler_id`, defaults to `session.handler.native_file`.
|
|
|
|
```
|
|
framework:
|
|
session:
|
|
storage_id: session.storage.native
|
|
handler_id: session.handler.native_file
|
|
```
|
|
|
|
To use mock session storage use the following. `handler_id` is irrelevant in this context.
|
|
|
|
```
|
|
framework:
|
|
session:
|
|
storage_id: session.storage.mock_file
|
|
```
|
|
### WebProfilerBundle
|
|
|
|
* You must clear old profiles after upgrading to 2.1. If you are using a
|
|
database then you will need to remove the table.
|
|
|