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.
* [BC BREAK] FormType::getDefaultOptions() and FormType::getAllowedOptionValues()
don't receive an options array anymore.
* Deprecated FormValidatorInterface and substituted its implementations
* deprecated FormValidatorInterface and substituted its implementations
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

View File

@ -358,6 +358,13 @@
(or any other of the BIND events). In case you used the CallbackValidator
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
* The methods `setMessage()`, `getMessageTemplate()` and

View File

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

View File

@ -1,3 +1,3 @@
{% block field_label %}
{% block form_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>
{% endblock field_label %}
{% endblock form_label %}

View File

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

View File

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

View File

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

View File

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

View File

@ -14,12 +14,9 @@
<argument>%kernel.secret%</argument>
</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">
<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.field_name%</argument>
</service>

View File

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

View File

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

View File

@ -1,2 +1 @@
<?php if ($required) { $attr['class'] = (isset($attr['class']) ? $attr['class'] : '').' required'; } ?>
<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>
<?php echo $view['form']->renderBlock('form_label') ?>

View File

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

View File

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

View File

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

View File

@ -1,5 +1 @@
<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') ?>
/>
<?php echo $view['form']->renderBlock('input') ?>

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 (!$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>

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>
<?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) ?>
</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') ?>>
<?php echo $view['form']->renderBlock('field_rows') ?>
<?php echo $view['form']->renderBlock('form_rows') ?>
<?php echo $view['form']->rest($form) ?>
</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 echo $view['form']->renderBlock('field_widget'); ?>
<?php echo $view['form']->renderBlock('input'); ?>
<?php else: ?>
<div <?php echo $view['form']->renderBlock('container_attributes') ?>>
<?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)) : ?>
<tr>
<td colspan="2">
<?php echo $view['form']->renderBlock('field_errors'); ?>
</td>
</tr>
<?php endif; ?>
<?php if ($form->hasChildren()): ?>
<?php if (count($errors) > 0): ?>
<tr>
<td colspan="2">
<?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 ?>
</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) ?>
</td>
<td>
<?php if (!$form->hasChildren()): ?>
<?php echo $view['form']->errors($form) ?>
<?php endif ?>
<?php echo $view['form']->widget($form) ?>
</td>
</tr>

View File

@ -1,5 +1,8 @@
<?php if ($form->hasChildren()): ?>
<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) ?>
</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())
{
return trim($this->renderSection($view, 'widget', $variables));
return $this->renderSection($view, 'widget', $variables);
}
/**
@ -276,7 +276,7 @@ class FormHelper extends Helper
$view->setRendered();
}
return $html;
return trim($html);
}
} while (--$typeIndex >= 0);
@ -311,7 +311,7 @@ class FormHelper extends Helper
$variables = array_replace_recursive($context['variables'], $variables);
return $this->engine->render($template, $variables);
return trim($this->engine->render($template, $variables));
}
public function getName()

View File

@ -27,9 +27,9 @@ abstract class FrameworkExtensionTest extends TestCase
$def = $container->getDefinition('form.type_extension.csrf');
$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('%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)));
}

View File

@ -24,7 +24,7 @@ interface DataTransformerInterface
* 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).
* 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
* 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).
@ -52,7 +52,7 @@ interface DataTransformerInterface
* Transforms a value from the transformed representation to its original
* 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.
*
* 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']) {
$this->addSubFields($builder, $options['choice_list']->getPreferredViews(), $options);
$this->addSubFields($builder, $options['choice_list']->getRemainingViews(), $options);
$this->addSubForms($builder, $options['choice_list']->getPreferredViews(), $options);
$this->addSubForms($builder, $options['choice_list']->getRemainingViews(), $options);
}
// empty value
@ -182,7 +182,7 @@ class ChoiceType extends AbstractType
*/
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 $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) {
if (is_array($choiceView)) {
// Flatten groups
$this->addSubFields($builder, $choiceView, $options);
$this->addSubForms($builder, $choiceView, $options);
} else {
$choiceOpts = array(
'value' => $choiceView->getValue(),

View File

@ -153,7 +153,7 @@ class DateTimeType extends AbstractType
'widget' => null,
// This will overwrite "empty_value" child options
'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
// representation is not \DateTime, but an array, we need to unset
// this option.
@ -200,7 +200,7 @@ class DateTimeType extends AbstractType
*/
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
'by_reference' => 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
// representation is not \DateTime, but an array, we need to unset
// this option.
@ -210,7 +210,7 @@ class DateType extends AbstractType
*/
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\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
{
/**
* {@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}
*/
@ -203,9 +39,4 @@ class FieldType extends AbstractType
{
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)
{
$view
->set('multipart', true)
->set('type', 'file')
->set('value', '')
;
}
/**
* {@inheritdoc}
*/
public function buildViewBottomUp(FormView $view, FormInterface $form)
{
$view
->set('multipart', true)
;
}
/**
* {@inheritdoc}
*/
public function getParent(array $options)
{
return 'field';

View File

@ -13,10 +13,16 @@ namespace Symfony\Component\Form\Extension\Core\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Options;
use Symfony\Component\Form\Util\PropertyPath;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormFactoryInterface;
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\EventDispatcher\EventDispatcher;
use Symfony\Component\Form\Exception\FormException;
class FormType extends AbstractType
{
@ -25,9 +31,102 @@ class FormType extends AbstractType
*/
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'])
->setAttribute('virtual', $options['virtual'])
->setData($options['data'])
->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()
{
$emptyData = function (Options $options, $currentValue) {
if (empty($options['data_class'])) {
return array();
// Derive "data_class" option from passed "data" object
$dataClass = function (Options $options) {
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(
'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(),
'virtual' => false,
// Errors in forms bubble by default, so that form errors will
// end up as global errors in the root form
'error_bubbling' => true,
'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 'field';
return null;
}
/**
@ -85,4 +230,9 @@ class FormType extends AbstractType
{
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
'by_reference' => 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
// representation is not \DateTime, but an array, we need to unset
// this option.
@ -183,7 +183,7 @@ class TimeType extends AbstractType
*/
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;
}
/**
* {@inheritDoc}
*/
protected function loadTypes()
{
return array(
new Type\CsrfType($this->csrfProvider),
);
}
/**
* {@inheritDoc}
*/
protected function loadTypeExtensions()
{
return array(
new Type\ChoiceTypeCsrfExtension(),
new Type\DateTypeCsrfExtension(),
new Type\FormTypeCsrfExtension(),
new Type\RepeatedTypeCsrfExtension(),
new Type\TimeTypeCsrfExtension(),
new Type\FormTypeCsrfExtension($this->csrfProvider),
);
}
}

View File

@ -14,7 +14,7 @@ namespace Symfony\Component\Form\Extension\Csrf\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvents;
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;
/**
@ -22,6 +22,12 @@ use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
*/
class CsrfValidationListener implements EventSubscriberInterface
{
/**
* The name of the CSRF field
* @var string
*/
private $fieldName;
/**
* The provider for generating and validating CSRF tokens
* @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->intention = $intention;
}
public function onBindClientData(DataEvent $event)
public function onBindClientData(FilterDataEvent $event)
{
$form = $event->getForm();
$data = $event->getData();
if ((!$form->hasParent() || $form->getParent()->isRoot())
&& !$this->csrfProvider->isCsrfTokenValid($this->intention, $data)) {
$form->addError(new FormError('The CSRF token is invalid. Please try to resubmit the form'));
if ($form->isRoot() && $form->hasChildren() && isset($data[$this->fieldName])) {
if (!$this->csrfProvider->isCsrfTokenValid($this->intention, $data[$this->fieldName])) {
$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.
// Regenerate the token so that a resubmission is possible.
$event->setData($this->csrfProvider->generateCsrfToken($this->intention));
unset($data[$this->fieldName]);
}
$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;
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\FormView;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class FormTypeCsrfExtension extends AbstractTypeExtension
{
private $enabled;
private $fieldName;
private $defaultCsrfProvider;
private $defaultEnabled;
private $defaultFieldName;
public function __construct($enabled = true, $fieldName = '_token')
public function __construct(CsrfProviderInterface $defaultCsrfProvider, $defaultEnabled = true, $defaultFieldName = '_token')
{
$this->enabled = $enabled;
$this->fieldName = $fieldName;
$this->defaultCsrfProvider = $defaultCsrfProvider;
$this->defaultEnabled = $defaultEnabled;
$this->defaultFieldName = $defaultFieldName;
}
/**
@ -41,35 +47,35 @@ class FormTypeCsrfExtension extends AbstractTypeExtension
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
$builder
->setAttribute('csrf_field_name', $options['csrf_field_name'])
->addEventListener(FormEvents::PRE_SET_DATA, array($listener, 'ensureCsrfField'), -10)
->addEventListener(FormEvents::PRE_BIND, array($listener, 'ensureCsrfField'), -10)
->setAttribute('csrf_provider', $options['csrf_provider'])
->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 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');
$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])) {
unset($view[$name]);
}
$view->addChild($csrfForm->createView($view));
}
}
@ -79,9 +85,9 @@ class FormTypeCsrfExtension extends AbstractTypeExtension
public function getDefaultOptions()
{
return array(
'csrf_protection' => $this->enabled,
'csrf_field_name' => $this->fieldName,
'csrf_provider' => null,
'csrf_protection' => $this->defaultEnabled,
'csrf_field_name' => $this->defaultFieldName,
'csrf_provider' => $this->defaultCsrfProvider,
'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>
*/
class FieldTypeValidatorExtension extends AbstractTypeExtension
class FormTypeValidatorExtension extends AbstractTypeExtension
{
private $validator;
@ -57,6 +57,6 @@ class FieldTypeValidatorExtension extends AbstractTypeExtension
public function getExtendedType()
{
return 'field';
return 'form';
}
}

View File

@ -38,7 +38,7 @@ class ValidatorExtension extends AbstractExtension
protected function loadTypeExtensions()
{
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 $normTransformers = array(),
DataMapperInterface $dataMapper = null, array $validators = array(),
$required = false, $disabled = false, $errorBubbling = false,
$required = false, $disabled = false, $errorBubbling = null,
$emptyData = null, array $attributes = array())
{
$name = (string) $name;
@ -225,7 +225,10 @@ class Form implements \IteratorAggregate, FormInterface
$this->validators = $validators;
$this->required = (Boolean) $required;
$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->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()
{
@ -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
*/
@ -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
*
@ -408,7 +411,7 @@ class Form implements \IteratorAggregate, FormInterface
$this->clientData = $clientData;
$this->synchronized = true;
if ($this->dataMapper) {
if (count($this->children) > 0 && $this->dataMapper) {
// Update child forms from the data
$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
*
@ -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.
* When the field is bound, the normalized bound data is
* returned if the field is valid, null otherwise.
* @return mixed When the form is not bound, the default data is returned.
* When the form is bound, the normalized bound data is
* returned if the form is valid, null otherwise.
*/
public function getNormData()
{
@ -646,7 +649,7 @@ class Form implements \IteratorAggregate, FormInterface
*/
public function addError(FormError $error)
{
if ($this->parent && $this->errorBubbling) {
if ($this->parent && $this->getErrorBubbling()) {
$this->parent->addError($error);
} else {
$this->errors[] = $error;
@ -662,11 +665,11 @@ class Form implements \IteratorAggregate, FormInterface
*/
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
*/
@ -702,7 +705,7 @@ class Form implements \IteratorAggregate, FormInterface
}
/**
* Returns whether the field is valid.
* Returns whether the form is valid.
*
* @return Boolean
*/
@ -735,9 +738,8 @@ class Form implements \IteratorAggregate, FormInterface
public function hasErrors()
{
// Don't call isValid() here, as its semantics are slightly different
// Field groups are not valid if their children are invalid, but
// hasErrors() returns only true if a field/field group itself has
// errors
// Forms are not valid if their children are invalid, but
// hasErrors() returns only true if a form itself has errors
return count($this->errors) > 0;
}
@ -894,7 +896,7 @@ class Form implements \IteratorAggregate, FormInterface
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();
}
$view = new FormView();
$view = new FormView($this->name);
$view->setParent($parent);
@ -989,14 +991,10 @@ class Form implements \IteratorAggregate, FormInterface
}
}
$childViews = array();
foreach ($this->children as $key => $child) {
$childViews[$key] = $child->createView($view);
foreach ($this->children as $child) {
$view->addChild($child->createView($view));
}
$view->setChildren($childViews);
foreach ($types as $type) {
$type->buildViewBottomUp($view, $this);

View File

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

View File

@ -75,7 +75,7 @@ class FormTypeGuesserChain implements FormTypeGuesserInterface
* @param \Closure $closure The closure to execute. Accepts a guesser
* 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)
{

View File

@ -11,8 +11,17 @@
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(
'value' => null,
'attr' => array(),
@ -33,6 +42,16 @@ class FormView implements \ArrayAccess, \IteratorAggregate, \Countable
*/
private $rendered = false;
public function __construct($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
/**
* @param string $name
* @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
*/
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;
}
@ -222,6 +255,18 @@ class FormView implements \ArrayAccess, \IteratorAggregate, \Countable
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).
*

View File

@ -202,7 +202,7 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest
);
}
public function testRestAndRepeatedWithRowPerField()
public function testRestAndRepeatedWithRowPerChild()
{
$view = $this->factory->createNamedBuilder('form', 'name')
->add('first', 'text')
@ -230,7 +230,7 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest
);
}
public function testRestAndRepeatedWithWidgetPerField()
public function testRestAndRepeatedWithWidgetPerChild()
{
$view = $this->factory->createNamedBuilder('form', 'name')
->add('first', 'text')
@ -348,7 +348,10 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest
public function testNestedFormError()
{
$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();
$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()
{
$form = $this->factory->createNamed('repeated', 'name', 'foobar', array(
@ -372,7 +400,8 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./div
./input[@type="hidden"][@id="name__token"]
/following-sibling::div
[
./label[@for="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"]
]
]
[count(.//input)=2]
[count(.//input)=3]
'
);
}
@ -399,7 +428,8 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./div
./input[@type="hidden"][@id="name__token"]
/following-sibling::div
[
./label[@for="name_first"][.="[trans]Test[/trans]"]
/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"]
]
]
[count(.//input)=2]
[count(.//input)=3]
'
);
}

View File

@ -646,12 +646,13 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/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::input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)]
/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(),
'/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::input[@type="radio"][@name="name"][@id="name_1"][not(@checked)]
/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(),
'/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::input[@type="radio"][@name="name"][@id="name_1"][not(@checked)]
/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(),
'/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::input[@type="checkbox"][@name="name[]"][@id="name_1"][not(@checked)][not(@required)]
/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::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()
{
$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());
$this->assertMatchesXpath($html,
'/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)=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]"]]
'/tr[@style="display: none"]
[./td[@colspan="2"]/input
[@type="hidden"]
[@id="name__token"]
]
/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()
{
$view = $this->factory->createNamedBuilder('form', 'name')
@ -151,9 +161,9 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/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="hidden"][@id="name__token"]]
]
[count(./tr[./td/input])=3]
'
@ -200,7 +210,10 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
public function testNestedFormError()
{
$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();
$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()
{
$form = $this->factory->createNamed('repeated', 'name', 'foobar', array(
@ -226,7 +267,12 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/table
[
./tr
./tr[@style="display: none"]
[./td[@colspan="2"]/input
[@type="hidden"]
[@id="name__token"]
]
/following-sibling::tr
[
./td
[./label[@for="name_first"]]
@ -241,7 +287,7 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
[./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(),
'/table
[
./tr
./tr[@style="display: none"]
[./td[@colspan="2"]/input
[@type="hidden"]
[@id="name__token"]
]
/following-sibling::tr
[
./td
[./label[@for="name_first"][.="[trans]Test[/trans]"]]
@ -272,7 +323,7 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
[./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(
'expanded' => true,
@ -141,7 +141,7 @@ class ChoiceTypeTest extends TypeTestCase
}
}
public function testExpandedRadiosAreRequiredIfChoiceFieldIsRequired()
public function testExpandedRadiosAreRequiredIfChoiceChildIsRequired()
{
$form = $this->factory->create('choice', null, array(
'multiple' => false,
@ -155,7 +155,7 @@ class ChoiceTypeTest extends TypeTestCase
}
}
public function testExpandedRadiosAreNotRequiredIfChoiceFieldIsNotRequired()
public function testExpandedRadiosAreNotRequiredIfChoiceChildIsNotRequired()
{
$form = $this->factory->create('choice', null, array(
'multiple' => false,
@ -288,7 +288,7 @@ class ChoiceTypeTest extends TypeTestCase
$this->assertNull($form[4]->getClientData());
}
public function testBindSingleExpandedWithFalseDoesNotHaveExtraFields()
public function testBindSingleExpandedWithFalseDoesNotHaveExtraChildren()
{
$form = $this->factory->create('choice', null, array(
'multiple' => false,
@ -302,7 +302,7 @@ class ChoiceTypeTest extends TypeTestCase
$this->assertNull($form->getData());
}
public function testBindSingleExpandedWithEmptyField()
public function testBindSingleExpandedWithEmptyChild()
{
$form = $this->factory->create('choice', null, array(
'multiple' => false,
@ -422,7 +422,7 @@ class ChoiceTypeTest extends TypeTestCase
$this->assertNull($form[4]->getClientData());
}
public function testBindMultipleExpandedWithEmptyField()
public function testBindMultipleExpandedWithEmptyChild()
{
$form = $this->factory->create('choice', null, array(
'multiple' => true,

View File

@ -15,10 +15,10 @@ use Symfony\Component\Form\Form;
class CollectionTypeTest extends TypeTestCase
{
public function testContainsNoFieldByDefault()
public function testContainsNoChildByDefault()
{
$form = $this->factory->create('collection', null, array(
'type' => 'field',
'type' => 'form',
));
$this->assertCount(0, $form);
@ -27,7 +27,7 @@ class CollectionTypeTest extends TypeTestCase
public function testSetDataAdjustsSize()
{
$form = $this->factory->create('collection', null, array(
'type' => 'field',
'type' => 'form',
'options' => array(
'max_length' => 20,
),
@ -53,7 +53,7 @@ class CollectionTypeTest extends TypeTestCase
public function testThrowsExceptionIfObjectIsNotTraversable()
{
$form = $this->factory->create('collection', null, array(
'type' => 'field',
'type' => 'form',
));
$this->setExpectedException('Symfony\Component\Form\Exception\UnexpectedTypeException');
$form->setData(new \stdClass());
@ -62,7 +62,7 @@ class CollectionTypeTest extends TypeTestCase
public function testNotResizedIfBoundWithMissingData()
{
$form = $this->factory->create('collection', null, array(
'type' => 'field',
'type' => 'form',
));
$form->setData(array('foo@foo.com', 'bar@bar.com'));
$form->bind(array('foo@bar.com'));
@ -76,7 +76,7 @@ class CollectionTypeTest extends TypeTestCase
public function testResizedDownIfBoundWithMissingDataAndAllowDelete()
{
$form = $this->factory->create('collection', null, array(
'type' => 'field',
'type' => 'form',
'allow_delete' => true,
));
$form->setData(array('foo@foo.com', 'bar@bar.com'));
@ -91,7 +91,7 @@ class CollectionTypeTest extends TypeTestCase
public function testNotResizedIfBoundWithExtraData()
{
$form = $this->factory->create('collection', null, array(
'type' => 'field',
'type' => 'form',
));
$form->setData(array('foo@bar.com'));
$form->bind(array('foo@foo.com', 'bar@bar.com'));
@ -104,7 +104,7 @@ class CollectionTypeTest extends TypeTestCase
public function testResizedUpIfBoundWithExtraDataAndAllowAdd()
{
$form = $this->factory->create('collection', null, array(
'type' => 'field',
'type' => 'form',
'allow_add' => true,
));
$form->setData(array('foo@bar.com'));
@ -120,7 +120,7 @@ class CollectionTypeTest extends TypeTestCase
public function testAllowAddButNoPrototype()
{
$form = $this->factory->create('collection', null, array(
'type' => 'field',
'type' => 'form',
'allow_add' => true,
'prototype' => false,
));
@ -169,7 +169,7 @@ class CollectionTypeTest extends TypeTestCase
public function testPrototypeNameOption()
{
$form = $this->factory->create('collection', null, array(
'type' => 'field',
'type' => 'form',
'prototype' => true,
'allow_add' => true,
));
@ -177,7 +177,7 @@ class CollectionTypeTest extends TypeTestCase
$this->assertSame('__name__', $form->getAttribute('prototype')->getName(), '__name__ is the default');
$form = $this->factory->create('collection', null, array(
'type' => 'field',
'type' => 'form',
'prototype' => true,
'allow_add' => true,
'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;
use Symfony\Component\Form\Util\PropertyPath;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\Tests\Fixtures\Author;
use Symfony\Component\Form\Tests\Fixtures\FixedDataTransformer;
class FormTest_AuthorWithoutRefSetter
{
@ -49,13 +51,388 @@ class FormTest_AuthorWithoutRefSetter
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()
{
$author = new FormTest_AuthorWithoutRefSetter(new Author());
$builder = $this->factory->createBuilder('form');
$builder->add('reference', 'form');
$builder->get('reference')->add('firstName', 'field');
$builder->get('reference')->add('firstName', 'form');
$builder->setData($author);
$form = $builder->getForm();
@ -77,7 +454,7 @@ class FormTypeTest extends TypeTestCase
$builder = $this->factory->createBuilder('form');
$builder->add('referenceCopy', 'form');
$builder->get('referenceCopy')->add('firstName', 'field');
$builder->get('referenceCopy')->add('firstName', 'form');
$builder->setData($author);
$form = $builder->getForm();
@ -99,7 +476,7 @@ class FormTypeTest extends TypeTestCase
$builder = $this->factory->createBuilder('form');
$builder->add('referenceCopy', 'form', array('by_reference' => false));
$builder->get('referenceCopy')->add('firstName', 'field');
$builder->get('referenceCopy')->add('firstName', 'form');
$builder->setData($author);
$form = $builder->getForm();

View File

@ -21,7 +21,7 @@ class RepeatedTypeTest extends TypeTestCase
parent::setUp();
$this->form = $this->factory->create('repeated', null, array(
'type' => 'field',
'type' => 'form',
));
$this->form->setData(null);
}
@ -37,7 +37,7 @@ class RepeatedTypeTest extends TypeTestCase
public function testSetOptions()
{
$form = $this->factory->create('repeated', null, array(
'type' => 'field',
'type' => 'form',
'options' => array('label' => 'Global'),
));
@ -47,11 +47,11 @@ class RepeatedTypeTest extends TypeTestCase
$this->assertTrue($form['second']->isRequired());
}
public function testSetOptionsPerField()
public function testSetOptionsPerChild()
{
$form = $this->factory->create('repeated', null, array(
// the global required value cannot be overriden
'type' => 'field',
'type' => 'form',
'first_options' => array('label' => 'Test', 'required' => false),
'second_options' => array('label' => 'Test2')
));
@ -66,17 +66,17 @@ class RepeatedTypeTest extends TypeTestCase
{
$form = $this->factory->create('repeated', null, array(
'required' => false,
'type' => 'field',
'type' => 'form',
));
$this->assertFalse($form['first']->isRequired());
$this->assertFalse($form['second']->isRequired());
}
public function testSetOptionsPerFieldAndOverwrite()
public function testSetOptionsPerChildAndOverwrite()
{
$form = $this->factory->create('repeated', null, array(
'type' => 'field',
'type' => 'form',
'options' => array('label' => '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;
use Symfony\Component\Form\Extension\Csrf\CsrfExtension;
use Symfony\Component\Form\Tests\Extension\Core\Type\TypeTestCase;
class FormTypeCsrfExtensionTest extends TypeTestCase
{
public function testCsrfProtectionByDefault()
{
$form = $this->factory->create('form', null, array(
'csrf_field_name' => 'csrf',
));
protected $csrfProvider;
$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()
{
$form = $this->factory->create('form', null, array(
'csrf_protection' => false,
));
$this->assertCount(0, $form);
}
public function testCsrfTokenIsOnlyIncludedInRootView()
{
$view =
$this->factory->createBuilder('form', null, array(
$view = $this->factory
->createBuilder('form', null, array(
'csrf_field_name' => 'csrf',
'csrf_protection' => false,
))
->add('notCsrf', 'text')
->add(
$this->factory->createNamedBuilder('form', 'child', null, array(
'csrf_field_name' => 'csrf',
))
->add('notCsrf', 'text')
)
->add($this->factory->createNamedBuilder('form', 'child'))
->getForm()
->createView();
$this->assertEquals(array('csrf', 'notCsrf', 'child'), array_keys(iterator_to_array($view)));
$this->assertEquals(array('notCsrf'), array_keys(iterator_to_array($view['child'])));
$this->assertFalse($view->hasChild('csrf'));
}
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->setAttribute('property_path', new PropertyPath($propertyPath ?: $name));
$builder->setAttribute('error_mapping', array());
$builder->setErrorBubbling(false);
return $builder;
}

View File

@ -13,18 +13,18 @@ namespace Symfony\Component\Form\Tests\Extension\Validator\Type;
use Symfony\Component\Form\FormInterface;
class FieldTypeValidatorExtensionTest extends TypeTestCase
class FormTypeValidatorExtensionTest extends TypeTestCase
{
public function testValidationGroupNullByDefault()
{
$form = $this->factory->create('field');
$form = $this->factory->create('form');
$this->assertNull($form->getAttribute('validation_groups'));
}
public function testValidationGroupsCanBeSetToString()
{
$form = $this->factory->create('field', null, array(
$form = $this->factory->create('form', null, array(
'validation_groups' => 'group',
));
@ -33,7 +33,7 @@ class FieldTypeValidatorExtensionTest extends TypeTestCase
public function testValidationGroupsCanBeSetToArray()
{
$form = $this->factory->create('field', null, array(
$form = $this->factory->create('form', null, array(
'validation_groups' => array('group1', 'group2'),
));
@ -42,7 +42,7 @@ class FieldTypeValidatorExtensionTest extends TypeTestCase
public function testValidationGroupsCanBeSetToCallback()
{
$form = $this->factory->create('field', null, array(
$form = $this->factory->create('form', null, array(
'validation_groups' => array($this, 'testValidationGroupsCanBeSetToCallback'),
));
@ -51,7 +51,7 @@ class FieldTypeValidatorExtensionTest extends TypeTestCase
public function testValidationGroupsCanBeSetToClosure()
{
$form = $this->factory->create('field', null, array(
$form = $this->factory->create('form', null, array(
'validation_groups' => function(FormInterface $form){ return null; },
));
@ -60,10 +60,10 @@ class FieldTypeValidatorExtensionTest extends TypeTestCase
public function testBindValidatesData()
{
$builder = $this->factory->createBuilder('field', null, array(
$builder = $this->factory->createBuilder('form', null, array(
'validation_groups' => 'group',
));
$builder->add('firstName', 'field');
$builder->add('firstName', 'form');
$form = $builder->getForm();
$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
* are not synchronized anymore
*
* @see FieldType::buildForm
* @see FormType::buildForm
*/
public function testNoSetName()
{

View File

@ -346,7 +346,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('foo', $builder->getName());
}
public function testCreateBuilderForPropertyCreatesFieldWithHighestConfidence()
public function testCreateBuilderForPropertyCreatesFormWithHighestConfidence()
{
$this->guesser1->expects($this->once())
->method('guessType')
@ -378,7 +378,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('builderInstance', $builder);
}
public function testCreateBuilderCreatesTextFieldIfNoGuess()
public function testCreateBuilderCreatesTextFormIfNoGuess()
{
$this->guesser1->expects($this->once())
->method('guessType')
@ -541,7 +541,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$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()));

View File

@ -159,6 +159,35 @@ class FormTest extends \PHPUnit_Framework_TestCase
$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()
{
$this->form->add($this->getValidForm('firstName'));
@ -1026,7 +1055,7 @@ class FormTest extends \PHPUnit_Framework_TestCase
/**
* @dataProvider requestMethodProvider
*/
public function testBindPostOrPutRequestWithSingleFieldForm($method)
public function testBindPostOrPutRequestWithSingleChildForm($method)
{
if (!class_exists('Symfony\Component\HttpFoundation\Request')) {
$this->markTestSkipped('The "HttpFoundation" component is not available');
@ -1063,7 +1092,7 @@ class FormTest extends \PHPUnit_Framework_TestCase
/**
* @dataProvider requestMethodProvider
*/
public function testBindPostOrPutRequestWithSingleFieldFormUploadedFile($method)
public function testBindPostOrPutRequestWithSingleChildFormUploadedFile($method)
{
if (!class_exists('Symfony\Component\HttpFoundation\Request')) {
$this->markTestSkipped('The "HttpFoundation" component is not available');
@ -1247,7 +1276,7 @@ class FormTest extends \PHPUnit_Framework_TestCase
public function testCreateViewAcceptsParent()
{
$parent = new FormView();
$parent = new FormView('form');
$form = $this->getBuilder()->getForm();
$view = $form->createView($parent);