merged branch bschussek/issue3878 (PR #3923)

Commits
-------

6e4ed9e [Form] Fixed regression: bind(null) was not converted to an empty string anymore
fcb2227 [Form] Deprecated FieldType, which has been merged into FormType
bfa7ef2 [Form] Removed obsolete exceptions
2a49449 [Form] Simplified CSRF mechanism and removed "csrf" type

Discussion
----------

[Form] Merged FieldType into FormType

Bug fix: no
Feature addition: no
Backwards compatibility break: yes
Symfony2 tests pass: yes
Fixes the following tickets: #3878
Todo: update the documentation on theming

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

This PR is a preparatory PR for #3879. See also #3878.

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

by juliendidier at 2012-04-13T14:25:19Z

What's the benefit ?

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

by henrikbjorn at 2012-04-13T14:26:40Z

why `input_widget` ? and not just `widget`

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

by Burgov at 2012-04-13T14:27:49Z

@juliendidier dynamic inheritance is now obsolete which fixes some other issues

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

by stloyd at 2012-04-13T14:37:26Z

What about __not__ breaking API so *badly* and leaving `FieldType` which will be simple like (with marking as deprecated):

```php
<?php

class FieldType extends AbstractType
{
    public function getParent(array $options)
    {
        return 'form';
    }

    public function getName()
    {
        return 'field';
    }
}

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

by bschussek at 2012-04-13T14:43:41Z

@stloyd That's a very good idea.

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

by mvrhov at 2012-04-13T17:41:21Z

IMHO what @stloyd proposed sounds like a good idea, but removing FieldType class, if #3903 will come into life might ensure that more forms will broke and people will check them thoroughly.

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

by r1pp3rj4ck at 2012-04-13T18:46:08Z

@bschussek looks great, but I'm concerned about how quickly will the third-party bundles adapt to this BC break. I hope really quick, because if they don't the whole stuff will be useless :S of course it's not your problem to solve.

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

by stof at 2012-04-13T18:50:32Z

@r1pp3rj4ck there is already another BC break requiring to update custom types for Symfony master. So third party bundles already have to do some work.

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

by r1pp3rj4ck at 2012-04-13T18:59:37Z

@stof which one? I've looked into @bschussek 's RFC about these [foo].bar stuff, but it's not yet implemented. Are you refering to this or another one I've missed?

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

by stof at 2012-04-13T19:04:06Z

@r1pp3rj4ck the change regarding default options

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

by r1pp3rj4ck at 2012-04-13T19:06:10Z

@stof oh, I forgot that one. Weird thing is that I've already changed my default options today and still forgetting these stuff :D

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

by bschussek at 2012-04-14T08:58:29Z

I restored and deprecated FieldType now. I'd appreciate further reviews.

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

by stloyd at 2012-04-14T09:02:32Z

Maybe we should try to avoid this BC in templates ? What do you think about similar move like with `FieldType` ? (hold old, but inside just render new)

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

by bschussek at 2012-04-14T09:07:22Z

@stloyd You mean for those cases where people explicitely render the block "field_*"? We can do that.

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

by stloyd at 2012-04-14T09:09:45Z

@bschussek Yes I mean this case =) Sorry for not being explicit, I need some coffee I think =)

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

by bschussek at 2012-04-17T14:45:35Z

I added the field_* blocks again for BC. Could someone please review again? Otherwise this can be merged.

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

by Burgov at 2012-04-17T15:11:16Z

@bschussek I'm not sure what has changed to cause this, but if I try out your branch on our forms, if I leave the value of an input empty, eventually the reverseTransform method receives a null value, rather than a '' (empty string) value, as on the current symfony master.

DateTimeToLocalizedStringTransformer, for example, will throw an Exception if the value is not a string

```php
if (!is_string($value)) {
   throw new UnexpectedTypeException($value, 'string');
}
```

Other than that, all forms render just the same as they do on symfony master

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

by bschussek at 2012-04-17T15:30:29Z

@Burgov Fixed.
This commit is contained in:
Fabien Potencier 2012-04-18 12:50:00 +02:00
commit 85bb439356
91 changed files with 1339 additions and 1507 deletions

View File

@ -267,8 +267,12 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c
in their name anymore. Their names terminate with "[]" now. in their name anymore. Their names terminate with "[]" now.
* [BC BREAK] FormType::getDefaultOptions() and FormType::getAllowedOptionValues() * [BC BREAK] FormType::getDefaultOptions() and FormType::getAllowedOptionValues()
don't receive an options array anymore. don't receive an options array anymore.
* Deprecated FormValidatorInterface and substituted its implementations * deprecated FormValidatorInterface and substituted its implementations
by event subscribers by event subscribers
* simplified CSRF protection and removed the csrf type
* deprecated FieldType and merged it into FormType
* [BC BREAK] renamed "field_*" theme blocks to "form_*" and "field_widget" to
"input"
### HttpFoundation ### HttpFoundation

View File

@ -358,6 +358,13 @@
(or any other of the BIND events). In case you used the CallbackValidator (or any other of the BIND events). In case you used the CallbackValidator
class, you should now pass the callback directly to `addEventListener`. class, you should now pass the callback directly to `addEventListener`.
* simplified CSRF protection and removed the csrf type
* deprecated FieldType and merged it into FormType
* [BC BREAK] renamed "field_*" theme blocks to "form_*" and "field_widget" to
"input"
### Validator ### Validator
* The methods `setMessage()`, `getMessageTemplate()` and * The methods `setMessage()`, `getMessageTemplate()` and

View File

@ -2,10 +2,14 @@
{% block form_widget %} {% block form_widget %}
{% spaceless %} {% spaceless %}
<div {{ block('widget_container_attributes') }}> {% if form.children|length > 0 %}
{{ block('field_rows') }} <div {{ block('widget_container_attributes') }}>
{{ form_rest(form) }} {{ block('form_rows') }}
</div> {{ form_rest(form) }}
</div>
{% else %}
{{ block('input') }}
{% endif %}
{% endspaceless %} {% endspaceless %}
{% endblock form_widget %} {% endblock form_widget %}
@ -83,7 +87,7 @@
{% block datetime_widget %} {% block datetime_widget %}
{% spaceless %} {% spaceless %}
{% if widget == 'single_text' %} {% if widget == 'single_text' %}
{{ block('field_widget') }} {{ block('input') }}
{% else %} {% else %}
<div {{ block('widget_container_attributes') }}> <div {{ block('widget_container_attributes') }}>
{{ form_errors(form.date) }} {{ form_errors(form.date) }}
@ -98,7 +102,7 @@
{% block date_widget %} {% block date_widget %}
{% spaceless %} {% spaceless %}
{% if widget == 'single_text' %} {% if widget == 'single_text' %}
{{ block('field_widget') }} {{ block('input') }}
{% else %} {% else %}
<div {{ block('widget_container_attributes') }}> <div {{ block('widget_container_attributes') }}>
{{ date_pattern|replace({ {{ date_pattern|replace({
@ -114,7 +118,7 @@
{% block time_widget %} {% block time_widget %}
{% spaceless %} {% spaceless %}
{% if widget == 'single_text' %} {% if widget == 'single_text' %}
{{ block('field_widget') }} {{ block('input') }}
{% else %} {% else %}
<div {{ block('widget_container_attributes') }}> <div {{ block('widget_container_attributes') }}>
{{ form_widget(form.hour, { 'attr': { 'size': '1' } }) }}:{{ form_widget(form.minute, { 'attr': { 'size': '1' } }) }}{% if with_seconds %}:{{ form_widget(form.second, { 'attr': { 'size': '1' } }) }}{% endif %} {{ form_widget(form.hour, { 'attr': { 'size': '1' } }) }}:{{ form_widget(form.minute, { 'attr': { 'size': '1' } }) }}{% if with_seconds %}:{{ form_widget(form.second, { 'attr': { 'size': '1' } }) }}{% endif %}
@ -127,67 +131,60 @@
{% spaceless %} {% spaceless %}
{# type="number" doesn't work with floats #} {# type="number" doesn't work with floats #}
{% set type = type|default('text') %} {% set type = type|default('text') %}
{{ block('field_widget') }} {{ block('input') }}
{% endspaceless %} {% endspaceless %}
{% endblock number_widget %} {% endblock number_widget %}
{% block integer_widget %} {% block integer_widget %}
{% spaceless %} {% spaceless %}
{% set type = type|default('number') %} {% set type = type|default('number') %}
{{ block('field_widget') }} {{ block('input') }}
{% endspaceless %} {% endspaceless %}
{% endblock integer_widget %} {% endblock integer_widget %}
{% block money_widget %} {% block money_widget %}
{% spaceless %} {% spaceless %}
{{ money_pattern|replace({ '{{ widget }}': block('field_widget') })|raw }} {{ money_pattern|replace({ '{{ widget }}': block('input') })|raw }}
{% endspaceless %} {% endspaceless %}
{% endblock money_widget %} {% endblock money_widget %}
{% block url_widget %} {% block url_widget %}
{% spaceless %} {% spaceless %}
{% set type = type|default('url') %} {% set type = type|default('url') %}
{{ block('field_widget') }} {{ block('input') }}
{% endspaceless %} {% endspaceless %}
{% endblock url_widget %} {% endblock url_widget %}
{% block search_widget %} {% block search_widget %}
{% spaceless %} {% spaceless %}
{% set type = type|default('search') %} {% set type = type|default('search') %}
{{ block('field_widget') }} {{ block('input') }}
{% endspaceless %} {% endspaceless %}
{% endblock search_widget %} {% endblock search_widget %}
{% block percent_widget %} {% block percent_widget %}
{% spaceless %} {% spaceless %}
{% set type = type|default('text') %} {% set type = type|default('text') %}
{{ block('field_widget') }} % {{ block('input') }} %
{% endspaceless %} {% endspaceless %}
{% endblock percent_widget %} {% endblock percent_widget %}
{% block field_widget %}
{% spaceless %}
{% set type = type|default('text') %}
<input type="{{ type }}" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %}/>
{% endspaceless %}
{% endblock field_widget %}
{% block password_widget %} {% block password_widget %}
{% spaceless %} {% spaceless %}
{% set type = type|default('password') %} {% set type = type|default('password') %}
{{ block('field_widget') }} {{ block('input') }}
{% endspaceless %} {% endspaceless %}
{% endblock password_widget %} {% endblock password_widget %}
{% block hidden_widget %} {% block hidden_widget %}
{% set type = type|default('hidden') %} {% set type = type|default('hidden') %}
{{ block('field_widget') }} {{ block('input') }}
{% endblock hidden_widget %} {% endblock hidden_widget %}
{% block email_widget %} {% block email_widget %}
{% spaceless %} {% spaceless %}
{% set type = type|default('email') %} {% set type = type|default('email') %}
{{ block('field_widget') }} {{ block('input') }}
{% endspaceless %} {% endspaceless %}
{% endblock email_widget %} {% endblock email_widget %}
@ -202,15 +199,11 @@
{% endspaceless %} {% endspaceless %}
{% endblock %} {% endblock %}
{% block field_label %}
{% spaceless %}
{% set attr = attr|merge({'for': id}) %}
{{ block('generic_label') }}
{% endspaceless %}
{% endblock field_label %}
{% block form_label %} {% block form_label %}
{% spaceless %} {% spaceless %}
{% if form.children|length == 0 %}
{% set attr = attr|merge({'for': id}) %}
{% endif %}
{{ block('generic_label') }} {{ block('generic_label') }}
{% endspaceless %} {% endspaceless %}
{% endblock form_label %} {% endblock form_label %}
@ -219,24 +212,17 @@
{% block repeated_row %} {% block repeated_row %}
{% spaceless %} {% spaceless %}
{{ block('field_rows') }} {{ block('form_rows') }}
{% endspaceless %} {% endspaceless %}
{% endblock repeated_row %} {% endblock repeated_row %}
{% block field_row %}
{% spaceless %}
<div>
{{ form_label(form, label|default(null)) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
</div>
{% endspaceless %}
{% endblock field_row %}
{% block form_row %} {% block form_row %}
{% spaceless %} {% spaceless %}
<div> <div>
{{ form_label(form, label|default(null)) }} {{ form_label(form, label|default(null)) }}
{% if form.children|length == 0 %}
{{ form_errors(form) }}
{% endif %}
{{ form_widget(form) }} {{ form_widget(form) }}
</div> </div>
{% endspaceless %} {% endspaceless %}
@ -248,13 +234,13 @@
{# Misc #} {# Misc #}
{% block field_enctype %} {% block form_enctype %}
{% spaceless %} {% spaceless %}
{% if multipart %}enctype="multipart/form-data"{% endif %} {% if multipart %}enctype="multipart/form-data"{% endif %}
{% endspaceless %} {% endspaceless %}
{% endblock field_enctype %} {% endblock form_enctype %}
{% block field_errors %} {% block form_errors %}
{% spaceless %} {% spaceless %}
{% if errors|length > 0 %} {% if errors|length > 0 %}
<ul> <ul>
@ -270,9 +256,9 @@
</ul> </ul>
{% endif %} {% endif %}
{% endspaceless %} {% endspaceless %}
{% endblock field_errors %} {% endblock form_errors %}
{% block field_rest %} {% block form_rest %}
{% spaceless %} {% spaceless %}
{% for child in form %} {% for child in form %}
{% if not child.rendered %} {% if not child.rendered %}
@ -280,18 +266,25 @@
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% endspaceless %} {% endspaceless %}
{% endblock field_rest %} {% endblock form_rest %}
{# Support #} {# Support #}
{% block field_rows %} {% block form_rows %}
{% spaceless %} {% spaceless %}
{{ form_errors(form) }} {{ form_errors(form) }}
{% for child in form %} {% for child in form %}
{{ form_row(child) }} {{ form_row(child) }}
{% endfor %} {% endfor %}
{% endspaceless %} {% endspaceless %}
{% endblock field_rows %} {% endblock form_rows %}
{% block input %}
{% spaceless %}
{% set type = type|default('text') %}
<input type="{{ type }}" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %}/>
{% endspaceless %}
{% endblock input %}
{% block widget_attributes %} {% block widget_attributes %}
{% spaceless %} {% spaceless %}
@ -306,3 +299,47 @@
{% for attrname,attrvalue in attr %}{{attrname}}="{{attrvalue}}" {% endfor %} {% for attrname,attrvalue in attr %}{{attrname}}="{{attrvalue}}" {% endfor %}
{% endspaceless %} {% endspaceless %}
{% endblock widget_container_attributes %} {% endblock widget_container_attributes %}
{# Deprecated in Symfony 2.1, to be removed in 2.3 #}
{% block field_widget %}
{% spaceless %}
{{ block('input') }}
{% endspaceless %}
{% endblock field_widget %}
{% block field_label %}
{% spaceless %}
{{ block('form_label') }}
{% endspaceless %}
{% endblock field_label %}
{% block field_row %}
{% spaceless %}
{{ block('form_row') }}
{% endspaceless %}
{% endblock field_row %}
{% block field_enctype %}
{% spaceless %}
{{ block('form_enctype') }}
{% endspaceless %}
{% endblock field_enctype %}
{% block field_errors %}
{% spaceless %}
{{ block('form_errors') }}
{% endspaceless %}
{% endblock field_errors %}
{% block field_rest %}
{% spaceless %}
{{ block('form_rest') }}
{% endspaceless %}
{% endblock field_rest %}
{% block field_rows %}
{% spaceless %}
{{ block('form_rows') }}
{% endspaceless %}
{% endblock field_rows %}

View File

@ -1,19 +1,5 @@
{% use "form_div_layout.html.twig" %} {% use "form_div_layout.html.twig" %}
{% block field_row %}
{% spaceless %}
<tr>
<td>
{{ form_label(form, label|default(null)) }}
</td>
<td>
{{ form_errors(form) }}
{{ form_widget(form) }}
</td>
</tr>
{% endspaceless %}
{% endblock field_row %}
{% block form_row %} {% block form_row %}
{% spaceless %} {% spaceless %}
<tr> <tr>
@ -21,6 +7,9 @@
{{ form_label(form, label|default(null)) }} {{ form_label(form, label|default(null)) }}
</td> </td>
<td> <td>
{% if form.children|length == 0 %}
{{ form_errors(form) }}
{% endif %}
{{ form_widget(form) }} {{ form_widget(form) }}
</td> </td>
</tr> </tr>
@ -29,12 +18,16 @@
{% block form_errors %} {% block form_errors %}
{% spaceless %} {% spaceless %}
{% if errors|length > 0 %} {% if form.children|length > 0 %}
<tr> {% if errors|length > 0 %}
<td colspan="2"> <tr>
{{ block('field_errors') }} <td colspan="2">
</td> {{ parent() }}
</tr> </td>
</tr>
{% endif %}
{% else %}
{{ parent() }}
{% endif %} {% endif %}
{% endspaceless %} {% endspaceless %}
{% endblock form_errors %} {% endblock form_errors %}
@ -51,9 +44,13 @@
{% block form_widget %} {% block form_widget %}
{% spaceless %} {% spaceless %}
<table {{ block('widget_container_attributes') }}> {% if form.children|length > 0 %}
{{ block('field_rows') }} <table {{ block('widget_container_attributes') }}>
{{ form_rest(form) }} {{ block('form_rows') }}
</table> {{ form_rest(form) }}
</table>
{% else %}
{{ parent() }}
{% endif %}
{% endspaceless %} {% endspaceless %}
{% endblock form_widget %} {% endblock form_widget %}

View File

@ -1,3 +1,3 @@
{% block field_label %} {% block form_label %}
<label>child</label> <label>child</label>
{% endblock field_label %} {% endblock form_label %}

View File

@ -1,3 +1,3 @@
{% block field_label %} {% block form_label %}
<label>parent</label> <label>parent</label>
{% endblock field_label %} {% endblock form_label %}

View File

@ -1,6 +1,6 @@
{% block field_widget %} {% block input %}
{% spaceless %} {% spaceless %}
{% set type = type|default('text') %} {% set type = type|default('text') %}
<input type="{{ type }}" {{ block('attributes') }} value="{{ value }}" rel="theme" /> <input type="{{ type }}" {{ block('attributes') }} value="{{ value }}" rel="theme" />
{% endspaceless %} {% endspaceless %}
{% endblock field_widget %} {% endblock input %}

View File

@ -1,8 +1,8 @@
{% extends 'form_div_layout.html.twig' %} {% extends 'form_div_layout.html.twig' %}
{% block field_widget %} {% block input %}
{% spaceless %} {% spaceless %}
{% set type = type|default('text') %} {% set type = type|default('text') %}
<input type="{{ type }}" {{ block('attributes') }} value="{{ value }}" rel="theme" /> <input type="{{ type }}" {{ block('attributes') }} value="{{ value }}" rel="theme" />
{% endspaceless %} {% endspaceless %}
{% endblock field_widget %} {% endblock input %}

View File

@ -1,8 +1,8 @@
{% use 'form_div_layout.html.twig' %} {% use 'form_div_layout.html.twig' %}
{% block field_widget %} {% block input %}
{% spaceless %} {% spaceless %}
{% set type = type|default('text') %} {% set type = type|default('text') %}
<input type="{{ type }}" {{ block('attributes') }} value="{{ value }}" rel="theme" /> <input type="{{ type }}" {{ block('attributes') }} value="{{ value }}" rel="theme" />
{% endspaceless %} {% endspaceless %}
{% endblock field_widget %} {% endblock input %}

View File

@ -52,7 +52,6 @@
<!-- CoreExtension --> <!-- CoreExtension -->
<service id="form.type.field" class="Symfony\Component\Form\Extension\Core\Type\FieldType"> <service id="form.type.field" class="Symfony\Component\Form\Extension\Core\Type\FieldType">
<tag name="form.type" alias="field" /> <tag name="form.type" alias="field" />
<argument type="service" id="validator" />
</service> </service>
<service id="form.type.form" class="Symfony\Component\Form\Extension\Core\Type\FormType"> <service id="form.type.form" class="Symfony\Component\Form\Extension\Core\Type\FormType">
<tag name="form.type" alias="form" /> <tag name="form.type" alias="form" />
@ -133,8 +132,8 @@
<tag name="form.type" alias="url" /> <tag name="form.type" alias="url" />
</service> </service>
<!-- FieldTypeValidatorExtension --> <!-- FormTypeValidatorExtension -->
<service id="form.type_extension.field" class="Symfony\Component\Form\Extension\Validator\Type\FieldTypeValidatorExtension"> <service id="form.type_extension.field" class="Symfony\Component\Form\Extension\Validator\Type\FormTypeValidatorExtension">
<tag name="form.type_extension" alias="field" /> <tag name="form.type_extension" alias="field" />
<argument type="service" id="validator" /> <argument type="service" id="validator" />
</service> </service>

View File

@ -14,12 +14,9 @@
<argument>%kernel.secret%</argument> <argument>%kernel.secret%</argument>
</service> </service>
<service id="form.type.csrf" class="Symfony\Component\Form\Extension\Csrf\Type\CsrfType">
<tag name="form.type" alias="csrf" />
<argument type="service" id="form.csrf_provider" />
</service>
<service id="form.type_extension.csrf" class="Symfony\Component\Form\Extension\Csrf\Type\FormTypeCsrfExtension"> <service id="form.type_extension.csrf" class="Symfony\Component\Form\Extension\Csrf\Type\FormTypeCsrfExtension">
<tag name="form.type_extension" alias="form" /> <tag name="form.type_extension" alias="form" />
<argument type="service" id="form.csrf_provider" />
<argument>%form.type_extension.csrf.enabled%</argument> <argument>%form.type_extension.csrf.enabled%</argument>
<argument>%form.type_extension.csrf.field_name%</argument> <argument>%form.type_extension.csrf.field_name%</argument>
</service> </service>

View File

@ -1,5 +1,5 @@
<?php if ($widget == 'single_text'): ?> <?php if ($widget == 'single_text'): ?>
<?php echo $view['form']->renderBlock('field_widget'); ?> <?php echo $view['form']->renderBlock('input'); ?>
<?php else: ?> <?php else: ?>
<div <?php echo $view['form']->renderBlock('container_attributes') ?>> <div <?php echo $view['form']->renderBlock('container_attributes') ?>>
<?php echo str_replace(array('{{ year }}', '{{ month }}', '{{ day }}'), array( <?php echo str_replace(array('{{ year }}', '{{ month }}', '{{ day }}'), array(

View File

@ -1,5 +1,5 @@
<?php if ($widget == 'single_text'): ?> <?php if ($widget == 'single_text'): ?>
<?php echo $view['form']->renderBlock('field_widget'); ?> <?php echo $view['form']->renderBlock('input'); ?>
<?php else: ?> <?php else: ?>
<div <?php echo $view['form']->renderBlock('container_attributes') ?>> <div <?php echo $view['form']->renderBlock('container_attributes') ?>>
<?php echo $view['form']->widget($form['date']).' '.$view['form']->widget($form['time']) ?> <?php echo $view['form']->widget($form['date']).' '.$view['form']->widget($form['time']) ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('field_widget', array('type' => isset($type) ? $type : 'email')) ?> <?php echo $view['form']->renderBlock('input', array('type' => isset($type) ? $type : 'email')) ?>

View File

@ -1 +1 @@
<?php if ($form->get('multipart')): ?>enctype="multipart/form-data"<?php endif ?> <?php echo $view['form']->renderBlock('form_enctype') ?>

View File

@ -1,21 +1 @@
<?php if ($errors): ?> <?php echo $view['form']->renderBlock('form_errors') ?>
<ul>
<?php foreach ($errors as $error): ?>
<li><?php
if (null === $error->getMessagePluralization()) {
echo $view['translator']->trans(
$error->getMessageTemplate(),
$error->getMessageParameters(),
'validators'
);
} else {
echo $view['translator']->transChoice(
$error->getMessageTemplate(),
$error->getMessagePluralization(),
$error->getMessageParameters(),
'validators'
);
}?></li>
<?php endforeach; ?>
</ul>
<?php endif ?>

View File

@ -1,2 +1 @@
<?php if ($required) { $attr['class'] = (isset($attr['class']) ? $attr['class'] : '').' required'; } ?> <?php echo $view['form']->renderBlock('form_label') ?>
<label for="<?php echo $view->escape($id) ?>" <?php foreach($attr as $k => $v) { printf('%s="%s" ', $view->escape($k), $view->escape($v)); } ?>><?php echo $view->escape($view['translator']->trans($label, array(), $translation_domain)) ?></label>

View File

@ -1,5 +1 @@
<?php foreach ($form as $child): ?> <?php echo $view['form']->renderBlock('form_rest') ?>
<?php if (!$child->isRendered()): ?>
<?php echo $view['form']->row($child) ?>
<?php endif; ?>
<?php endforeach; ?>

View File

@ -1,5 +1 @@
<div> <?php echo $view['form']->renderBlock('form_row') ?>
<?php echo $view['form']->label($form, isset($label) ? $label : null) ?>
<?php echo $view['form']->errors($form) ?>
<?php echo $view['form']->widget($form) ?>
</div>

View File

@ -1,4 +1 @@
<?php echo $view['form']->errors($form) ?> <?php echo $view['form']->renderBlock('form_rows') ?>
<?php foreach ($form as $child) : ?>
<?php echo $view['form']->row($child) ?>
<?php endforeach; ?>

View File

@ -1,5 +1 @@
<input <?php echo $view['form']->renderBlock('input') ?>
type="<?php echo isset($type) ? $view->escape($type) : "text" ?>"
<?php if (!empty($value)): ?>value="<?php echo $view->escape($value) ?>"<?php endif ?>
<?php echo $view['form']->renderBlock('attributes') ?>
/>

View File

@ -0,0 +1 @@
<?php if ($form->get('multipart')): ?>enctype="multipart/form-data"<?php endif ?>

View File

@ -0,0 +1,21 @@
<?php if ($errors): ?>
<ul>
<?php foreach ($errors as $error): ?>
<li><?php
if (null === $error->getMessagePluralization()) {
echo $view['translator']->trans(
$error->getMessageTemplate(),
$error->getMessageParameters(),
'validators'
);
} else {
echo $view['translator']->transChoice(
$error->getMessageTemplate(),
$error->getMessagePluralization(),
$error->getMessageParameters(),
'validators'
);
}?></li>
<?php endforeach; ?>
</ul>
<?php endif ?>

View File

@ -1,2 +1,3 @@
<?php if ($required) { $attr['class'] = (isset($attr['class']) ? $attr['class'] : '').' required'; } ?> <?php if ($required) { $attr['class'] = (isset($attr['class']) ? $attr['class'] : '').' required'; } ?>
<?php if (!$form->hasChildren()) { $attr['for'] = $id; } ?>
<label <?php foreach($attr as $k => $v) { printf('%s="%s" ', $view->escape($k), $view->escape($v)); } ?>><?php echo $view->escape($view['translator']->trans($label, array(), $translation_domain)) ?></label> <label <?php foreach($attr as $k => $v) { printf('%s="%s" ', $view->escape($k), $view->escape($v)); } ?>><?php echo $view->escape($view['translator']->trans($label, array(), $translation_domain)) ?></label>

View File

@ -0,0 +1,5 @@
<?php foreach ($form as $child): ?>
<?php if (!$child->isRendered()): ?>
<?php echo $view['form']->row($child) ?>
<?php endif; ?>
<?php endforeach; ?>

View File

@ -1,4 +1,7 @@
<div> <div>
<?php echo $view['form']->label($form, isset($label) ? $label : null) ?> <?php echo $view['form']->label($form, isset($label) ? $label : null) ?>
<?php if (!$form->hasChildren()): ?>
<?php echo $view['form']->errors($form) ?>
<?php endif ?>
<?php echo $view['form']->widget($form) ?> <?php echo $view['form']->widget($form) ?>
</div> </div>

View File

@ -0,0 +1,4 @@
<?php echo $view['form']->errors($form) ?>
<?php foreach ($form as $child) : ?>
<?php echo $view['form']->row($child) ?>
<?php endforeach; ?>

View File

@ -1,5 +1,8 @@
<?php if ($form->hasChildren()): ?>
<div <?php echo $view['form']->renderBlock('container_attributes') ?>> <div <?php echo $view['form']->renderBlock('container_attributes') ?>>
<?php echo $view['form']->renderBlock('field_rows') ?> <?php echo $view['form']->renderBlock('form_rows') ?>
<?php echo $view['form']->rest($form) ?> <?php echo $view['form']->rest($form) ?>
</div> </div>
<?php else: ?>
<?php echo $view['form']->renderBlock('input')?>
<?php endif ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('field_widget', array('type' => isset($type) ? $type : "hidden")) ?> <?php echo $view['form']->renderBlock('input', array('type' => isset($type) ? $type : "hidden")) ?>

View File

@ -0,0 +1,5 @@
<input
type="<?php echo isset($type) ? $view->escape($type) : "text" ?>"
<?php if (!empty($value)): ?>value="<?php echo $view->escape($value) ?>"<?php endif ?>
<?php echo $view['form']->renderBlock('attributes') ?>
/>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('field_widget', array('type' => isset($type) ? $type : "number")) ?> <?php echo $view['form']->renderBlock('input', array('type' => isset($type) ? $type : "number")) ?>

View File

@ -1 +1 @@
<?php echo str_replace('{{ widget }}', $view['form']->renderBlock('field_widget'), $money_pattern) ?> <?php echo str_replace('{{ widget }}', $view['form']->renderBlock('input'), $money_pattern) ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('field_widget', array('type' => isset($type) ? $type : "text")) ?> <?php echo $view['form']->renderBlock('input', array('type' => isset($type) ? $type : "text")) ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('field_widget', array('type' => isset($type) ? $type : "password")) ?> <?php echo $view['form']->renderBlock('input', array('type' => isset($type) ? $type : "password")) ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('field_widget', array('type' => isset($type) ? $type : "text")) ?> % <?php echo $view['form']->renderBlock('input', array('type' => isset($type) ? $type : "text")) ?> %

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('field_rows') ?> <?php echo $view['form']->renderBlock('form_rows') ?>

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('field_widget', array('type' => isset($type) ? $type : "search")) ?> <?php echo $view['form']->renderBlock('input', array('type' => isset($type) ? $type : "search")) ?>

View File

@ -1,5 +1,5 @@
<?php if ($widget == 'single_text'): ?> <?php if ($widget == 'single_text'): ?>
<?php echo $view['form']->renderBlock('field_widget'); ?> <?php echo $view['form']->renderBlock('input'); ?>
<?php else: ?> <?php else: ?>
<div <?php echo $view['form']->renderBlock('container_attributes') ?>> <div <?php echo $view['form']->renderBlock('container_attributes') ?>>
<?php <?php

View File

@ -1 +1 @@
<?php echo $view['form']->renderBlock('field_widget', array('type' => isset($type) ? $type : "url")) ?> <?php echo $view['form']->renderBlock('input', array('type' => isset($type) ? $type : "url")) ?>

View File

@ -1,9 +0,0 @@
<tr>
<td>
<?php echo $view['form']->label($form, isset($label) ? $label : null) ?>
</td>
<td>
<?php echo $view['form']->errors($form) ?>
<?php echo $view['form']->widget($form) ?>
</td>
</tr>

View File

@ -1,7 +1,51 @@
<?php if (0 < count($errors)) : ?> <?php if ($form->hasChildren()): ?>
<tr> <?php if (count($errors) > 0): ?>
<td colspan="2"> <tr>
<?php echo $view['form']->renderBlock('field_errors'); ?> <td colspan="2">
</td> <?php if ($errors): ?>
</tr> <ul>
<?php endif; ?> <?php foreach ($errors as $error): ?>
<li><?php
if (null === $error->getMessagePluralization()) {
echo $view['translator']->trans(
$error->getMessageTemplate(),
$error->getMessageParameters(),
'validators'
);
} else {
echo $view['translator']->transChoice(
$error->getMessageTemplate(),
$error->getMessagePluralization(),
$error->getMessageParameters(),
'validators'
);
}?></li>
<?php endforeach; ?>
</ul>
<?php endif ?>
</td>
</tr>
<?php endif; ?>
<?php else: ?>
<?php if ($errors): ?>
<ul>
<?php foreach ($errors as $error): ?>
<li><?php
if (null === $error->getMessagePluralization()) {
echo $view['translator']->trans(
$error->getMessageTemplate(),
$error->getMessageParameters(),
'validators'
);
} else {
echo $view['translator']->transChoice(
$error->getMessageTemplate(),
$error->getMessagePluralization(),
$error->getMessageParameters(),
'validators'
);
}?></li>
<?php endforeach; ?>
</ul>
<?php endif ?>
<?php endif; ?>

View File

@ -3,6 +3,9 @@
<?php echo $view['form']->label($form, isset($label) ? $label : null) ?> <?php echo $view['form']->label($form, isset($label) ? $label : null) ?>
</td> </td>
<td> <td>
<?php if (!$form->hasChildren()): ?>
<?php echo $view['form']->errors($form) ?>
<?php endif ?>
<?php echo $view['form']->widget($form) ?> <?php echo $view['form']->widget($form) ?>
</td> </td>
</tr> </tr>

View File

@ -1,5 +1,8 @@
<?php if ($form->hasChildren()): ?>
<table <?php echo $view['form']->renderBlock('container_attributes') ?>> <table <?php echo $view['form']->renderBlock('container_attributes') ?>>
<?php echo $view['form']->renderBlock('field_rows') ?> <?php echo $view['form']->renderBlock('form_rows') ?>
<?php echo $view['form']->rest($form) ?> <?php echo $view['form']->rest($form) ?>
</table> </table>
<?php else: ?>
<?php echo $view['form']->renderBlock('input')?>
<?php endif ?>

View File

@ -119,7 +119,7 @@ class FormHelper extends Helper
*/ */
public function widget(FormView $view, array $variables = array()) public function widget(FormView $view, array $variables = array())
{ {
return trim($this->renderSection($view, 'widget', $variables)); return $this->renderSection($view, 'widget', $variables);
} }
/** /**
@ -276,7 +276,7 @@ class FormHelper extends Helper
$view->setRendered(); $view->setRendered();
} }
return $html; return trim($html);
} }
} while (--$typeIndex >= 0); } while (--$typeIndex >= 0);
@ -311,7 +311,7 @@ class FormHelper extends Helper
$variables = array_replace_recursive($context['variables'], $variables); $variables = array_replace_recursive($context['variables'], $variables);
return $this->engine->render($template, $variables); return trim($this->engine->render($template, $variables));
} }
public function getName() public function getName()

View File

@ -27,9 +27,9 @@ abstract class FrameworkExtensionTest extends TestCase
$def = $container->getDefinition('form.type_extension.csrf'); $def = $container->getDefinition('form.type_extension.csrf');
$this->assertTrue($container->getParameter('form.type_extension.csrf.enabled')); $this->assertTrue($container->getParameter('form.type_extension.csrf.enabled'));
$this->assertEquals('%form.type_extension.csrf.enabled%', $def->getArgument(0)); $this->assertEquals('%form.type_extension.csrf.enabled%', $def->getArgument(1));
$this->assertEquals('_csrf', $container->getParameter('form.type_extension.csrf.field_name')); $this->assertEquals('_csrf', $container->getParameter('form.type_extension.csrf.field_name'));
$this->assertEquals('%form.type_extension.csrf.field_name%', $def->getArgument(1)); $this->assertEquals('%form.type_extension.csrf.field_name%', $def->getArgument(2));
$this->assertEquals('s3cr3t', $container->getParameterBag()->resolveValue($container->findDefinition('form.csrf_provider')->getArgument(1))); $this->assertEquals('s3cr3t', $container->getParameterBag()->resolveValue($container->findDefinition('form.csrf_provider')->getArgument(1)));
} }

View File

@ -24,7 +24,7 @@ interface DataTransformerInterface
* This method is called on two occasions inside a form field: * This method is called on two occasions inside a form field:
* *
* 1. When the form field is initialized with the data attached from the datasource (object or array). * 1. When the form field is initialized with the data attached from the datasource (object or array).
* 2. When data from a request is bound using {@link Field::bind()} to transform the new input data * 2. When data from a request is bound using {@link Form::bind()} to transform the new input data
* back into the renderable format. For example if you have a date field and bind '2009-10-10' onto * back into the renderable format. For example if you have a date field and bind '2009-10-10' onto
* it you might accept this value because its easily parsed, but the transformer still writes back * it you might accept this value because its easily parsed, but the transformer still writes back
* "2009/10/10" onto the form field (for further displaying or other purposes). * "2009/10/10" onto the form field (for further displaying or other purposes).
@ -52,7 +52,7 @@ interface DataTransformerInterface
* Transforms a value from the transformed representation to its original * Transforms a value from the transformed representation to its original
* representation. * representation.
* *
* This method is called when {@link Field::bind()} is called to transform the requests tainted data * This method is called when {@link Form::bind()} is called to transform the requests tainted data
* into an acceptable format for your data processing/model layer. * into an acceptable format for your data processing/model layer.
* *
* This method must be able to deal with empty values. Usually this will * This method must be able to deal with empty values. Usually this will

View File

@ -1,21 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Exception;
/**
* Thrown when a field is expected to be added to a form but is not
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
class DanglingFieldException extends FormException
{
}

View File

@ -1,16 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Exception;
class FieldDefinitionException extends FormException
{
}

View File

@ -44,8 +44,8 @@ class ChoiceType extends AbstractType
} }
if ($options['expanded']) { if ($options['expanded']) {
$this->addSubFields($builder, $options['choice_list']->getPreferredViews(), $options); $this->addSubForms($builder, $options['choice_list']->getPreferredViews(), $options);
$this->addSubFields($builder, $options['choice_list']->getRemainingViews(), $options); $this->addSubForms($builder, $options['choice_list']->getRemainingViews(), $options);
} }
// empty value // empty value
@ -182,7 +182,7 @@ class ChoiceType extends AbstractType
*/ */
public function getParent(array $options) public function getParent(array $options)
{ {
return isset($options['expanded']) && $options['expanded'] ? 'form' : 'field'; return 'field';
} }
/** /**
@ -200,12 +200,12 @@ class ChoiceType extends AbstractType
* @param array $choiceViews The choice view objects. * @param array $choiceViews The choice view objects.
* @param array $options The build options. * @param array $options The build options.
*/ */
private function addSubFields(FormBuilder $builder, array $choiceViews, array $options) private function addSubForms(FormBuilder $builder, array $choiceViews, array $options)
{ {
foreach ($choiceViews as $i => $choiceView) { foreach ($choiceViews as $i => $choiceView) {
if (is_array($choiceView)) { if (is_array($choiceView)) {
// Flatten groups // Flatten groups
$this->addSubFields($builder, $choiceView, $options); $this->addSubForms($builder, $choiceView, $options);
} else { } else {
$choiceOpts = array( $choiceOpts = array(
'value' => $choiceView->getValue(), 'value' => $choiceView->getValue(),

View File

@ -153,7 +153,7 @@ class DateTimeType extends AbstractType
'widget' => null, 'widget' => null,
// This will overwrite "empty_value" child options // This will overwrite "empty_value" child options
'empty_value' => null, 'empty_value' => null,
// If initialized with a \DateTime object, FieldType initializes // If initialized with a \DateTime object, FormType initializes
// this option to "\DateTime". Since the internal, normalized // this option to "\DateTime". Since the internal, normalized
// representation is not \DateTime, but an array, we need to unset // representation is not \DateTime, but an array, we need to unset
// this option. // this option.
@ -200,7 +200,7 @@ class DateTimeType extends AbstractType
*/ */
public function getParent(array $options) public function getParent(array $options)
{ {
return isset($options['widget']) && 'single_text' === $options['widget'] ? 'field' : 'form'; return 'field';
} }
/** /**

View File

@ -177,7 +177,7 @@ class DateType extends AbstractType
// them like immutable value objects // them like immutable value objects
'by_reference' => false, 'by_reference' => false,
'error_bubbling' => false, 'error_bubbling' => false,
// If initialized with a \DateTime object, FieldType initializes // If initialized with a \DateTime object, FormType initializes
// this option to "\DateTime". Since the internal, normalized // this option to "\DateTime". Since the internal, normalized
// representation is not \DateTime, but an array, we need to unset // representation is not \DateTime, but an array, we need to unset
// this option. // this option.
@ -210,7 +210,7 @@ class DateType extends AbstractType
*/ */
public function getParent(array $options) public function getParent(array $options)
{ {
return isset($options['widget']) && 'single_text' === $options['widget'] ? 'field' : 'form'; return 'field';
} }
/** /**

View File

@ -23,179 +23,15 @@ use Symfony\Component\Form\Extension\Core\EventListener\ValidationListener;
use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Form\Exception\FormException; use Symfony\Component\Form\Exception\FormException;
/**
* Deprecated. You should extend FormType instead.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @deprecated Deprecated since version 2.1, to be removed in 2.3.
*/
class FieldType extends AbstractType class FieldType extends AbstractType
{ {
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilder $builder, array $options)
{
if (null === $options['property_path']) {
$options['property_path'] = $builder->getName();
}
if (false === $options['property_path'] || '' === $options['property_path']) {
$options['property_path'] = null;
} else {
$options['property_path'] = new PropertyPath($options['property_path']);
}
if (!is_array($options['attr'])) {
throw new FormException('The "attr" option must be "array".');
}
$builder
->setRequired($options['required'])
->setDisabled($options['disabled'])
->setErrorBubbling($options['error_bubbling'])
->setEmptyData($options['empty_data'])
->setAttribute('read_only', $options['read_only'])
->setAttribute('by_reference', $options['by_reference'])
->setAttribute('property_path', $options['property_path'])
->setAttribute('error_mapping', $options['error_mapping'])
->setAttribute('max_length', $options['max_length'])
->setAttribute('pattern', $options['pattern'])
->setAttribute('label', $options['label'] ?: $this->humanize($builder->getName()))
->setAttribute('attr', $options['attr'] ?: array())
->setAttribute('invalid_message', $options['invalid_message'])
->setAttribute('invalid_message_parameters', $options['invalid_message_parameters'])
->setAttribute('translation_domain', $options['translation_domain'])
->setData($options['data'])
->addEventSubscriber(new ValidationListener())
;
if ($options['trim']) {
$builder->addEventSubscriber(new TrimListener());
}
}
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form)
{
$name = $form->getName();
$readOnly = $form->getAttribute('read_only');
if ($view->hasParent()) {
if ('' === $name) {
throw new FormException('Form node with empty name can be used only as root form node.');
}
if ('' !== ($parentFullName = $view->getParent()->get('full_name'))) {
$id = sprintf('%s_%s', $view->getParent()->get('id'), $name);
$fullName = sprintf('%s[%s]', $parentFullName, $name);
} else {
$id = $name;
$fullName = $name;
}
// Complex fields are read-only if themselves or their parent is.
$readOnly = $readOnly || $view->getParent()->get('read_only');
} else {
$id = $name;
$fullName = $name;
// Strip leading underscores and digits. These are allowed in
// form names, but not in HTML4 ID attributes.
// http://www.w3.org/TR/html401/struct/global.html#adef-id
$id = ltrim($id, '_0123456789');
}
$types = array();
foreach ($form->getTypes() as $type) {
$types[] = $type->getName();
}
$view
->set('form', $view)
->set('id', $id)
->set('name', $name)
->set('full_name', $fullName)
->set('read_only', $readOnly)
->set('errors', $form->getErrors())
->set('value', $form->getClientData())
->set('disabled', $form->isDisabled())
->set('required', $form->isRequired())
->set('max_length', $form->getAttribute('max_length'))
->set('pattern', $form->getAttribute('pattern'))
->set('size', null)
->set('label', $form->getAttribute('label'))
->set('multipart', false)
->set('attr', $form->getAttribute('attr'))
->set('types', $types)
->set('translation_domain', $form->getAttribute('translation_domain'))
;
}
/**
* {@inheritdoc}
*/
public function getDefaultOptions()
{
// Derive "data_class" option from passed "data" object
$dataClass = function (Options $options) {
if (is_object($options['data'])) {
return get_class($options['data']);
}
return null;
};
// Derive "empty_data" closure from "data_class" option
$emptyData = function (Options $options) {
$class = $options['data_class'];
if (null !== $class) {
return function (FormInterface $form) use ($class) {
if ($form->isEmpty() && !$form->isRequired()) {
return null;
}
return new $class();
};
}
return '';
};
return array(
'data' => null,
'data_class' => $dataClass,
'empty_data' => $emptyData,
'trim' => true,
'required' => true,
'read_only' => false,
'disabled' => false,
'max_length' => null,
'pattern' => null,
'property_path' => null,
'by_reference' => true,
'error_bubbling' => false,
'error_mapping' => array(),
'label' => null,
'attr' => array(),
'invalid_message' => 'This value is not valid',
'invalid_message_parameters' => array(),
'translation_domain' => 'messages',
);
}
/**
* {@inheritdoc}
*/
public function createBuilder($name, FormFactoryInterface $factory, array $options)
{
return new FormBuilder($name, $factory, new EventDispatcher(), $options['data_class']);
}
/**
* {@inheritdoc}
*/
public function getParent(array $options)
{
return null;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -203,9 +39,4 @@ class FieldType extends AbstractType
{ {
return 'field'; return 'field';
} }
private function humanize($text)
{
return ucfirst(trim(strtolower(preg_replace('/[_\s]+/', ' ', $text))));
}
} }

View File

@ -23,12 +23,24 @@ class FileType extends AbstractType
public function buildView(FormView $view, FormInterface $form) public function buildView(FormView $view, FormInterface $form)
{ {
$view $view
->set('multipart', true)
->set('type', 'file') ->set('type', 'file')
->set('value', '') ->set('value', '')
; ;
} }
/**
* {@inheritdoc}
*/
public function buildViewBottomUp(FormView $view, FormInterface $form)
{
$view
->set('multipart', true)
;
}
/**
* {@inheritdoc}
*/
public function getParent(array $options) public function getParent(array $options)
{ {
return 'field'; return 'field';

View File

@ -13,10 +13,16 @@ namespace Symfony\Component\Form\Extension\Core\Type;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Options; use Symfony\Component\Form\Options;
use Symfony\Component\Form\Util\PropertyPath;
use Symfony\Component\Form\FormBuilder; use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Extension\Core\EventListener\TrimListener;
use Symfony\Component\Form\Extension\Core\EventListener\ValidationListener;
use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper; use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Form\Exception\FormException;
class FormType extends AbstractType class FormType extends AbstractType
{ {
@ -25,9 +31,102 @@ class FormType extends AbstractType
*/ */
public function buildForm(FormBuilder $builder, array $options) public function buildForm(FormBuilder $builder, array $options)
{ {
if (null === $options['property_path']) {
$options['property_path'] = $builder->getName();
}
if (false === $options['property_path'] || '' === $options['property_path']) {
$options['property_path'] = null;
} else {
$options['property_path'] = new PropertyPath($options['property_path']);
}
if (!is_array($options['attr'])) {
throw new FormException('The "attr" option must be "array".');
}
$builder $builder
->setRequired($options['required'])
->setDisabled($options['disabled'])
->setErrorBubbling($options['error_bubbling'])
->setEmptyData($options['empty_data'])
->setAttribute('read_only', $options['read_only'])
->setAttribute('by_reference', $options['by_reference'])
->setAttribute('property_path', $options['property_path'])
->setAttribute('error_mapping', $options['error_mapping'])
->setAttribute('max_length', $options['max_length'])
->setAttribute('pattern', $options['pattern'])
->setAttribute('label', $options['label'] ?: $this->humanize($builder->getName()))
->setAttribute('attr', $options['attr'] ?: array())
->setAttribute('invalid_message', $options['invalid_message'])
->setAttribute('invalid_message_parameters', $options['invalid_message_parameters'])
->setAttribute('translation_domain', $options['translation_domain'])
->setAttribute('virtual', $options['virtual']) ->setAttribute('virtual', $options['virtual'])
->setData($options['data'])
->setDataMapper(new PropertyPathMapper($options['data_class'])) ->setDataMapper(new PropertyPathMapper($options['data_class']))
->addEventSubscriber(new ValidationListener())
;
if ($options['trim']) {
$builder->addEventSubscriber(new TrimListener());
}
}
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form)
{
$name = $form->getName();
$readOnly = $form->getAttribute('read_only');
if ($view->hasParent()) {
if ('' === $name) {
throw new FormException('Form node with empty name can be used only as root form node.');
}
if ('' !== ($parentFullName = $view->getParent()->get('full_name'))) {
$id = sprintf('%s_%s', $view->getParent()->get('id'), $name);
$fullName = sprintf('%s[%s]', $parentFullName, $name);
} else {
$id = $name;
$fullName = $name;
}
// Complex fields are read-only if themselves or their parent is.
$readOnly = $readOnly || $view->getParent()->get('read_only');
} else {
$id = $name;
$fullName = $name;
// Strip leading underscores and digits. These are allowed in
// form names, but not in HTML4 ID attributes.
// http://www.w3.org/TR/html401/struct/global.html#adef-id
$id = ltrim($id, '_0123456789');
}
$types = array();
foreach ($form->getTypes() as $type) {
$types[] = $type->getName();
}
$view
->set('form', $view)
->set('id', $id)
->set('name', $name)
->set('full_name', $fullName)
->set('read_only', $readOnly)
->set('errors', $form->getErrors())
->set('value', $form->getClientData())
->set('disabled', $form->isDisabled())
->set('required', $form->isRequired())
->set('max_length', $form->getAttribute('max_length'))
->set('pattern', $form->getAttribute('pattern'))
->set('size', null)
->set('label', $form->getAttribute('label'))
->set('multipart', false)
->set('attr', $form->getAttribute('attr'))
->set('types', $types)
->set('translation_domain', $form->getAttribute('translation_domain'))
; ;
} }
@ -53,29 +152,75 @@ class FormType extends AbstractType
*/ */
public function getDefaultOptions() public function getDefaultOptions()
{ {
$emptyData = function (Options $options, $currentValue) { // Derive "data_class" option from passed "data" object
if (empty($options['data_class'])) { $dataClass = function (Options $options) {
return array(); if (is_object($options['data'])) {
return get_class($options['data']);
} }
return $currentValue; return null;
}; };
// Derive "empty_data" closure from "data_class" option
$emptyData = function (Options $options) {
$class = $options['data_class'];
if (null !== $class) {
return function (FormInterface $form) use ($class) {
if ($form->isEmpty() && !$form->isRequired()) {
return null;
}
return new $class();
};
}
return function (FormInterface $form) {
if ($form->hasChildren()) {
return array();
}
return '';
};
};
return array( return array(
'data' => null,
'data_class' => $dataClass,
'empty_data' => $emptyData, 'empty_data' => $emptyData,
'trim' => true,
'required' => true,
'read_only' => false,
'disabled' => false,
'max_length' => null,
'pattern' => null,
'property_path' => null,
'by_reference' => true,
'error_bubbling' => false,
'error_mapping' => array(),
'label' => null,
'attr' => array(),
'virtual' => false, 'virtual' => false,
// Errors in forms bubble by default, so that form errors will 'invalid_message' => 'This value is not valid',
// end up as global errors in the root form 'invalid_message_parameters' => array(),
'error_bubbling' => true, 'translation_domain' => 'messages',
); );
} }
/**
* {@inheritdoc}
*/
public function createBuilder($name, FormFactoryInterface $factory, array $options)
{
return new FormBuilder($name, $factory, new EventDispatcher(), $options['data_class']);
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getParent(array $options) public function getParent(array $options)
{ {
return 'field'; return null;
} }
/** /**
@ -85,4 +230,9 @@ class FormType extends AbstractType
{ {
return 'form'; return 'form';
} }
private function humanize($text)
{
return ucfirst(trim(strtolower(preg_replace('/[_\s]+/', ' ', $text))));
}
} }

View File

@ -150,7 +150,7 @@ class TimeType extends AbstractType
// them like immutable value objects // them like immutable value objects
'by_reference' => false, 'by_reference' => false,
'error_bubbling' => false, 'error_bubbling' => false,
// If initialized with a \DateTime object, FieldType initializes // If initialized with a \DateTime object, FormType initializes
// this option to "\DateTime". Since the internal, normalized // this option to "\DateTime". Since the internal, normalized
// representation is not \DateTime, but an array, we need to unset // representation is not \DateTime, but an array, we need to unset
// this option. // this option.
@ -183,7 +183,7 @@ class TimeType extends AbstractType
*/ */
public function getParent(array $options) public function getParent(array $options)
{ {
return isset($options['widget']) && 'single_text' === $options['widget'] ? 'field' : 'form'; return 'field';
} }
/** /**

View File

@ -32,27 +32,13 @@ class CsrfExtension extends AbstractExtension
$this->csrfProvider = $csrfProvider; $this->csrfProvider = $csrfProvider;
} }
/**
* {@inheritDoc}
*/
protected function loadTypes()
{
return array(
new Type\CsrfType($this->csrfProvider),
);
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
protected function loadTypeExtensions() protected function loadTypeExtensions()
{ {
return array( return array(
new Type\ChoiceTypeCsrfExtension(), new Type\FormTypeCsrfExtension($this->csrfProvider),
new Type\DateTypeCsrfExtension(),
new Type\FormTypeCsrfExtension(),
new Type\RepeatedTypeCsrfExtension(),
new Type\TimeTypeCsrfExtension(),
); );
} }
} }

View File

@ -14,7 +14,7 @@ namespace Symfony\Component\Form\Extension\Csrf\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormError;
use Symfony\Component\Form\Event\DataEvent; use Symfony\Component\Form\Event\FilterDataEvent;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
/** /**
@ -22,6 +22,12 @@ use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
*/ */
class CsrfValidationListener implements EventSubscriberInterface class CsrfValidationListener implements EventSubscriberInterface
{ {
/**
* The name of the CSRF field
* @var string
*/
private $fieldName;
/** /**
* The provider for generating and validating CSRF tokens * The provider for generating and validating CSRF tokens
* @var CsrfProviderInterface * @var CsrfProviderInterface
@ -45,24 +51,26 @@ class CsrfValidationListener implements EventSubscriberInterface
); );
} }
public function __construct(CsrfProviderInterface $csrfProvider, $intention) public function __construct($fieldName, CsrfProviderInterface $csrfProvider, $intention)
{ {
$this->fieldName = $fieldName;
$this->csrfProvider = $csrfProvider; $this->csrfProvider = $csrfProvider;
$this->intention = $intention; $this->intention = $intention;
} }
public function onBindClientData(DataEvent $event) public function onBindClientData(FilterDataEvent $event)
{ {
$form = $event->getForm(); $form = $event->getForm();
$data = $event->getData(); $data = $event->getData();
if ((!$form->hasParent() || $form->getParent()->isRoot()) if ($form->isRoot() && $form->hasChildren() && isset($data[$this->fieldName])) {
&& !$this->csrfProvider->isCsrfTokenValid($this->intention, $data)) { if (!$this->csrfProvider->isCsrfTokenValid($this->intention, $data[$this->fieldName])) {
$form->addError(new FormError('The CSRF token is invalid. Please try to resubmit the form')); $form->addError(new FormError('The CSRF token is invalid. Please try to resubmit the form'));
}
// If the session timed out, the token is invalid now. unset($data[$this->fieldName]);
// Regenerate the token so that a resubmission is possible.
$event->setData($this->csrfProvider->generateCsrfToken($this->intention));
} }
$event->setData($data);
} }
} }

View File

@ -1,66 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\Csrf\EventListener;
use Symfony\Component\Form\Event\DataEvent;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
use Symfony\Component\Form\FormFactoryInterface;
/**
* Ensures the CSRF field.
*
* @author Bulat Shakirzyanov <mallluhuct@gmail.com>
* @author Kris Wallsmith <kris@symfony.com>
*/
class EnsureCsrfFieldListener
{
private $factory;
private $name;
private $intention;
private $provider;
/**
* Constructor.
*
* @param FormFactoryInterface $factory The form factory
* @param string $name A name for the CSRF field
* @param string $intention The intention string
* @param CsrfProviderInterface $provider The CSRF provider
*/
public function __construct(FormFactoryInterface $factory, $name, $intention = null, CsrfProviderInterface $provider = null)
{
$this->factory = $factory;
$this->name = $name;
$this->intention = $intention;
$this->provider = $provider;
}
/**
* Ensures a root form has a CSRF field.
*
* This method should be connected to both form.pre_set_data and form.pre_bind.
*/
public function ensureCsrfField(DataEvent $event)
{
$form = $event->getForm();
$options = array();
if ($this->intention) {
$options['intention'] = $this->intention;
}
if ($this->provider) {
$options['csrf_provider'] = $this->provider;
}
$form->add($this->factory->createNamed('csrf', $this->name, null, $options));
}
}

View File

@ -1,27 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\Csrf\Type;
use Symfony\Component\Form\AbstractTypeExtension;
class ChoiceTypeCsrfExtension extends AbstractTypeExtension
{
public function getDefaultOptions()
{
return array('csrf_protection' => false);
}
public function getExtendedType()
{
return 'choice';
}
}

View File

@ -1,83 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\Csrf\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\Extension\Csrf\EventListener\CsrfValidationListener;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
class CsrfType extends AbstractType
{
private $csrfProvider;
/**
* Constructor.
*
* @param CsrfProviderInterface $csrfProvider The provider to use to generate the token
*/
public function __construct(CsrfProviderInterface $csrfProvider)
{
$this->csrfProvider = $csrfProvider;
}
/**
* Builds the CSRF field.
*
* A validator is added to check the token value when the CSRF field is added to
* a root form
*
* @param FormBuilder $builder The form builder
* @param array $options The options
*/
public function buildForm(FormBuilder $builder, array $options)
{
$csrfProvider = $options['csrf_provider'];
$intention = $options['intention'];
$builder
->setData($csrfProvider->generateCsrfToken($intention))
->addEventSubscriber(new CsrfValidationListener($csrfProvider, $intention))
;
}
/**
* {@inheritDoc}
*/
public function getDefaultOptions()
{
return array(
'csrf_provider' => $this->csrfProvider,
'intention' => null,
'property_path' => false,
);
}
/**
* {@inheritDoc}
*/
public function getParent(array $options)
{
return 'hidden';
}
/**
* Returns the name of this form.
*
* @return string 'csrf'
*/
public function getName()
{
return 'csrf';
}
}

View File

@ -1,27 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\Csrf\Type;
use Symfony\Component\Form\AbstractTypeExtension;
class DateTypeCsrfExtension extends AbstractTypeExtension
{
public function getDefaultOptions()
{
return array('csrf_protection' => false);
}
public function getExtendedType()
{
return 'date';
}
}

View File

@ -12,21 +12,27 @@
namespace Symfony\Component\Form\Extension\Csrf\Type; namespace Symfony\Component\Form\Extension\Csrf\Type;
use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Csrf\EventListener\EnsureCsrfFieldListener; use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
use Symfony\Component\Form\Extension\Csrf\EventListener\CsrfValidationListener;
use Symfony\Component\Form\FormBuilder; use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormInterface;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class FormTypeCsrfExtension extends AbstractTypeExtension class FormTypeCsrfExtension extends AbstractTypeExtension
{ {
private $enabled; private $defaultCsrfProvider;
private $fieldName; private $defaultEnabled;
private $defaultFieldName;
public function __construct($enabled = true, $fieldName = '_token') public function __construct(CsrfProviderInterface $defaultCsrfProvider, $defaultEnabled = true, $defaultFieldName = '_token')
{ {
$this->enabled = $enabled; $this->defaultCsrfProvider = $defaultCsrfProvider;
$this->fieldName = $fieldName; $this->defaultEnabled = $defaultEnabled;
$this->defaultFieldName = $defaultFieldName;
} }
/** /**
@ -41,35 +47,35 @@ class FormTypeCsrfExtension extends AbstractTypeExtension
return; return;
} }
$listener = new EnsureCsrfFieldListener(
$builder->getFormFactory(),
$options['csrf_field_name'],
$options['intention'],
$options['csrf_provider']
);
// use a low priority so higher priority listeners don't remove the field // use a low priority so higher priority listeners don't remove the field
$builder $builder
->setAttribute('csrf_field_name', $options['csrf_field_name']) ->setAttribute('csrf_field_name', $options['csrf_field_name'])
->addEventListener(FormEvents::PRE_SET_DATA, array($listener, 'ensureCsrfField'), -10) ->setAttribute('csrf_provider', $options['csrf_provider'])
->addEventListener(FormEvents::PRE_BIND, array($listener, 'ensureCsrfField'), -10) ->setAttribute('csrf_intention', $options['intention'])
->setAttribute('csrf_factory', $builder->getFormFactory())
->addEventSubscriber(new CsrfValidationListener($options['csrf_field_name'], $options['csrf_provider'], $options['intention']))
; ;
} }
/** /**
* Removes CSRF fields from all the form views except the root one. * Adds a CSRF field to the root form view.
* *
* @param FormView $view The form view * @param FormView $view The form view
* @param FormInterface $form The form * @param FormInterface $form The form
*/ */
public function buildViewBottomUp(FormView $view, FormInterface $form) public function buildView(FormView $view, FormInterface $form)
{ {
if ($view->hasParent() && $form->hasAttribute('csrf_field_name')) { if ($form->isRoot() && $form->hasChildren() && $form->hasAttribute('csrf_field_name')) {
$name = $form->getAttribute('csrf_field_name'); $name = $form->getAttribute('csrf_field_name');
$csrfProvider = $form->getAttribute('csrf_provider');
$intention = $form->getAttribute('csrf_intention');
$factory = $form->getAttribute('csrf_factory');
$data = $csrfProvider->generateCsrfToken($intention);
$csrfForm = $factory->createNamed('hidden', $name, $data, array(
'property_path' => false,
));
if (isset($view[$name])) { $view->addChild($csrfForm->createView($view));
unset($view[$name]);
}
} }
} }
@ -79,9 +85,9 @@ class FormTypeCsrfExtension extends AbstractTypeExtension
public function getDefaultOptions() public function getDefaultOptions()
{ {
return array( return array(
'csrf_protection' => $this->enabled, 'csrf_protection' => $this->defaultEnabled,
'csrf_field_name' => $this->fieldName, 'csrf_field_name' => $this->defaultFieldName,
'csrf_provider' => null, 'csrf_provider' => $this->defaultCsrfProvider,
'intention' => 'unknown', 'intention' => 'unknown',
); );
} }

View File

@ -1,27 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\Csrf\Type;
use Symfony\Component\Form\AbstractTypeExtension;
class RepeatedTypeCsrfExtension extends AbstractTypeExtension
{
public function getDefaultOptions()
{
return array('csrf_protection' => false);
}
public function getExtendedType()
{
return 'repeated';
}
}

View File

@ -1,27 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Extension\Csrf\Type;
use Symfony\Component\Form\AbstractTypeExtension;
class TimeTypeCsrfExtension extends AbstractTypeExtension
{
public function getDefaultOptions()
{
return array('csrf_protection' => false);
}
public function getExtendedType()
{
return 'time';
}
}

View File

@ -19,7 +19,7 @@ use Symfony\Component\Validator\ValidatorInterface;
/** /**
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
*/ */
class FieldTypeValidatorExtension extends AbstractTypeExtension class FormTypeValidatorExtension extends AbstractTypeExtension
{ {
private $validator; private $validator;
@ -57,6 +57,6 @@ class FieldTypeValidatorExtension extends AbstractTypeExtension
public function getExtendedType() public function getExtendedType()
{ {
return 'field'; return 'form';
} }
} }

View File

@ -38,7 +38,7 @@ class ValidatorExtension extends AbstractExtension
protected function loadTypeExtensions() protected function loadTypeExtensions()
{ {
return array( return array(
new Type\FieldTypeValidatorExtension($this->validator), new Type\FormTypeValidatorExtension($this->validator),
); );
} }
} }

View File

@ -191,7 +191,7 @@ class Form implements \IteratorAggregate, FormInterface
array $types = array(), array $clientTransformers = array(), array $types = array(), array $clientTransformers = array(),
array $normTransformers = array(), array $normTransformers = array(),
DataMapperInterface $dataMapper = null, array $validators = array(), DataMapperInterface $dataMapper = null, array $validators = array(),
$required = false, $disabled = false, $errorBubbling = false, $required = false, $disabled = false, $errorBubbling = null,
$emptyData = null, array $attributes = array()) $emptyData = null, array $attributes = array())
{ {
$name = (string) $name; $name = (string) $name;
@ -225,7 +225,10 @@ class Form implements \IteratorAggregate, FormInterface
$this->validators = $validators; $this->validators = $validators;
$this->required = (Boolean) $required; $this->required = (Boolean) $required;
$this->disabled = (Boolean) $disabled; $this->disabled = (Boolean) $disabled;
$this->errorBubbling = (Boolean) $errorBubbling; // NULL is the default meaning:
// bubble up if the form has children (complex forms)
// don't bubble up if the form has no children (primitive fields)
$this->errorBubbling = null === $errorBubbling ? null : (Boolean) $errorBubbling;
$this->emptyData = $emptyData; $this->emptyData = $emptyData;
$this->attributes = $attributes; $this->attributes = $attributes;
@ -312,9 +315,9 @@ class Form implements \IteratorAggregate, FormInterface
} }
/** /**
* Returns the parent field. * Returns the parent form.
* *
* @return FormInterface The parent field * @return FormInterface The parent form
*/ */
public function getParent() public function getParent()
{ {
@ -342,7 +345,7 @@ class Form implements \IteratorAggregate, FormInterface
} }
/** /**
* Returns whether the field is the root of the form tree. * Returns whether the form is the root of the form tree.
* *
* @return Boolean * @return Boolean
*/ */
@ -374,7 +377,7 @@ class Form implements \IteratorAggregate, FormInterface
} }
/** /**
* Updates the field with default data. * Updates the form with default data.
* *
* @param array $appData The data formatted as expected for the underlying object * @param array $appData The data formatted as expected for the underlying object
* *
@ -408,7 +411,7 @@ class Form implements \IteratorAggregate, FormInterface
$this->clientData = $clientData; $this->clientData = $clientData;
$this->synchronized = true; $this->synchronized = true;
if ($this->dataMapper) { if (count($this->children) > 0 && $this->dataMapper) {
// Update child forms from the data // Update child forms from the data
$this->dataMapper->mapDataToForms($clientData, $this->children); $this->dataMapper->mapDataToForms($clientData, $this->children);
} }
@ -450,7 +453,7 @@ class Form implements \IteratorAggregate, FormInterface
} }
/** /**
* Binds data to the field, transforms and validates it. * Binds data to the form, transforms and validates it.
* *
* @param string|array $clientData The data * @param string|array $clientData The data
* *
@ -626,11 +629,11 @@ class Form implements \IteratorAggregate, FormInterface
} }
/** /**
* Returns the normalized data of the field. * Returns the normalized data of the form.
* *
* @return mixed When the field is not bound, the default data is returned. * @return mixed When the form is not bound, the default data is returned.
* When the field is bound, the normalized bound data is * When the form is bound, the normalized bound data is
* returned if the field is valid, null otherwise. * returned if the form is valid, null otherwise.
*/ */
public function getNormData() public function getNormData()
{ {
@ -646,7 +649,7 @@ class Form implements \IteratorAggregate, FormInterface
*/ */
public function addError(FormError $error) public function addError(FormError $error)
{ {
if ($this->parent && $this->errorBubbling) { if ($this->parent && $this->getErrorBubbling()) {
$this->parent->addError($error); $this->parent->addError($error);
} else { } else {
$this->errors[] = $error; $this->errors[] = $error;
@ -662,11 +665,11 @@ class Form implements \IteratorAggregate, FormInterface
*/ */
public function getErrorBubbling() public function getErrorBubbling()
{ {
return $this->errorBubbling; return null === $this->errorBubbling ? $this->hasChildren() : $this->errorBubbling;
} }
/** /**
* Returns whether the field is bound. * Returns whether the form is bound.
* *
* @return Boolean true if the form is bound to input values, false otherwise * @return Boolean true if the form is bound to input values, false otherwise
*/ */
@ -702,7 +705,7 @@ class Form implements \IteratorAggregate, FormInterface
} }
/** /**
* Returns whether the field is valid. * Returns whether the form is valid.
* *
* @return Boolean * @return Boolean
*/ */
@ -735,9 +738,8 @@ class Form implements \IteratorAggregate, FormInterface
public function hasErrors() public function hasErrors()
{ {
// Don't call isValid() here, as its semantics are slightly different // Don't call isValid() here, as its semantics are slightly different
// Field groups are not valid if their children are invalid, but // Forms are not valid if their children are invalid, but
// hasErrors() returns only true if a field/field group itself has // hasErrors() returns only true if a form itself has errors
// errors
return count($this->errors) > 0; return count($this->errors) > 0;
} }
@ -894,7 +896,7 @@ class Form implements \IteratorAggregate, FormInterface
return $this->children[$name]; return $this->children[$name];
} }
throw new \InvalidArgumentException(sprintf('Field "%s" does not exist.', $name)); throw new \InvalidArgumentException(sprintf('Child "%s" does not exist.', $name));
} }
/** /**
@ -975,7 +977,7 @@ class Form implements \IteratorAggregate, FormInterface
$parent = $this->parent->createView(); $parent = $this->parent->createView();
} }
$view = new FormView(); $view = new FormView($this->name);
$view->setParent($parent); $view->setParent($parent);
@ -989,14 +991,10 @@ class Form implements \IteratorAggregate, FormInterface
} }
} }
$childViews = array(); foreach ($this->children as $child) {
$view->addChild($child->createView($view));
foreach ($this->children as $key => $child) {
$childViews[$key] = $child->createView($view);
} }
$view->setChildren($childViews);
foreach ($types as $type) { foreach ($types as $type) {
$type->buildViewBottomUp($view, $this); $type->buildViewBottomUp($view, $this);

View File

@ -106,7 +106,7 @@ class FormBuilder
* Whether added errors should bubble up to the parent * Whether added errors should bubble up to the parent
* @var Boolean * @var Boolean
*/ */
private $errorBubbling = false; private $errorBubbling;
/** /**
* Data used for the client data when no value is bound * Data used for the client data when no value is bound
@ -243,7 +243,7 @@ class FormBuilder
*/ */
public function setErrorBubbling($errorBubbling) public function setErrorBubbling($errorBubbling)
{ {
$this->errorBubbling = (Boolean) $errorBubbling; $this->errorBubbling = null === $errorBubbling ? null : (Boolean) $errorBubbling;
return $this; return $this;
} }

View File

@ -183,7 +183,7 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable
* The content of a disabled form is displayed, but not allowed to be * The content of a disabled form is displayed, but not allowed to be
* modified. The validation of modified disabled forms should fail. * modified. The validation of modified disabled forms should fail.
* *
* Fields whose parents are disabled are considered disabled regardless of * Forms whose parents are disabled are considered disabled regardless of
* their own state. * their own state.
* *
* @return Boolean * @return Boolean

View File

@ -75,7 +75,7 @@ class FormTypeGuesserChain implements FormTypeGuesserInterface
* @param \Closure $closure The closure to execute. Accepts a guesser * @param \Closure $closure The closure to execute. Accepts a guesser
* as argument and should return a Guess instance * as argument and should return a Guess instance
* *
* @return FieldFactoryGuess The guess with the highest confidence * @return Guess The guess with the highest confidence
*/ */
private function guess(\Closure $closure) private function guess(\Closure $closure)
{ {

View File

@ -11,8 +11,17 @@
namespace Symfony\Component\Form; namespace Symfony\Component\Form;
class FormView implements \ArrayAccess, \IteratorAggregate, \Countable use ArrayAccess;
use IteratorAggregate;
use Countable;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class FormView implements ArrayAccess, IteratorAggregate, Countable
{ {
private $name;
private $vars = array( private $vars = array(
'value' => null, 'value' => null,
'attr' => array(), 'attr' => array(),
@ -33,6 +42,16 @@ class FormView implements \ArrayAccess, \IteratorAggregate, \Countable
*/ */
private $rendered = false; private $rendered = false;
public function __construct($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
/** /**
* @param string $name * @param string $name
* @param mixed $value * @param mixed $value
@ -177,15 +196,29 @@ class FormView implements \ArrayAccess, \IteratorAggregate, \Countable
} }
/** /**
* Sets the children view. * Adds a child view.
* *
* @param array $children The children as instances of FormView * @param FormView $child The child view to add.
* *
* @return FormView The current view * @return FormView The current view
*/ */
public function setChildren(array $children) public function addChild(FormView $child)
{ {
$this->children = $children; $this->children[$child->getName()] = $child;
return $this;
}
/**
* Removes a child view.
*
* @param string $name The name of the removed child view.
*
* @return FormView The current view
*/
public function removeChild($name)
{
unset($this->children[$name]);
return $this; return $this;
} }
@ -222,6 +255,18 @@ class FormView implements \ArrayAccess, \IteratorAggregate, \Countable
return count($this->children) > 0; return count($this->children) > 0;
} }
/**
* Returns whether this view has a given child.
*
* @param string $name The name of the child
*
* @return Boolean Whether the child with the given name exists
*/
public function hasChild($name)
{
return isset($this->children[$name]);
}
/** /**
* Returns a child by name (implements \ArrayAccess). * Returns a child by name (implements \ArrayAccess).
* *

View File

@ -202,7 +202,7 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest
); );
} }
public function testRestAndRepeatedWithRowPerField() public function testRestAndRepeatedWithRowPerChild()
{ {
$view = $this->factory->createNamedBuilder('form', 'name') $view = $this->factory->createNamedBuilder('form', 'name')
->add('first', 'text') ->add('first', 'text')
@ -230,7 +230,7 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest
); );
} }
public function testRestAndRepeatedWithWidgetPerField() public function testRestAndRepeatedWithWidgetPerChild()
{ {
$view = $this->factory->createNamedBuilder('form', 'name') $view = $this->factory->createNamedBuilder('form', 'name')
->add('first', 'text') ->add('first', 'text')
@ -348,7 +348,10 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest
public function testNestedFormError() public function testNestedFormError()
{ {
$form = $this->factory->createNamedBuilder('form', 'name') $form = $this->factory->createNamedBuilder('form', 'name')
->add('child', 'form', array('error_bubbling' => false)) ->add($this->factory
->createNamedBuilder('form', 'child', null, array('error_bubbling' => false))
->add('grandChild', 'form')
)
->getForm(); ->getForm();
$form->get('child')->addError(new FormError('Error!')); $form->get('child')->addError(new FormError('Error!'));
@ -363,6 +366,31 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest
); );
} }
public function testCsrf()
{
$this->csrfProvider->expects($this->any())
->method('generateCsrfToken')
->will($this->returnValue('foo&bar'));
$form = $this->factory->createNamedBuilder('form', 'name')
->add($this->factory
// No CSRF protection on nested forms
->createNamedBuilder('form', 'child')
->add($this->factory->createNamedBuilder('text', 'grandchild'))
)
->getForm();
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./input[@type="hidden"][@id="name__token"][@value="foo&bar"]
/following-sibling::div
]
[count(.//input[@type="hidden"])=1]
'
);
}
public function testRepeated() public function testRepeated()
{ {
$form = $this->factory->createNamed('repeated', 'name', 'foobar', array( $form = $this->factory->createNamed('repeated', 'name', 'foobar', array(
@ -372,7 +400,8 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest
$this->assertWidgetMatchesXpath($form->createView(), array(), $this->assertWidgetMatchesXpath($form->createView(), array(),
'/div '/div
[ [
./div ./input[@type="hidden"][@id="name__token"]
/following-sibling::div
[ [
./label[@for="name_first"] ./label[@for="name_first"]
/following-sibling::input[@type="text"][@id="name_first"] /following-sibling::input[@type="text"][@id="name_first"]
@ -383,7 +412,7 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest
/following-sibling::input[@type="text"][@id="name_second"] /following-sibling::input[@type="text"][@id="name_second"]
] ]
] ]
[count(.//input)=2] [count(.//input)=3]
' '
); );
} }
@ -399,7 +428,8 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest
$this->assertWidgetMatchesXpath($form->createView(), array(), $this->assertWidgetMatchesXpath($form->createView(), array(),
'/div '/div
[ [
./div ./input[@type="hidden"][@id="name__token"]
/following-sibling::div
[ [
./label[@for="name_first"][.="[trans]Test[/trans]"] ./label[@for="name_first"][.="[trans]Test[/trans]"]
/following-sibling::input[@type="text"][@id="name_first"][@required="required"] /following-sibling::input[@type="text"][@id="name_first"][@required="required"]
@ -410,7 +440,7 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest
/following-sibling::input[@type="text"][@id="name_second"][@required="required"] /following-sibling::input[@type="text"][@id="name_second"][@required="required"]
] ]
] ]
[count(.//input)=2] [count(.//input)=3]
' '
); );
} }

View File

@ -646,12 +646,13 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
$this->assertWidgetMatchesXpath($form->createView(), array(), $this->assertWidgetMatchesXpath($form->createView(), array(),
'/div '/div
[ [
./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] ./input[@type="hidden"][@id="name__token"]
/following-sibling::input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked]
/following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"] /following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"]
/following-sibling::input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)] /following-sibling::input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)]
/following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"] /following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"]
] ]
[count(./input)=2] [count(./input)=3]
' '
); );
} }
@ -668,12 +669,13 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
$this->assertWidgetMatchesXpath($form->createView(), array(), $this->assertWidgetMatchesXpath($form->createView(), array(),
'/div '/div
[ [
./input[@type="radio"][@name="name"][@id="name_0"][@checked] ./input[@type="hidden"][@id="name__token"]
/following-sibling::input[@type="radio"][@name="name"][@id="name_0"][@checked]
/following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"] /following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"]
/following-sibling::input[@type="radio"][@name="name"][@id="name_1"][not(@checked)] /following-sibling::input[@type="radio"][@name="name"][@id="name_1"][not(@checked)]
/following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"] /following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"]
] ]
[count(./input)=2] [count(./input)=3]
' '
); );
} }
@ -689,12 +691,13 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
$this->assertWidgetMatchesXpath($form->createView(), array(), $this->assertWidgetMatchesXpath($form->createView(), array(),
'/div '/div
[ [
./input[@type="radio"][@name="name"][@id="name_0"][@checked] ./input[@type="hidden"][@id="name__token"]
/following-sibling::input[@type="radio"][@name="name"][@id="name_0"][@checked]
/following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"] /following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"]
/following-sibling::input[@type="radio"][@name="name"][@id="name_1"][not(@checked)] /following-sibling::input[@type="radio"][@name="name"][@id="name_1"][not(@checked)]
/following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"] /following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"]
] ]
[count(./input)=2] [count(./input)=3]
' '
); );
} }
@ -711,14 +714,15 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
$this->assertWidgetMatchesXpath($form->createView(), array(), $this->assertWidgetMatchesXpath($form->createView(), array(),
'/div '/div
[ [
./input[@type="checkbox"][@name="name[]"][@id="name_0"][@checked][not(@required)] ./input[@type="hidden"][@id="name__token"]
/following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_0"][@checked][not(@required)]
/following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"] /following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"]
/following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_1"][not(@checked)][not(@required)] /following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_1"][not(@checked)][not(@required)]
/following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"] /following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"]
/following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_2"][@checked][not(@required)] /following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_2"][@checked][not(@required)]
/following-sibling::label[@for="name_2"][.="[trans]Choice&C[/trans]"] /following-sibling::label[@for="name_2"][.="[trans]Choice&C[/trans]"]
] ]
[count(./input)=3] [count(./input)=4]
' '
); );
} }
@ -753,22 +757,6 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
); );
} }
public function testCsrf()
{
$this->csrfProvider->expects($this->any())
->method('generateCsrfToken')
->will($this->returnValue('foo&bar'));
$form = $this->factory->createNamed('csrf', 'name');
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
[@type="hidden"]
[@value="foo&bar"]
'
);
}
public function testDateTime() public function testDateTime()
{ {
$form = $this->factory->createNamed('datetime', 'name', '2011-02-03 04:05:06', array( $form = $this->factory->createNamed('datetime', 'name', '2011-02-03 04:05:06', array(

View File

@ -45,36 +45,10 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
$html = $this->renderRow($form->createView()); $html = $this->renderRow($form->createView());
$this->assertMatchesXpath($html, $this->assertMatchesXpath($html,
'/tr '/tr[@style="display: none"]
[ [./td[@colspan="2"]/input
./td [@type="hidden"]
[./label[@for="name_first"]] [@id="name__token"]
/following-sibling::td
[./input[@id="name_first"]]
]
/following-sibling::tr
[
./td
[./label[@for="name_second"]]
/following-sibling::td
[./input[@id="name_second"]]
]
[count(../tr)=2]
'
);
}
public function testRepeatedRowWithErrors()
{
$form = $this->factory->createNamed('repeated', 'name');
$form->addError(new FormError('Error!'));
$view = $form->createView();
$html = $this->renderRow($view);
$this->assertMatchesXpath($html,
'/tr
[./td[@colspan="2"]/ul
[./li[.="[trans]Error![/trans]"]]
] ]
/following-sibling::tr /following-sibling::tr
[ [
@ -95,6 +69,42 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
); );
} }
public function testRepeatedRowWithErrors()
{
$form = $this->factory->createNamed('repeated', 'name');
$form->addError(new FormError('Error!'));
$view = $form->createView();
$html = $this->renderRow($view);
$this->assertMatchesXpath($html,
'/tr
[./td[@colspan="2"]/ul
[./li[.="[trans]Error![/trans]"]]
]
/following-sibling::tr[@style="display: none"]
[./td[@colspan="2"]/input
[@type="hidden"]
[@id="name__token"]
]
/following-sibling::tr
[
./td
[./label[@for="name_first"]]
/following-sibling::td
[./input[@id="name_first"]]
]
/following-sibling::tr
[
./td
[./label[@for="name_second"]]
/following-sibling::td
[./input[@id="name_second"]]
]
[count(../tr)=4]
'
);
}
public function testRest() public function testRest()
{ {
$view = $this->factory->createNamedBuilder('form', 'name') $view = $this->factory->createNamedBuilder('form', 'name')
@ -151,9 +161,9 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
$this->assertWidgetMatchesXpath($form->createView(), array(), $this->assertWidgetMatchesXpath($form->createView(), array(),
'/table '/table
[ [
./tr[./td/input[@type="text"][@value="a"]] ./tr[@style="display: none"][./td[@colspan="2"]/input[@type="hidden"][@id="name__token"]]
/following-sibling::tr[./td/input[@type="text"][@value="a"]]
/following-sibling::tr[./td/input[@type="text"][@value="b"]] /following-sibling::tr[./td/input[@type="text"][@value="b"]]
/following-sibling::tr[./td/input[@type="hidden"][@id="name__token"]]
] ]
[count(./tr[./td/input])=3] [count(./tr[./td/input])=3]
' '
@ -200,7 +210,10 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
public function testNestedFormError() public function testNestedFormError()
{ {
$form = $this->factory->createNamedBuilder('form', 'name') $form = $this->factory->createNamedBuilder('form', 'name')
->add('child', 'form', array('error_bubbling' => false)) ->add($this->factory
->createNamedBuilder('form', 'child', null, array('error_bubbling' => false))
->add('grandChild', 'form')
)
->getForm(); ->getForm();
$form->get('child')->addError(new FormError('Error!')); $form->get('child')->addError(new FormError('Error!'));
@ -217,6 +230,34 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
); );
} }
public function testCsrf()
{
$this->csrfProvider->expects($this->any())
->method('generateCsrfToken')
->will($this->returnValue('foo&bar'));
$form = $this->factory->createNamedBuilder('form', 'name')
->add($this->factory
// No CSRF protection on nested forms
->createNamedBuilder('form', 'child')
->add($this->factory->createNamedBuilder('text', 'grandchild'))
)
->getForm();
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/table
[
./tr[@style="display: none"]
[./td[@colspan="2"]/input
[@type="hidden"]
[@id="name__token"]
]
]
[count(.//input[@type="hidden"])=1]
'
);
}
public function testRepeated() public function testRepeated()
{ {
$form = $this->factory->createNamed('repeated', 'name', 'foobar', array( $form = $this->factory->createNamed('repeated', 'name', 'foobar', array(
@ -226,7 +267,12 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
$this->assertWidgetMatchesXpath($form->createView(), array(), $this->assertWidgetMatchesXpath($form->createView(), array(),
'/table '/table
[ [
./tr ./tr[@style="display: none"]
[./td[@colspan="2"]/input
[@type="hidden"]
[@id="name__token"]
]
/following-sibling::tr
[ [
./td ./td
[./label[@for="name_first"]] [./label[@for="name_first"]]
@ -241,7 +287,7 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
[./input[@type="text"][@id="name_second"]] [./input[@type="text"][@id="name_second"]]
] ]
] ]
[count(.//input)=2] [count(.//input)=3]
' '
); );
} }
@ -257,7 +303,12 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
$this->assertWidgetMatchesXpath($form->createView(), array(), $this->assertWidgetMatchesXpath($form->createView(), array(),
'/table '/table
[ [
./tr ./tr[@style="display: none"]
[./td[@colspan="2"]/input
[@type="hidden"]
[@id="name__token"]
]
/following-sibling::tr
[ [
./td ./td
[./label[@for="name_first"][.="[trans]Test[/trans]"]] [./label[@for="name_first"][.="[trans]Test[/trans]"]]
@ -272,7 +323,7 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
[./input[@type="password"][@id="name_second"][@required="required"]] [./input[@type="password"][@id="name_second"][@required="required"]]
] ]
] ]
[count(.//input)=2] [count(.//input)=3]
' '
); );
} }

View File

@ -98,7 +98,7 @@ class ChoiceTypeTest extends TypeTestCase
)); ));
} }
public function testExpandedChoicesOptionsTurnIntoFields() public function testExpandedChoicesOptionsTurnIntoChildren()
{ {
$form = $this->factory->create('choice', null, array( $form = $this->factory->create('choice', null, array(
'expanded' => true, 'expanded' => true,
@ -141,7 +141,7 @@ class ChoiceTypeTest extends TypeTestCase
} }
} }
public function testExpandedRadiosAreRequiredIfChoiceFieldIsRequired() public function testExpandedRadiosAreRequiredIfChoiceChildIsRequired()
{ {
$form = $this->factory->create('choice', null, array( $form = $this->factory->create('choice', null, array(
'multiple' => false, 'multiple' => false,
@ -155,7 +155,7 @@ class ChoiceTypeTest extends TypeTestCase
} }
} }
public function testExpandedRadiosAreNotRequiredIfChoiceFieldIsNotRequired() public function testExpandedRadiosAreNotRequiredIfChoiceChildIsNotRequired()
{ {
$form = $this->factory->create('choice', null, array( $form = $this->factory->create('choice', null, array(
'multiple' => false, 'multiple' => false,
@ -288,7 +288,7 @@ class ChoiceTypeTest extends TypeTestCase
$this->assertNull($form[4]->getClientData()); $this->assertNull($form[4]->getClientData());
} }
public function testBindSingleExpandedWithFalseDoesNotHaveExtraFields() public function testBindSingleExpandedWithFalseDoesNotHaveExtraChildren()
{ {
$form = $this->factory->create('choice', null, array( $form = $this->factory->create('choice', null, array(
'multiple' => false, 'multiple' => false,
@ -302,7 +302,7 @@ class ChoiceTypeTest extends TypeTestCase
$this->assertNull($form->getData()); $this->assertNull($form->getData());
} }
public function testBindSingleExpandedWithEmptyField() public function testBindSingleExpandedWithEmptyChild()
{ {
$form = $this->factory->create('choice', null, array( $form = $this->factory->create('choice', null, array(
'multiple' => false, 'multiple' => false,
@ -422,7 +422,7 @@ class ChoiceTypeTest extends TypeTestCase
$this->assertNull($form[4]->getClientData()); $this->assertNull($form[4]->getClientData());
} }
public function testBindMultipleExpandedWithEmptyField() public function testBindMultipleExpandedWithEmptyChild()
{ {
$form = $this->factory->create('choice', null, array( $form = $this->factory->create('choice', null, array(
'multiple' => true, 'multiple' => true,

View File

@ -15,10 +15,10 @@ use Symfony\Component\Form\Form;
class CollectionTypeTest extends TypeTestCase class CollectionTypeTest extends TypeTestCase
{ {
public function testContainsNoFieldByDefault() public function testContainsNoChildByDefault()
{ {
$form = $this->factory->create('collection', null, array( $form = $this->factory->create('collection', null, array(
'type' => 'field', 'type' => 'form',
)); ));
$this->assertCount(0, $form); $this->assertCount(0, $form);
@ -27,7 +27,7 @@ class CollectionTypeTest extends TypeTestCase
public function testSetDataAdjustsSize() public function testSetDataAdjustsSize()
{ {
$form = $this->factory->create('collection', null, array( $form = $this->factory->create('collection', null, array(
'type' => 'field', 'type' => 'form',
'options' => array( 'options' => array(
'max_length' => 20, 'max_length' => 20,
), ),
@ -53,7 +53,7 @@ class CollectionTypeTest extends TypeTestCase
public function testThrowsExceptionIfObjectIsNotTraversable() public function testThrowsExceptionIfObjectIsNotTraversable()
{ {
$form = $this->factory->create('collection', null, array( $form = $this->factory->create('collection', null, array(
'type' => 'field', 'type' => 'form',
)); ));
$this->setExpectedException('Symfony\Component\Form\Exception\UnexpectedTypeException'); $this->setExpectedException('Symfony\Component\Form\Exception\UnexpectedTypeException');
$form->setData(new \stdClass()); $form->setData(new \stdClass());
@ -62,7 +62,7 @@ class CollectionTypeTest extends TypeTestCase
public function testNotResizedIfBoundWithMissingData() public function testNotResizedIfBoundWithMissingData()
{ {
$form = $this->factory->create('collection', null, array( $form = $this->factory->create('collection', null, array(
'type' => 'field', 'type' => 'form',
)); ));
$form->setData(array('foo@foo.com', 'bar@bar.com')); $form->setData(array('foo@foo.com', 'bar@bar.com'));
$form->bind(array('foo@bar.com')); $form->bind(array('foo@bar.com'));
@ -76,7 +76,7 @@ class CollectionTypeTest extends TypeTestCase
public function testResizedDownIfBoundWithMissingDataAndAllowDelete() public function testResizedDownIfBoundWithMissingDataAndAllowDelete()
{ {
$form = $this->factory->create('collection', null, array( $form = $this->factory->create('collection', null, array(
'type' => 'field', 'type' => 'form',
'allow_delete' => true, 'allow_delete' => true,
)); ));
$form->setData(array('foo@foo.com', 'bar@bar.com')); $form->setData(array('foo@foo.com', 'bar@bar.com'));
@ -91,7 +91,7 @@ class CollectionTypeTest extends TypeTestCase
public function testNotResizedIfBoundWithExtraData() public function testNotResizedIfBoundWithExtraData()
{ {
$form = $this->factory->create('collection', null, array( $form = $this->factory->create('collection', null, array(
'type' => 'field', 'type' => 'form',
)); ));
$form->setData(array('foo@bar.com')); $form->setData(array('foo@bar.com'));
$form->bind(array('foo@foo.com', 'bar@bar.com')); $form->bind(array('foo@foo.com', 'bar@bar.com'));
@ -104,7 +104,7 @@ class CollectionTypeTest extends TypeTestCase
public function testResizedUpIfBoundWithExtraDataAndAllowAdd() public function testResizedUpIfBoundWithExtraDataAndAllowAdd()
{ {
$form = $this->factory->create('collection', null, array( $form = $this->factory->create('collection', null, array(
'type' => 'field', 'type' => 'form',
'allow_add' => true, 'allow_add' => true,
)); ));
$form->setData(array('foo@bar.com')); $form->setData(array('foo@bar.com'));
@ -120,7 +120,7 @@ class CollectionTypeTest extends TypeTestCase
public function testAllowAddButNoPrototype() public function testAllowAddButNoPrototype()
{ {
$form = $this->factory->create('collection', null, array( $form = $this->factory->create('collection', null, array(
'type' => 'field', 'type' => 'form',
'allow_add' => true, 'allow_add' => true,
'prototype' => false, 'prototype' => false,
)); ));
@ -169,7 +169,7 @@ class CollectionTypeTest extends TypeTestCase
public function testPrototypeNameOption() public function testPrototypeNameOption()
{ {
$form = $this->factory->create('collection', null, array( $form = $this->factory->create('collection', null, array(
'type' => 'field', 'type' => 'form',
'prototype' => true, 'prototype' => true,
'allow_add' => true, 'allow_add' => true,
)); ));
@ -177,7 +177,7 @@ class CollectionTypeTest extends TypeTestCase
$this->assertSame('__name__', $form->getAttribute('prototype')->getName(), '__name__ is the default'); $this->assertSame('__name__', $form->getAttribute('prototype')->getName(), '__name__ is the default');
$form = $this->factory->create('collection', null, array( $form = $this->factory->create('collection', null, array(
'type' => 'field', 'type' => 'form',
'prototype' => true, 'prototype' => true,
'allow_add' => true, 'allow_add' => true,
'prototype_name' => '__test__', 'prototype_name' => '__test__',

View File

@ -1,380 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Tests\Extension\Core\Type;
use Symfony\Component\Form\Util\PropertyPath;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\Tests\Fixtures\Author;
use Symfony\Component\Form\Tests\Fixtures\FixedDataTransformer;
class FieldTypeTest extends TypeTestCase
{
public function testGetPropertyPathDefaultPath()
{
$form = $this->factory->createNamed('field', 'title');
$this->assertEquals(new PropertyPath('title'), $form->getAttribute('property_path'));
}
public function testGetPropertyPathPathIsZero()
{
$form = $this->factory->create('field', null, array('property_path' => '0'));
$this->assertEquals(new PropertyPath('0'), $form->getAttribute('property_path'));
}
public function testGetPropertyPathPathIsEmpty()
{
$form = $this->factory->create('field', null, array('property_path' => ''));
$this->assertNull($form->getAttribute('property_path'));
}
public function testGetPropertyPathPathIsFalse()
{
$form = $this->factory->create('field', null, array('property_path' => false));
$this->assertNull($form->getAttribute('property_path'));
}
public function testGetPropertyPathPathIsNull()
{
$form = $this->factory->createNamed('field', 'title', null, array('property_path' => null));
$this->assertEquals(new PropertyPath('title'), $form->getAttribute('property_path'));
}
public function testPassRequiredAsOption()
{
$form = $this->factory->create('field', null, array('required' => false));
$this->assertFalse($form->isRequired());
$form = $this->factory->create('field', null, array('required' => true));
$this->assertTrue($form->isRequired());
}
public function testPassDisabledAsOption()
{
$form = $this->factory->create('field', null, array('disabled' => true));
$this->assertTrue($form->isDisabled());
}
public function testBoundDataIsTrimmedBeforeTransforming()
{
$form = $this->factory->createBuilder('field')
->appendClientTransformer(new FixedDataTransformer(array(
null => '',
'reverse[a]' => 'a',
)))
->getForm();
$form->bind(' a ');
$this->assertEquals('a', $form->getClientData());
$this->assertEquals('reverse[a]', $form->getData());
}
public function testBoundDataIsNotTrimmedBeforeTransformingIfNoTrimming()
{
$form = $this->factory->createBuilder('field', null, array('trim' => false))
->appendClientTransformer(new FixedDataTransformer(array(
null => '',
'reverse[ a ]' => ' a ',
)))
->getForm();
$form->bind(' a ');
$this->assertEquals(' a ', $form->getClientData());
$this->assertEquals('reverse[ a ]', $form->getData());
}
public function testPassIdAndNameToView()
{
$form = $this->factory->createNamed('field', 'name');
$view = $form->createView();
$this->assertEquals('name', $view->get('id'));
$this->assertEquals('name', $view->get('name'));
$this->assertEquals('name', $view->get('full_name'));
}
public function testStripLeadingUnderscoresAndDigitsFromId()
{
$form = $this->factory->createNamed('field', '_09name');
$view = $form->createView();
$this->assertEquals('name', $view->get('id'));
$this->assertEquals('_09name', $view->get('name'));
$this->assertEquals('_09name', $view->get('full_name'));
}
public function testPassIdAndNameToViewWithParent()
{
$parent = $this->factory->createNamed('field', 'parent');
$parent->add($this->factory->createNamed('field', 'child'));
$view = $parent->createView();
$this->assertEquals('parent_child', $view['child']->get('id'));
$this->assertEquals('child', $view['child']->get('name'));
$this->assertEquals('parent[child]', $view['child']->get('full_name'));
}
public function testPassIdAndNameToViewWithGrandParent()
{
$parent = $this->factory->createNamed('field', 'parent');
$parent->add($this->factory->createNamed('field', 'child'));
$parent['child']->add($this->factory->createNamed('field', 'grand_child'));
$view = $parent->createView();
$this->assertEquals('parent_child_grand_child', $view['child']['grand_child']->get('id'));
$this->assertEquals('grand_child', $view['child']['grand_child']->get('name'));
$this->assertEquals('parent[child][grand_child]', $view['child']['grand_child']->get('full_name'));
}
public function testNonReadOnlyFieldWithReadOnlyParentBeingReadOnly()
{
$parent = $this->factory->createNamed('field', 'parent', null, array('read_only' => true));
$child = $this->factory->createNamed('field', 'child');
$view = $parent->add($child)->createView();
$this->assertTrue($view['child']->get('read_only'));
}
public function testReadOnlyFieldWithNonReadOnlyParentBeingReadOnly()
{
$parent = $this->factory->createNamed('field', 'parent');
$child = $this->factory->createNamed('field', 'child', null, array('read_only' => true));
$view = $parent->add($child)->createView();
$this->assertTrue($view['child']->get('read_only'));
}
public function testNonReadOnlyFieldWithNonReadOnlyParentBeingNonReadOnly()
{
$parent = $this->factory->createNamed('field', 'parent');
$child = $this->factory->createNamed('field', 'child');
$view = $parent->add($child)->createView();
$this->assertFalse($view['child']->get('read_only'));
}
public function testPassMaxLengthToView()
{
$form = $this->factory->create('field', null, array('max_length' => 10));
$view = $form->createView();
$this->assertSame(10, $view->get('max_length'));
}
public function testPassTranslationDomainToView()
{
$form = $this->factory->create('field', null, array('translation_domain' => 'test'));
$view = $form->createView();
$this->assertSame('test', $view->get('translation_domain'));
}
public function testPassDefaultLabelToView()
{
$form = $this->factory->createNamed('field', '__test___field');
$view = $form->createView();
$this->assertSame('Test field', $view->get('label'));
}
public function testPassLabelToView()
{
$form = $this->factory->createNamed('field', '__test___field', null, array('label' => 'My label'));
$view = $form->createView();
$this->assertSame('My label', $view->get('label'));
}
public function testDefaultTranslationDomain()
{
$form = $this->factory->create('field');
$view = $form->createView();
$this->assertSame('messages', $view->get('translation_domain'));
}
public function testBindWithEmptyDataCreatesObjectIfClassAvailable()
{
$form = $this->factory->create('form', null, array(
'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author',
'required' => false,
));
$form->add($this->factory->createNamed('field', 'firstName'));
$form->add($this->factory->createNamed('field', 'lastName'));
$form->setData(null);
// partially empty, still an object is created
$form->bind(array('firstName' => 'Bernhard', 'lastName' => ''));
$author = new Author();
$author->firstName = 'Bernhard';
$author->setLastName('');
$this->assertEquals($author, $form->getData());
}
public function testBindWithEmptyDataCreatesObjectIfInitiallyBoundWithObject()
{
$form = $this->factory->create('form', null, array(
// data class is inferred from the passed object
'data' => new Author(),
'required' => false,
));
$form->add($this->factory->createNamed('field', 'firstName'));
$form->add($this->factory->createNamed('field', 'lastName'));
$form->setData(null);
// partially empty, still an object is created
$form->bind(array('firstName' => 'Bernhard', 'lastName' => ''));
$author = new Author();
$author->firstName = 'Bernhard';
$author->setLastName('');
$this->assertEquals($author, $form->getData());
}
public function testBindWithEmptyDataDoesNotCreateObjectIfDataClassIsNull()
{
$form = $this->factory->create('form', null, array(
'data' => new Author(),
'data_class' => null,
'required' => false,
));
$form->add($this->factory->createNamed('field', 'firstName'));
$form->setData(null);
$form->bind(array('firstName' => 'Bernhard'));
$this->assertSame(array('firstName' => 'Bernhard'), $form->getData());
}
public function testBindEmptyWithEmptyDataCreatesNoObjectIfNotRequired()
{
$form = $this->factory->create('form', null, array(
'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author',
'required' => false,
));
$form->add($this->factory->createNamed('field', 'firstName'));
$form->add($this->factory->createNamed('field', 'lastName'));
$form->setData(null);
$form->bind(array('firstName' => '', 'lastName' => ''));
$this->assertNull($form->getData());
}
public function testBindEmptyWithEmptyDataCreatesObjectIfRequired()
{
$form = $this->factory->create('form', null, array(
'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author',
'required' => true,
));
$form->add($this->factory->createNamed('field', 'firstName'));
$form->add($this->factory->createNamed('field', 'lastName'));
$form->setData(null);
$form->bind(array('firstName' => '', 'lastName' => ''));
$this->assertEquals(new Author(), $form->getData());
}
/*
* We need something to write the field values into
*/
public function testBindWithEmptyDataStoresArrayIfNoClassAvailable()
{
$form = $this->factory->create('form');
$form->add($this->factory->createNamed('field', 'firstName'));
$form->setData(null);
$form->bind(array('firstName' => 'Bernhard'));
$this->assertSame(array('firstName' => 'Bernhard'), $form->getData());
}
public function testBindWithEmptyDataUsesEmptyDataOption()
{
$author = new Author();
$form = $this->factory->create('form', null, array(
'empty_data' => $author,
));
$form->add($this->factory->createNamed('field', 'firstName'));
$form->bind(array('firstName' => 'Bernhard'));
$this->assertSame($author, $form->getData());
$this->assertEquals('Bernhard', $author->firstName);
}
public function testGetAttributesIsEmpty()
{
$form = $this->factory->create('field', null, array('attr' => array()));
$this->assertCount(0, $form->getAttribute('attr'));
}
/**
* @see https://github.com/symfony/symfony/issues/1986
*/
public function testSetDataThroughParamsWithZero()
{
$form = $this->factory->create('field', null, array('data' => 0));
$view = $form->createView();
$this->assertFalse($form->isEmpty());
$this->assertSame('0', $view->get('value'));
$this->assertSame('0', $form->getData());
$form = $this->factory->create('field', null, array('data' => '0'));
$view = $form->createView();
$this->assertFalse($form->isEmpty());
$this->assertSame('0', $view->get('value'));
$this->assertSame('0', $form->getData());
$form = $this->factory->create('field', null, array('data' => '00000'));
$view = $form->createView();
$this->assertFalse($form->isEmpty());
$this->assertSame('00000', $view->get('value'));
$this->assertSame('00000', $form->getData());
}
/**
* @expectedException Symfony\Component\Form\Exception\FormException
*/
public function testAttributesException()
{
$form = $this->factory->create('field', null, array('attr' => ''));
}
public function testNameCanBeEmptyString()
{
$form = $this->factory->createNamed('field', '');
$this->assertEquals('', $form->getName());
}
}

View File

@ -11,9 +11,11 @@
namespace Symfony\Component\Form\Tests\Extension\Core\Type; namespace Symfony\Component\Form\Tests\Extension\Core\Type;
use Symfony\Component\Form\Util\PropertyPath;
use Symfony\Component\Form\Form; use Symfony\Component\Form\Form;
use Symfony\Component\Form\CallbackTransformer; use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\Tests\Fixtures\Author; use Symfony\Component\Form\Tests\Fixtures\Author;
use Symfony\Component\Form\Tests\Fixtures\FixedDataTransformer;
class FormTest_AuthorWithoutRefSetter class FormTest_AuthorWithoutRefSetter
{ {
@ -49,13 +51,388 @@ class FormTest_AuthorWithoutRefSetter
class FormTypeTest extends TypeTestCase class FormTypeTest extends TypeTestCase
{ {
public function testGetPropertyPathDefaultPath()
{
$form = $this->factory->createNamed('form', 'title');
$this->assertEquals(new PropertyPath('title'), $form->getAttribute('property_path'));
}
public function testGetPropertyPathPathIsZero()
{
$form = $this->factory->create('form', null, array('property_path' => '0'));
$this->assertEquals(new PropertyPath('0'), $form->getAttribute('property_path'));
}
public function testGetPropertyPathPathIsEmpty()
{
$form = $this->factory->create('form', null, array('property_path' => ''));
$this->assertNull($form->getAttribute('property_path'));
}
public function testGetPropertyPathPathIsFalse()
{
$form = $this->factory->create('form', null, array('property_path' => false));
$this->assertNull($form->getAttribute('property_path'));
}
public function testGetPropertyPathPathIsNull()
{
$form = $this->factory->createNamed('form', 'title', null, array('property_path' => null));
$this->assertEquals(new PropertyPath('title'), $form->getAttribute('property_path'));
}
public function testPassRequiredAsOption()
{
$form = $this->factory->create('form', null, array('required' => false));
$this->assertFalse($form->isRequired());
$form = $this->factory->create('form', null, array('required' => true));
$this->assertTrue($form->isRequired());
}
public function testPassDisabledAsOption()
{
$form = $this->factory->create('form', null, array('disabled' => true));
$this->assertTrue($form->isDisabled());
}
public function testBoundDataIsTrimmedBeforeTransforming()
{
$form = $this->factory->createBuilder('form')
->appendClientTransformer(new FixedDataTransformer(array(
null => '',
'reverse[a]' => 'a',
)))
->getForm();
$form->bind(' a ');
$this->assertEquals('a', $form->getClientData());
$this->assertEquals('reverse[a]', $form->getData());
}
public function testBoundDataIsNotTrimmedBeforeTransformingIfNoTrimming()
{
$form = $this->factory->createBuilder('form', null, array('trim' => false))
->appendClientTransformer(new FixedDataTransformer(array(
null => '',
'reverse[ a ]' => ' a ',
)))
->getForm();
$form->bind(' a ');
$this->assertEquals(' a ', $form->getClientData());
$this->assertEquals('reverse[ a ]', $form->getData());
}
public function testPassIdAndNameToView()
{
$form = $this->factory->createNamed('form', 'name');
$view = $form->createView();
$this->assertEquals('name', $view->get('id'));
$this->assertEquals('name', $view->get('name'));
$this->assertEquals('name', $view->get('full_name'));
}
public function testStripLeadingUnderscoresAndDigitsFromId()
{
$form = $this->factory->createNamed('form', '_09name');
$view = $form->createView();
$this->assertEquals('name', $view->get('id'));
$this->assertEquals('_09name', $view->get('name'));
$this->assertEquals('_09name', $view->get('full_name'));
}
public function testPassIdAndNameToViewWithParent()
{
$parent = $this->factory->createNamed('form', 'parent');
$parent->add($this->factory->createNamed('form', 'child'));
$view = $parent->createView();
$this->assertEquals('parent_child', $view['child']->get('id'));
$this->assertEquals('child', $view['child']->get('name'));
$this->assertEquals('parent[child]', $view['child']->get('full_name'));
}
public function testPassIdAndNameToViewWithGrandParent()
{
$parent = $this->factory->createNamed('form', 'parent');
$parent->add($this->factory->createNamed('form', 'child'));
$parent['child']->add($this->factory->createNamed('form', 'grand_child'));
$view = $parent->createView();
$this->assertEquals('parent_child_grand_child', $view['child']['grand_child']->get('id'));
$this->assertEquals('grand_child', $view['child']['grand_child']->get('name'));
$this->assertEquals('parent[child][grand_child]', $view['child']['grand_child']->get('full_name'));
}
public function testNonReadOnlyFormWithReadOnlyParentBeingReadOnly()
{
$parent = $this->factory->createNamed('form', 'parent', null, array('read_only' => true));
$child = $this->factory->createNamed('form', 'child');
$view = $parent->add($child)->createView();
$this->assertTrue($view['child']->get('read_only'));
}
public function testReadOnlyFormWithNonReadOnlyParentBeingReadOnly()
{
$parent = $this->factory->createNamed('form', 'parent');
$child = $this->factory->createNamed('form', 'child', null, array('read_only' => true));
$view = $parent->add($child)->createView();
$this->assertTrue($view['child']->get('read_only'));
}
public function testNonReadOnlyFormWithNonReadOnlyParentBeingNonReadOnly()
{
$parent = $this->factory->createNamed('form', 'parent');
$child = $this->factory->createNamed('form', 'child');
$view = $parent->add($child)->createView();
$this->assertFalse($view['child']->get('read_only'));
}
public function testPassMaxLengthToView()
{
$form = $this->factory->create('form', null, array('max_length' => 10));
$view = $form->createView();
$this->assertSame(10, $view->get('max_length'));
}
public function testPassTranslationDomainToView()
{
$form = $this->factory->create('form', null, array('translation_domain' => 'test'));
$view = $form->createView();
$this->assertSame('test', $view->get('translation_domain'));
}
public function testPassDefaultLabelToView()
{
$form = $this->factory->createNamed('form', '__test___field');
$view = $form->createView();
$this->assertSame('Test field', $view->get('label'));
}
public function testPassLabelToView()
{
$form = $this->factory->createNamed('form', '__test___field', null, array('label' => 'My label'));
$view = $form->createView();
$this->assertSame('My label', $view->get('label'));
}
public function testDefaultTranslationDomain()
{
$form = $this->factory->create('form');
$view = $form->createView();
$this->assertSame('messages', $view->get('translation_domain'));
}
public function testBindWithEmptyDataCreatesObjectIfClassAvailable()
{
$form = $this->factory->create('form', null, array(
'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author',
'required' => false,
));
$form->add($this->factory->createNamed('form', 'firstName'));
$form->add($this->factory->createNamed('form', 'lastName'));
$form->setData(null);
// partially empty, still an object is created
$form->bind(array('firstName' => 'Bernhard', 'lastName' => ''));
$author = new Author();
$author->firstName = 'Bernhard';
$author->setLastName('');
$this->assertEquals($author, $form->getData());
}
public function testBindWithEmptyDataCreatesObjectIfInitiallyBoundWithObject()
{
$form = $this->factory->create('form', null, array(
// data class is inferred from the passed object
'data' => new Author(),
'required' => false,
));
$form->add($this->factory->createNamed('form', 'firstName'));
$form->add($this->factory->createNamed('form', 'lastName'));
$form->setData(null);
// partially empty, still an object is created
$form->bind(array('firstName' => 'Bernhard', 'lastName' => ''));
$author = new Author();
$author->firstName = 'Bernhard';
$author->setLastName('');
$this->assertEquals($author, $form->getData());
}
public function testBindWithEmptyDataDoesNotCreateObjectIfDataClassIsNull()
{
$form = $this->factory->create('form', null, array(
'data' => new Author(),
'data_class' => null,
'required' => false,
));
$form->add($this->factory->createNamed('form', 'firstName'));
$form->setData(null);
$form->bind(array('firstName' => 'Bernhard'));
$this->assertSame(array('firstName' => 'Bernhard'), $form->getData());
}
public function testBindEmptyWithEmptyDataCreatesNoObjectIfNotRequired()
{
$form = $this->factory->create('form', null, array(
'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author',
'required' => false,
));
$form->add($this->factory->createNamed('form', 'firstName'));
$form->add($this->factory->createNamed('form', 'lastName'));
$form->setData(null);
$form->bind(array('firstName' => '', 'lastName' => ''));
$this->assertNull($form->getData());
}
public function testBindEmptyWithEmptyDataCreatesObjectIfRequired()
{
$form = $this->factory->create('form', null, array(
'data_class' => 'Symfony\Component\Form\Tests\Fixtures\Author',
'required' => true,
));
$form->add($this->factory->createNamed('form', 'firstName'));
$form->add($this->factory->createNamed('form', 'lastName'));
$form->setData(null);
$form->bind(array('firstName' => '', 'lastName' => ''));
$this->assertEquals(new Author(), $form->getData());
}
/*
* We need something to write the field values into
*/
public function testBindWithEmptyDataStoresArrayIfNoClassAvailable()
{
$form = $this->factory->create('form');
$form->add($this->factory->createNamed('form', 'firstName'));
$form->setData(null);
$form->bind(array('firstName' => 'Bernhard'));
$this->assertSame(array('firstName' => 'Bernhard'), $form->getData());
}
public function testBindWithEmptyDataPassesEmptyStringToTransformerIfNoChildren()
{
$form = $this->factory->createBuilder('form')
->appendClientTransformer(new FixedDataTransformer(array(
// required for the initial, internal setData(null)
null => 'null',
// required to test that bind(null) is converted to ''
'empty' => '',
)))
->getForm();
$form->bind(null);
$this->assertSame('empty', $form->getData());
}
public function testBindWithEmptyDataUsesEmptyDataOption()
{
$author = new Author();
$form = $this->factory->create('form', null, array(
'empty_data' => $author,
));
$form->add($this->factory->createNamed('form', 'firstName'));
$form->bind(array('firstName' => 'Bernhard'));
$this->assertSame($author, $form->getData());
$this->assertEquals('Bernhard', $author->firstName);
}
public function testGetAttributesIsEmpty()
{
$form = $this->factory->create('form', null, array('attr' => array()));
$this->assertCount(0, $form->getAttribute('attr'));
}
/**
* @see https://github.com/symfony/symfony/issues/1986
*/
public function testSetDataThroughParamsWithZero()
{
$form = $this->factory->create('form', null, array('data' => 0));
$view = $form->createView();
$this->assertFalse($form->isEmpty());
$this->assertSame('0', $view->get('value'));
$this->assertSame('0', $form->getData());
$form = $this->factory->create('form', null, array('data' => '0'));
$view = $form->createView();
$this->assertFalse($form->isEmpty());
$this->assertSame('0', $view->get('value'));
$this->assertSame('0', $form->getData());
$form = $this->factory->create('form', null, array('data' => '00000'));
$view = $form->createView();
$this->assertFalse($form->isEmpty());
$this->assertSame('00000', $view->get('value'));
$this->assertSame('00000', $form->getData());
}
/**
* @expectedException Symfony\Component\Form\Exception\FormException
*/
public function testAttributesException()
{
$form = $this->factory->create('form', null, array('attr' => ''));
}
public function testNameCanBeEmptyString()
{
$form = $this->factory->createNamed('form', '');
$this->assertEquals('', $form->getName());
}
public function testSubformDoesntCallSetters() public function testSubformDoesntCallSetters()
{ {
$author = new FormTest_AuthorWithoutRefSetter(new Author()); $author = new FormTest_AuthorWithoutRefSetter(new Author());
$builder = $this->factory->createBuilder('form'); $builder = $this->factory->createBuilder('form');
$builder->add('reference', 'form'); $builder->add('reference', 'form');
$builder->get('reference')->add('firstName', 'field'); $builder->get('reference')->add('firstName', 'form');
$builder->setData($author); $builder->setData($author);
$form = $builder->getForm(); $form = $builder->getForm();
@ -77,7 +454,7 @@ class FormTypeTest extends TypeTestCase
$builder = $this->factory->createBuilder('form'); $builder = $this->factory->createBuilder('form');
$builder->add('referenceCopy', 'form'); $builder->add('referenceCopy', 'form');
$builder->get('referenceCopy')->add('firstName', 'field'); $builder->get('referenceCopy')->add('firstName', 'form');
$builder->setData($author); $builder->setData($author);
$form = $builder->getForm(); $form = $builder->getForm();
@ -99,7 +476,7 @@ class FormTypeTest extends TypeTestCase
$builder = $this->factory->createBuilder('form'); $builder = $this->factory->createBuilder('form');
$builder->add('referenceCopy', 'form', array('by_reference' => false)); $builder->add('referenceCopy', 'form', array('by_reference' => false));
$builder->get('referenceCopy')->add('firstName', 'field'); $builder->get('referenceCopy')->add('firstName', 'form');
$builder->setData($author); $builder->setData($author);
$form = $builder->getForm(); $form = $builder->getForm();

View File

@ -21,7 +21,7 @@ class RepeatedTypeTest extends TypeTestCase
parent::setUp(); parent::setUp();
$this->form = $this->factory->create('repeated', null, array( $this->form = $this->factory->create('repeated', null, array(
'type' => 'field', 'type' => 'form',
)); ));
$this->form->setData(null); $this->form->setData(null);
} }
@ -37,7 +37,7 @@ class RepeatedTypeTest extends TypeTestCase
public function testSetOptions() public function testSetOptions()
{ {
$form = $this->factory->create('repeated', null, array( $form = $this->factory->create('repeated', null, array(
'type' => 'field', 'type' => 'form',
'options' => array('label' => 'Global'), 'options' => array('label' => 'Global'),
)); ));
@ -47,11 +47,11 @@ class RepeatedTypeTest extends TypeTestCase
$this->assertTrue($form['second']->isRequired()); $this->assertTrue($form['second']->isRequired());
} }
public function testSetOptionsPerField() public function testSetOptionsPerChild()
{ {
$form = $this->factory->create('repeated', null, array( $form = $this->factory->create('repeated', null, array(
// the global required value cannot be overriden // the global required value cannot be overriden
'type' => 'field', 'type' => 'form',
'first_options' => array('label' => 'Test', 'required' => false), 'first_options' => array('label' => 'Test', 'required' => false),
'second_options' => array('label' => 'Test2') 'second_options' => array('label' => 'Test2')
)); ));
@ -66,17 +66,17 @@ class RepeatedTypeTest extends TypeTestCase
{ {
$form = $this->factory->create('repeated', null, array( $form = $this->factory->create('repeated', null, array(
'required' => false, 'required' => false,
'type' => 'field', 'type' => 'form',
)); ));
$this->assertFalse($form['first']->isRequired()); $this->assertFalse($form['first']->isRequired());
$this->assertFalse($form['second']->isRequired()); $this->assertFalse($form['second']->isRequired());
} }
public function testSetOptionsPerFieldAndOverwrite() public function testSetOptionsPerChildAndOverwrite()
{ {
$form = $this->factory->create('repeated', null, array( $form = $this->factory->create('repeated', null, array(
'type' => 'field', 'type' => 'form',
'options' => array('label' => 'Label'), 'options' => array('label' => 'Label'),
'second_options' => array('label' => 'Second label') 'second_options' => array('label' => 'Second label')
)); ));

View File

@ -1,87 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Tests\Extension\Csrf\EventListener;
use Symfony\Component\Form\Event\DataEvent;
use Symfony\Component\Form\Extension\Csrf\EventListener\EnsureCsrfFieldListener;
class EnsureCsrfFieldListenerTest extends \PHPUnit_Framework_TestCase
{
private $form;
private $formFactory;
private $field;
private $event;
protected function setUp()
{
if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) {
$this->markTestSkipped('The "EventDispatcher" component is not available');
}
$this->formFactory = $this->getMock('Symfony\\Component\\Form\\FormFactoryInterface');
$this->form = $this->getMock('Symfony\\Component\\Form\\Tests\\FormInterface');
$this->field = $this->getMock('Symfony\\Component\\Form\\Tests\\FormInterface');
$this->event = new DataEvent($this->form, array());
}
protected function tearDown()
{
$this->form = null;
$this->formFactory = null;
$this->field = null;
$this->event = null;
}
public function testAddField()
{
$this->formFactory->expects($this->once())
->method('createNamed')
->with('csrf', '_token', null, array())
->will($this->returnValue($this->field));
$this->form->expects($this->once())
->method('add')
->with($this->isInstanceOf('Symfony\\Component\\Form\\Tests\\FormInterface'));
$listener = new EnsureCsrfFieldListener($this->formFactory, '_token');
$listener->ensureCsrfField($this->event);
}
public function testIntention()
{
$this->formFactory->expects($this->once())
->method('createNamed')
->with('csrf', '_token', null, array('intention' => 'something'))
->will($this->returnValue($this->field));
$this->form->expects($this->once())
->method('add')
->with($this->isInstanceOf('Symfony\\Component\\Form\\Tests\\FormInterface'));
$listener = new EnsureCsrfFieldListener($this->formFactory, '_token', 'something');
$listener->ensureCsrfField($this->event);
}
public function testProvider()
{
$provider = $this->getMock('Symfony\\Component\\Form\\Extension\\Csrf\\CsrfProvider\\CsrfProviderInterface');
$this->formFactory->expects($this->once())
->method('createNamed')
->with('csrf', '_token', null, array('csrf_provider' => $provider))
->will($this->returnValue($this->field));
$this->form->expects($this->once())
->method('add')
->with($this->isInstanceOf('Symfony\\Component\\Form\\Tests\\FormInterface'));
$listener = new EnsureCsrfFieldListener($this->formFactory, '_token', null, $provider);
$listener->ensureCsrfField($this->event);
}
}

View File

@ -1,112 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Tests\Extension\Csrf\Type;
class CsrfTypeTest extends TypeTestCase
{
protected $provider;
protected function setUp()
{
parent::setUp();
$this->provider = $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface');
}
protected function tearDown()
{
parent::tearDown();
$this->provider = null;
}
protected function getNonRootForm()
{
$form = $this->getMock('Symfony\Component\Form\Tests\FormInterface');
$form->expects($this->any())
->method('isRoot')
->will($this->returnValue(false));
return $form;
}
public function testGenerateCsrfToken()
{
$this->provider->expects($this->once())
->method('generateCsrfToken')
->with('%INTENTION%')
->will($this->returnValue('token'));
$form = $this->factory->create('csrf', null, array(
'csrf_provider' => $this->provider,
'intention' => '%INTENTION%'
));
$this->assertEquals('token', $form->getData());
}
public function testValidateTokenOnBind()
{
$this->provider->expects($this->once())
->method('isCsrfTokenValid')
->with('%INTENTION%', 'token')
->will($this->returnValue(true));
$form = $this->factory->create('csrf', null, array(
'csrf_provider' => $this->provider,
'intention' => '%INTENTION%'
));
$form->bind('token');
$this->assertEquals('token', $form->getData());
}
public function testDontValidateTokenIfParentIsNotRoot()
{
$this->provider->expects($this->never())
->method('isCsrfTokenValid');
$form = $this->factory->create('csrf', null, array(
'csrf_provider' => $this->provider,
'intention' => '%INTENTION%'
));
$form->setParent($this->getNonRootForm());
$form->bind('token');
}
public function testCsrfTokenIsRegeneratedIfValidationFails()
{
$this->provider->expects($this->at(0))
->method('generateCsrfToken')
->with('%INTENTION%')
->will($this->returnValue('token1'));
$this->provider->expects($this->at(1))
->method('isCsrfTokenValid')
->with('%INTENTION%', 'invalid')
->will($this->returnValue(false));
// The token is regenerated to avoid stalled tokens, for example when
// the session ID changed
$this->provider->expects($this->at(2))
->method('generateCsrfToken')
->with('%INTENTION%')
->will($this->returnValue('token2'));
$form = $this->factory->create('csrf', null, array(
'csrf_provider' => $this->provider,
'intention' => '%INTENTION%'
));
$form->bind('invalid');
$this->assertEquals('token2', $form->getData());
}
}

View File

@ -11,43 +11,188 @@
namespace Symfony\Component\Form\Tests\Extension\Csrf\Type; namespace Symfony\Component\Form\Tests\Extension\Csrf\Type;
use Symfony\Component\Form\Extension\Csrf\CsrfExtension;
use Symfony\Component\Form\Tests\Extension\Core\Type\TypeTestCase;
class FormTypeCsrfExtensionTest extends TypeTestCase class FormTypeCsrfExtensionTest extends TypeTestCase
{ {
public function testCsrfProtectionByDefault() protected $csrfProvider;
{
$form = $this->factory->create('form', null, array(
'csrf_field_name' => 'csrf',
));
$this->assertTrue($form->has('csrf')); protected function setUp()
{
$this->csrfProvider = $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface');
parent::setUp();
}
protected function tearDown()
{
$this->csrfProvider = null;
parent::tearDown();
}
protected function getExtensions()
{
return array_merge(parent::getExtensions(), array(
new CsrfExtension($this->csrfProvider),
));
}
public function testCsrfProtectionByDefaultIfRootAndChildren()
{
$view = $this->factory
->createBuilder('form', null, array(
'csrf_field_name' => 'csrf',
))
->add($this->factory->createNamedBuilder('form', 'child'))
->getForm()
->createView();
$this->assertTrue($view->hasChild('csrf'));
}
public function testNoCsrfProtectionByDefaultIfChildrenButNotRoot()
{
$view = $this->factory
->createNamedBuilder('form', 'root')
->add($this->factory
->createNamedBuilder('form', 'form', null, array(
'csrf_field_name' => 'csrf',
))
->add($this->factory->createNamedBuilder('form', 'child'))
)
->getForm()
->get('form')
->createView();
$this->assertFalse($view->hasChild('csrf'));
}
public function testNoCsrfProtectionByDefaultIfRootButNoChildren()
{
$view = $this->factory
->createBuilder('form', null, array(
'csrf_field_name' => 'csrf',
))
->getForm()
->createView();
$this->assertFalse($view->hasChild('csrf'));
} }
public function testCsrfProtectionCanBeDisabled() public function testCsrfProtectionCanBeDisabled()
{ {
$form = $this->factory->create('form', null, array( $view = $this->factory
'csrf_protection' => false, ->createBuilder('form', null, array(
));
$this->assertCount(0, $form);
}
public function testCsrfTokenIsOnlyIncludedInRootView()
{
$view =
$this->factory->createBuilder('form', null, array(
'csrf_field_name' => 'csrf', 'csrf_field_name' => 'csrf',
'csrf_protection' => false,
)) ))
->add('notCsrf', 'text') ->add($this->factory->createNamedBuilder('form', 'child'))
->add(
$this->factory->createNamedBuilder('form', 'child', null, array(
'csrf_field_name' => 'csrf',
))
->add('notCsrf', 'text')
)
->getForm() ->getForm()
->createView(); ->createView();
$this->assertEquals(array('csrf', 'notCsrf', 'child'), array_keys(iterator_to_array($view))); $this->assertFalse($view->hasChild('csrf'));
$this->assertEquals(array('notCsrf'), array_keys(iterator_to_array($view['child']))); }
public function testGenerateCsrfToken()
{
$this->csrfProvider->expects($this->once())
->method('generateCsrfToken')
->with('%INTENTION%')
->will($this->returnValue('token'));
$view = $this->factory
->createBuilder('form', null, array(
'csrf_field_name' => 'csrf',
'csrf_provider' => $this->csrfProvider,
'intention' => '%INTENTION%'
))
->add($this->factory->createNamedBuilder('form', 'child'))
->getForm()
->createView();
$this->assertEquals('token', $view->getChild('csrf')->get('value'));
}
public function provideBoolean()
{
return array(
array(true),
array(false),
);
}
/**
* @dataProvider provideBoolean
*/
public function testValidateTokenOnBindIfRootAndChildren($valid)
{
$this->csrfProvider->expects($this->once())
->method('isCsrfTokenValid')
->with('%INTENTION%', 'token')
->will($this->returnValue($valid));
$form = $this->factory
->createBuilder('form', null, array(
'csrf_field_name' => 'csrf',
'csrf_provider' => $this->csrfProvider,
'intention' => '%INTENTION%'
))
->add($this->factory->createNamedBuilder('form', 'child'))
->getForm();
$form->bind(array(
'child' => 'foobar',
'csrf' => 'token',
));
// Remove token from data
$this->assertSame(array('child' => 'foobar'), $form->getData());
// Validate accordingly
$this->assertSame($valid, $form->isValid());
}
public function testDontValidateTokenIfChildrenButNoRoot()
{
$this->csrfProvider->expects($this->never())
->method('isCsrfTokenValid');
$form = $this->factory
->createNamedBuilder('form', 'root')
->add($this->factory
->createNamedBuilder('form', 'form', null, array(
'csrf_field_name' => 'csrf',
'csrf_provider' => $this->csrfProvider,
'intention' => '%INTENTION%'
))
->add($this->factory->createNamedBuilder('form', 'child'))
)
->getForm()
->get('form');
$form->bind(array(
'child' => 'foobar',
'csrf' => 'token',
));
}
public function testDontValidateTokenIfRootButNoChildren()
{
$this->csrfProvider->expects($this->never())
->method('isCsrfTokenValid');
$form = $this->factory
->createBuilder('form', null, array(
'csrf_field_name' => 'csrf',
'csrf_provider' => $this->csrfProvider,
'intention' => '%INTENTION%'
))
->getForm();
$form->bind(array(
'csrf' => 'token',
));
} }
} }

View File

@ -1,41 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Tests\Extension\Csrf\Type;
use Symfony\Component\Form\Tests\Extension\Core\Type\TypeTestCase as BaseTestCase;
use Symfony\Component\Form\Extension\Csrf\CsrfExtension;
abstract class TypeTestCase extends BaseTestCase
{
protected $csrfProvider;
protected function setUp()
{
$this->csrfProvider = $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface');
parent::setUp();
}
protected function tearDown()
{
$this->csrfProvider = null;
parent::tearDown();
}
protected function getExtensions()
{
return array_merge(parent::getExtensions(), array(
new CsrfExtension($this->csrfProvider),
));
}
}

View File

@ -93,6 +93,7 @@ class DelegatingValidationListenerTest extends \PHPUnit_Framework_TestCase
$builder = new FormBuilder($name, $this->factory, $this->dispatcher); $builder = new FormBuilder($name, $this->factory, $this->dispatcher);
$builder->setAttribute('property_path', new PropertyPath($propertyPath ?: $name)); $builder->setAttribute('property_path', new PropertyPath($propertyPath ?: $name));
$builder->setAttribute('error_mapping', array()); $builder->setAttribute('error_mapping', array());
$builder->setErrorBubbling(false);
return $builder; return $builder;
} }

View File

@ -13,18 +13,18 @@ namespace Symfony\Component\Form\Tests\Extension\Validator\Type;
use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormInterface;
class FieldTypeValidatorExtensionTest extends TypeTestCase class FormTypeValidatorExtensionTest extends TypeTestCase
{ {
public function testValidationGroupNullByDefault() public function testValidationGroupNullByDefault()
{ {
$form = $this->factory->create('field'); $form = $this->factory->create('form');
$this->assertNull($form->getAttribute('validation_groups')); $this->assertNull($form->getAttribute('validation_groups'));
} }
public function testValidationGroupsCanBeSetToString() public function testValidationGroupsCanBeSetToString()
{ {
$form = $this->factory->create('field', null, array( $form = $this->factory->create('form', null, array(
'validation_groups' => 'group', 'validation_groups' => 'group',
)); ));
@ -33,7 +33,7 @@ class FieldTypeValidatorExtensionTest extends TypeTestCase
public function testValidationGroupsCanBeSetToArray() public function testValidationGroupsCanBeSetToArray()
{ {
$form = $this->factory->create('field', null, array( $form = $this->factory->create('form', null, array(
'validation_groups' => array('group1', 'group2'), 'validation_groups' => array('group1', 'group2'),
)); ));
@ -42,7 +42,7 @@ class FieldTypeValidatorExtensionTest extends TypeTestCase
public function testValidationGroupsCanBeSetToCallback() public function testValidationGroupsCanBeSetToCallback()
{ {
$form = $this->factory->create('field', null, array( $form = $this->factory->create('form', null, array(
'validation_groups' => array($this, 'testValidationGroupsCanBeSetToCallback'), 'validation_groups' => array($this, 'testValidationGroupsCanBeSetToCallback'),
)); ));
@ -51,7 +51,7 @@ class FieldTypeValidatorExtensionTest extends TypeTestCase
public function testValidationGroupsCanBeSetToClosure() public function testValidationGroupsCanBeSetToClosure()
{ {
$form = $this->factory->create('field', null, array( $form = $this->factory->create('form', null, array(
'validation_groups' => function(FormInterface $form){ return null; }, 'validation_groups' => function(FormInterface $form){ return null; },
)); ));
@ -60,10 +60,10 @@ class FieldTypeValidatorExtensionTest extends TypeTestCase
public function testBindValidatesData() public function testBindValidatesData()
{ {
$builder = $this->factory->createBuilder('field', null, array( $builder = $this->factory->createBuilder('form', null, array(
'validation_groups' => 'group', 'validation_groups' => 'group',
)); ));
$builder->add('firstName', 'field'); $builder->add('firstName', 'form');
$form = $builder->getForm(); $form = $builder->getForm();
$this->validator->expects($this->once()) $this->validator->expects($this->once())

View File

@ -71,7 +71,7 @@ class FormBuilderTest extends \PHPUnit_Framework_TestCase
* Changing the name is not allowed, otherwise the name and property path * Changing the name is not allowed, otherwise the name and property path
* are not synchronized anymore * are not synchronized anymore
* *
* @see FieldType::buildForm * @see FormType::buildForm
*/ */
public function testNoSetName() public function testNoSetName()
{ {

View File

@ -346,7 +346,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('foo', $builder->getName()); $this->assertEquals('foo', $builder->getName());
} }
public function testCreateBuilderForPropertyCreatesFieldWithHighestConfidence() public function testCreateBuilderForPropertyCreatesFormWithHighestConfidence()
{ {
$this->guesser1->expects($this->once()) $this->guesser1->expects($this->once())
->method('guessType') ->method('guessType')
@ -378,7 +378,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('builderInstance', $builder); $this->assertEquals('builderInstance', $builder);
} }
public function testCreateBuilderCreatesTextFieldIfNoGuess() public function testCreateBuilderCreatesTextFormIfNoGuess()
{ {
$this->guesser1->expects($this->once()) $this->guesser1->expects($this->once())
->method('guessType') ->method('guessType')
@ -541,7 +541,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$factory->createNamedBuilder($type, "text", "value", array("unknown" => "opt")); $factory->createNamedBuilder($type, "text", "value", array("unknown" => "opt"));
} }
public function testFieldTypeCreatesDefaultValueForEmptyDataOption() public function testFormTypeCreatesDefaultValueForEmptyDataOption()
{ {
$factory = new FormFactory(array(new \Symfony\Component\Form\Extension\Core\CoreExtension())); $factory = new FormFactory(array(new \Symfony\Component\Form\Extension\Core\CoreExtension()));

View File

@ -159,6 +159,35 @@ class FormTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array(), $parent->getErrors()); $this->assertEquals(array(), $parent->getErrors());
} }
public function testErrorsBubbleUpIfNullAndChildren()
{
$error = new FormError('Error!');
$parent = $this->form;
$form = $this->getBuilder()
->setErrorBubbling(null)
->add($this->getBuilder('child'))
->getForm();
$form->setParent($parent);
$form->addError($error);
$this->assertEquals(array(), $form->getErrors());
$this->assertEquals(array($error), $parent->getErrors());
}
public function testErrorsDontBubbleUpIfNullAndNoChildren()
{
$error = new FormError('Error!');
$parent = $this->form;
$form = $this->getBuilder()->setErrorBubbling(null)->getForm();
$form->setParent($parent);
$form->addError($error);
$this->assertEquals(array($error), $form->getErrors());
$this->assertEquals(array(), $parent->getErrors());
}
public function testValidIfAllChildrenAreValid() public function testValidIfAllChildrenAreValid()
{ {
$this->form->add($this->getValidForm('firstName')); $this->form->add($this->getValidForm('firstName'));
@ -1026,7 +1055,7 @@ class FormTest extends \PHPUnit_Framework_TestCase
/** /**
* @dataProvider requestMethodProvider * @dataProvider requestMethodProvider
*/ */
public function testBindPostOrPutRequestWithSingleFieldForm($method) public function testBindPostOrPutRequestWithSingleChildForm($method)
{ {
if (!class_exists('Symfony\Component\HttpFoundation\Request')) { if (!class_exists('Symfony\Component\HttpFoundation\Request')) {
$this->markTestSkipped('The "HttpFoundation" component is not available'); $this->markTestSkipped('The "HttpFoundation" component is not available');
@ -1063,7 +1092,7 @@ class FormTest extends \PHPUnit_Framework_TestCase
/** /**
* @dataProvider requestMethodProvider * @dataProvider requestMethodProvider
*/ */
public function testBindPostOrPutRequestWithSingleFieldFormUploadedFile($method) public function testBindPostOrPutRequestWithSingleChildFormUploadedFile($method)
{ {
if (!class_exists('Symfony\Component\HttpFoundation\Request')) { if (!class_exists('Symfony\Component\HttpFoundation\Request')) {
$this->markTestSkipped('The "HttpFoundation" component is not available'); $this->markTestSkipped('The "HttpFoundation" component is not available');
@ -1247,7 +1276,7 @@ class FormTest extends \PHPUnit_Framework_TestCase
public function testCreateViewAcceptsParent() public function testCreateViewAcceptsParent()
{ {
$parent = new FormView(); $parent = new FormView('form');
$form = $this->getBuilder()->getForm(); $form = $this->getBuilder()->getForm();
$view = $form->createView($parent); $view = $form->createView($parent);