merged branch bschussek/issue5493 (PR #6522)

This PR was merged into the master branch.

Discussion
----------

[2.3] [Form] Implemented form processors

Bug fix: no
Feature addition: yes
Backwards compatibility break: no
Symfony2 tests pass: yes
Fixes the following tickets: partially #5493
Todo: -
License of the code: MIT
Documentation PR: symfony/symfony-docs#2092

Commits
-------

11fee06 [TwigBridge] Removed duplicate entries from the CHANGELOG
68f360c [Form] Moved upgrade nodes to UPGRADE-3.0
01b71a4 [Form] Removed trigger_error() for deprecations as of 3.0
81f8c67 [Form] Implemented form processors
0ea75db [Form] Improved FormRenderer::renderBlock() to be usable outside of form blocks
This commit is contained in:
Fabien Potencier 2013-04-19 08:01:34 +02:00
commit fcd941c033
42 changed files with 1968 additions and 55 deletions

View File

@ -1,4 +1,4 @@
UPGRADE FROM 2.2 to 2.3 UPGRADE FROM 2.2 to 2.3
======================= =======================
### Form ### Form

View File

@ -18,6 +18,97 @@ UPGRADE FROM 2.x to 3.0
`DebugClassLoader`. The difference is that the constructor now takes a `DebugClassLoader`. The difference is that the constructor now takes a
loader to wrap. loader to wrap.
### Form
* Passing a `Symfony\Component\HttpFoundation\Request` instance to
`FormInterface::bind()` was disabled. You should use
`FormInterface::process()` instead.
Before:
```
if ('POST' === $request->getMethod()) {
$form->bind($request);
if ($form->isValid()) {
// ...
}
}
```
After:
```
if ($form->process($request)->isValid()) {
// ...
}
```
If you want to test whether the form was submitted separately, you can use
the method `isBound()`:
```
if ($form->process($request)->isBound()) {
// ...
if ($form->isValid()) {
// ...
}
}
```
### FrameworkBundle
* The `enctype` method of the `form` helper was removed. You should use the
new method `start` instead.
Before:
```
<form method="post" action="http://example.com" <?php echo $view['form']->enctype($form) ?>>
...
</form>
```
After:
```
<?php echo $view['form']->start($form) ?>
...
<?php echo $view['form']->end($form) ?>
```
The method and action of the form default to "POST" and the current
document. If you want to change these values, you can set them explicitly in
the controller.
Alternative 1:
```
$form = $this->createForm('my_form', $formData, array(
'method' => 'PUT',
'action' => $this->generateUrl('target_route'),
));
```
Alternative 2:
```
$form = $this->createFormBuilder($formData)
// ...
->setMethod('PUT')
->setAction($this->generateUrl('target_route'))
->getForm();
```
It is also possible to override the method and the action in the template:
```
<?php echo $view['form']->start($form, array('method' => 'GET', 'action' => 'http://example.com')) ?>
...
<?php echo $view['form']->end($form) ?>
```
### HttpKernel ### HttpKernel
* The `Symfony\Component\HttpKernel\Log\LoggerInterface` has been removed in * The `Symfony\Component\HttpKernel\Log\LoggerInterface` has been removed in
@ -98,6 +189,56 @@ UPGRADE FROM 2.x to 3.0
* The `render` tag is deprecated in favor of the `render` function. * The `render` tag is deprecated in favor of the `render` function.
* The `form_enctype` helper was removed. You should use the new `form_start`
function instead.
Before:
```
<form method="post" action="http://example.com" {{ form_enctype(form) }}>
...
</form>
```
After:
```
{{ form_start(form) }}
...
{{ form_end(form) }}
```
The method and action of the form default to "POST" and the current
document. If you want to change these values, you can set them explicitly in
the controller.
Alternative 1:
```
$form = $this->createForm('my_form', $formData, array(
'method' => 'PUT',
'action' => $this->generateUrl('target_route'),
));
```
Alternative 2:
```
$form = $this->createFormBuilder($formData)
// ...
->setMethod('PUT')
->setAction($this->generateUrl('target_route'))
->getForm();
```
It is also possible to override the method and the action in the template:
```
{{ form_start(form, {'method': 'GET', 'action': 'http://example.com'}) }}
...
{{ form_end(form) }}
```
### Yaml ### Yaml
* The ability to pass file names to `Yaml::parse()` has been removed. * The ability to pass file names to `Yaml::parse()` has been removed.

View File

@ -1,6 +1,12 @@
CHANGELOG CHANGELOG
========= =========
2.3.0
-----
* added helpers form(), form_start() and form_end()
* deprecated form_enctype() in favor of form_start()
2.2.0 2.2.0
----- -----

View File

@ -61,12 +61,14 @@ class FormExtension extends \Twig_Extension
public function getFunctions() public function getFunctions()
{ {
return array( return array(
'form_enctype' => new \Twig_Function_Node('Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', array('is_safe' => array('html'))), 'form_enctype' => new \Twig_Function_Node('Symfony\Bridge\Twig\Node\FormEnctypeNode', array('is_safe' => array('html'))),
'form_widget' => new \Twig_Function_Node('Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', array('is_safe' => array('html'))), 'form_widget' => new \Twig_Function_Node('Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', array('is_safe' => array('html'))),
'form_errors' => new \Twig_Function_Node('Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', array('is_safe' => array('html'))), 'form_errors' => new \Twig_Function_Node('Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', array('is_safe' => array('html'))),
'form_label' => new \Twig_Function_Node('Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', array('is_safe' => array('html'))), 'form_label' => new \Twig_Function_Node('Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', array('is_safe' => array('html'))),
'form_row' => new \Twig_Function_Node('Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', array('is_safe' => array('html'))), 'form_row' => new \Twig_Function_Node('Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', array('is_safe' => array('html'))),
'form_rest' => new \Twig_Function_Node('Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', array('is_safe' => array('html'))), 'form_rest' => new \Twig_Function_Node('Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', array('is_safe' => array('html'))),
'form_start' => new \Twig_Function_Node('Symfony\Bridge\Twig\Node\RenderBlockNode', array('is_safe' => array('html'))),
'form_end' => new \Twig_Function_Node('Symfony\Bridge\Twig\Node\RenderBlockNode', array('is_safe' => array('html'))),
'csrf_token' => new \Twig_Function_Method($this, 'renderer->renderCsrfToken'), 'csrf_token' => new \Twig_Function_Method($this, 'renderer->renderCsrfToken'),
); );
} }

View File

@ -0,0 +1,31 @@
<?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\Bridge\Twig\Node;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @deprecated Deprecated since version 2.3, to be removed in 3.0. Use
* the helper "form_start()" instead.
*/
class FormEnctypeNode extends SearchAndRenderBlockNode
{
public function compile(\Twig_Compiler $compiler)
{
parent::compile($compiler);
$compiler->raw(";\n");
// Uncomment this as soon as the deprecation note should be shown
// $compiler->write('trigger_error(\'The helper form_enctype(form) is deprecated since version 2.3 and will be removed in 3.0. Use form_start(form) instead.\', E_USER_DEPRECATED)');
}
}

View File

@ -0,0 +1,42 @@
<?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\Bridge\Twig\Node;
/**
* Compiles a call to {@link FormRendererInterface::renderBlock()}.
*
* The function name is used as block name. For example, if the function name
* is "foo", the block "foo" will be rendered.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class RenderBlockNode extends \Twig_Node_Expression_Function
{
public function compile(\Twig_Compiler $compiler)
{
$compiler->addDebugInfo($this);
$arguments = iterator_to_array($this->getNode('arguments'));
$compiler->write('$this->env->getExtension(\'form\')->renderer->renderBlock(');
if (isset($arguments[0])) {
$compiler->subcompile($arguments[0]);
$compiler->raw(', \'' . $this->getAttribute('name') . '\'');
if (isset($arguments[1])) {
$compiler->raw(', ');
$compiler->subcompile($arguments[1]);
}
}
$compiler->raw(')');
}
}

View File

@ -298,6 +298,38 @@
{# Misc #} {# Misc #}
{% block form %}
{% spaceless %}
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
{% endspaceless %}
{% endblock form %}
{% block form_start %}
{% spaceless %}
{% set method = method|upper %}
{% if method in ["GET", "POST"] %}
{% set form_method = method %}
{% else %}
{% set form_method = "POST" %}
{% endif %}
<form method="{{ form_method|lower }}" action="{{ action }}"{% for attrname, attrvalue in attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}{% if multipart %} enctype="multipart/form-data"{% endif %}>
{% if form_method != method %}
<input type="hidden" name="_method" value="{{ method }}" />
{% endif %}
{% endspaceless %}
{% endblock form_start %}
{% block form_end %}
{% spaceless %}
{% if not render_rest is defined or render_rest %}
{{ form_rest(form) }}
{% endif %}
</form>
{% endspaceless %}
{% endblock form_end %}
{% block form_enctype %} {% block form_enctype %}
{% spaceless %} {% spaceless %}
{% if multipart %}enctype="multipart/form-data"{% endif %} {% if multipart %}enctype="multipart/form-data"{% endif %}

View File

@ -139,6 +139,11 @@ class FormExtensionDivLayoutTest extends AbstractDivLayoutTest
$this->assertSame($expected, $this->extension->isSelectedChoice($choice, $value)); $this->assertSame($expected, $this->extension->isSelectedChoice($choice, $value));
} }
protected function renderForm(FormView $view, array $vars = array())
{
return (string) $this->extension->renderer->renderBlock($view, 'form', $vars);
}
protected function renderEnctype(FormView $view) protected function renderEnctype(FormView $view)
{ {
return (string) $this->extension->renderer->searchAndRenderBlock($view, 'enctype'); return (string) $this->extension->renderer->searchAndRenderBlock($view, 'enctype');
@ -173,6 +178,16 @@ class FormExtensionDivLayoutTest extends AbstractDivLayoutTest
return (string) $this->extension->renderer->searchAndRenderBlock($view, 'rest', $vars); return (string) $this->extension->renderer->searchAndRenderBlock($view, 'rest', $vars);
} }
protected function renderStart(FormView $view, array $vars = array())
{
return (string) $this->extension->renderer->renderBlock($view, 'form_start', $vars);
}
protected function renderEnd(FormView $view, array $vars = array())
{
return (string) $this->extension->renderer->renderBlock($view, 'form_end', $vars);
}
protected function setTheme(FormView $view, array $themes) protected function setTheme(FormView $view, array $themes)
{ {
$this->extension->renderer->setTheme($view, $themes); $this->extension->renderer->setTheme($view, $themes);

View File

@ -75,6 +75,11 @@ class FormExtensionTableLayoutTest extends AbstractTableLayoutTest
$this->extension = null; $this->extension = null;
} }
protected function renderForm(FormView $view, array $vars = array())
{
return (string) $this->extension->renderer->renderBlock($view, 'form', $vars);
}
protected function renderEnctype(FormView $view) protected function renderEnctype(FormView $view)
{ {
return (string) $this->extension->renderer->searchAndRenderBlock($view, 'enctype'); return (string) $this->extension->renderer->searchAndRenderBlock($view, 'enctype');
@ -109,6 +114,16 @@ class FormExtensionTableLayoutTest extends AbstractTableLayoutTest
return (string) $this->extension->renderer->searchAndRenderBlock($view, 'rest', $vars); return (string) $this->extension->renderer->searchAndRenderBlock($view, 'rest', $vars);
} }
protected function renderStart(FormView $view, array $vars = array())
{
return (string) $this->extension->renderer->renderBlock($view, 'form_start', $vars);
}
protected function renderEnd(FormView $view, array $vars = array())
{
return (string) $this->extension->renderer->renderBlock($view, 'form_end', $vars);
}
protected function setTheme(FormView $view, array $themes) protected function setTheme(FormView $view, array $themes)
{ {
$this->extension->renderer->setTheme($view, $themes); $this->extension->renderer->setTheme($view, $themes);

View File

@ -10,6 +10,9 @@ CHANGELOG
* added `TimedPhpEngine` * added `TimedPhpEngine`
* added `--clean` option the the `translation:update` command * added `--clean` option the the `translation:update` command
* added `http_method_override` option * added `http_method_override` option
* added support for default templates per render tag
* added FormHelper::form(), FormHelper::start() and FormHelper::end()
* deprecated FormHelper::enctype() in favor of FormHelper::start()
2.2.0 2.2.0
----- -----
@ -27,10 +30,10 @@ CHANGELOG
* replaced Symfony\Bundle\FrameworkBundle\Controller\TraceableControllerResolver by Symfony\Component\HttpKernel\Controller\TraceableControllerResolver * replaced Symfony\Bundle\FrameworkBundle\Controller\TraceableControllerResolver by Symfony\Component\HttpKernel\Controller\TraceableControllerResolver
* replaced Symfony\Component\HttpKernel\Debug\ContainerAwareTraceableEventDispatcher by Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher * replaced Symfony\Component\HttpKernel\Debug\ContainerAwareTraceableEventDispatcher by Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher
* added Client::enableProfiler() * added Client::enableProfiler()
* A new parameter has been added to the DIC: `router.request_context.base_url` * a new parameter has been added to the DIC: `router.request_context.base_url`
You can customize it for your functional tests or for generating urls with You can customize it for your functional tests or for generating urls with
the right base url when your are in the cli context. the right base url when your are in the cli context.
* Added support for default templates per render tag * added support for default templates per render tag
2.1.0 2.1.0
----- -----

View File

@ -0,0 +1,3 @@
<?php echo $view['form']->start($form) ?>
<?php echo $view['form']->widget($form) ?>
<?php echo $view['form']->end($form) ?>

View File

@ -0,0 +1,4 @@
<?php if (!isset($render_rest) || $render_rest): ?>
<?php echo $view['form']->rest($form) ?>
<?php endif ?>
</form>

View File

@ -0,0 +1,6 @@
<?php $method = strtoupper($method) ?>
<?php $form_method = $method === 'GET' || $method === 'POST' ? $method : 'POST' ?>
<form method="<?php echo strtolower($form_method) ?>" action="<?php echo $action ?>"<?php foreach ($attr as $k => $v) { printf(' %s="%s"', $view->escape($k), $view->escape($v)); } ?><?php if ($multipart): ?> enctype="multipart/form-data"<?php endif ?>>
<?php if ($form_method !== $method): ?>
<input type="hidden" name="_method" value="<?php echo $method ?>" />
<?php endif ?>

View File

@ -57,19 +57,88 @@ class FormHelper extends Helper
$this->renderer->setTheme($view, $themes); $this->renderer->setTheme($view, $themes);
} }
/**
* Renders the HTML for a form.
*
* Example usage:
*
* <?php echo view['form']->form($form) ?>
*
* You can pass options during the call:
*
* <?php echo view['form']->form($form, array('attr' => array('class' => 'foo'))) ?>
*
* <?php echo view['form']->form($form, array('separator' => '+++++')) ?>
*
* This method is mainly intended for prototyping purposes. If you want to
* control the layout of a form in a more fine-grained manner, you are
* advised to use the other helper methods for rendering the parts of the
* form individually. You can also create a custom form theme to adapt
* the look of the form.
*
* @param FormView $view The view for which to render the form
* @param array $variables Additional variables passed to the template
*
* @return string The HTML markup
*/
public function form(FormView $view, array $variables = array())
{
return $this->renderer->renderBlock($view, 'form', $variables);
}
/**
* Renders the form start tag.
*
* Example usage templates:
*
* <?php echo $view['form']->start($form) ?>>
*
* @param FormView $view The view for which to render the start tag
* @param array $variables Additional variables passed to the template
*
* @return string The HTML markup
*/
public function start(FormView $view, array $variables = array())
{
return $this->renderer->renderBlock($view, 'form_start', $variables);
}
/**
* Renders the form end tag.
*
* Example usage templates:
*
* <?php echo $view['form']->end($form) ?>>
*
* @param FormView $view The view for which to render the end tag
* @param array $variables Additional variables passed to the template
*
* @return string The HTML markup
*/
public function end(FormView $view, array $variables = array())
{
return $this->renderer->renderBlock($view, 'form_end', $variables);
}
/** /**
* Renders the HTML enctype in the form tag, if necessary. * Renders the HTML enctype in the form tag, if necessary.
* *
* Example usage templates: * Example usage templates:
* *
* <form action="..." method="post" <?php echo $view['form']->enctype() ?>> * <form action="..." method="post" <?php echo $view['form']->enctype($form) ?>>
* *
* @param FormView $view The view for which to render the encoding type * @param FormView $view The view for which to render the encoding type
* *
* @return string The HTML markup * @return string The HTML markup
*
* @deprecated Deprecated since version 2.3, to be removed in 3.0. Use
* {@link start} instead.
*/ */
public function enctype(FormView $view) public function enctype(FormView $view)
{ {
// Uncomment this as soon as the deprecation note should be shown
// trigger_error('The form helper $view[\'form\']->enctype() is deprecated since version 2.3 and will be removed in 3.0. Use $view[\'form\']->start() instead.', E_USER_DEPRECATED);
return $this->renderer->searchAndRenderBlock($view, 'enctype'); return $this->renderer->searchAndRenderBlock($view, 'enctype');
} }
@ -78,13 +147,13 @@ class FormHelper extends Helper
* *
* Example usage: * Example usage:
* *
* <?php echo view['form']->widget() ?> * <?php echo view['form']->widget($form) ?>
* *
* You can pass options during the call: * You can pass options during the call:
* *
* <?php echo view['form']->widget(array('attr' => array('class' => 'foo'))) ?> * <?php echo view['form']->widget($form, array('attr' => array('class' => 'foo'))) ?>
* *
* <?php echo view['form']->widget(array('separator' => '+++++')) ?> * <?php echo view['form']->widget($form, array('separator' => '+++++')) ?>
* *
* @param FormView $view The view for which to render the widget * @param FormView $view The view for which to render the widget
* @param array $variables Additional variables passed to the template * @param array $variables Additional variables passed to the template

View File

@ -72,6 +72,11 @@ class FormHelperDivLayoutTest extends AbstractDivLayoutTest
parent::tearDown(); parent::tearDown();
} }
protected function renderForm(FormView $view, array $vars = array())
{
return (string) $this->engine->get('form')->form($view, $vars);
}
protected function renderEnctype(FormView $view) protected function renderEnctype(FormView $view)
{ {
return (string) $this->engine->get('form')->enctype($view); return (string) $this->engine->get('form')->enctype($view);
@ -102,6 +107,16 @@ class FormHelperDivLayoutTest extends AbstractDivLayoutTest
return (string) $this->engine->get('form')->rest($view, $vars); return (string) $this->engine->get('form')->rest($view, $vars);
} }
protected function renderStart(FormView $view, array $vars = array())
{
return (string) $this->engine->get('form')->start($view, $vars);
}
protected function renderEnd(FormView $view, array $vars = array())
{
return (string) $this->engine->get('form')->end($view, $vars);
}
protected function setTheme(FormView $view, array $themes) protected function setTheme(FormView $view, array $themes)
{ {
$this->engine->get('form')->setTheme($view, $themes); $this->engine->get('form')->setTheme($view, $themes);

View File

@ -73,6 +73,11 @@ class FormHelperTableLayoutTest extends AbstractTableLayoutTest
parent::tearDown(); parent::tearDown();
} }
protected function renderForm(FormView $view, array $vars = array())
{
return (string) $this->engine->get('form')->form($view, $vars);
}
protected function renderEnctype(FormView $view) protected function renderEnctype(FormView $view)
{ {
return (string) $this->engine->get('form')->enctype($view); return (string) $this->engine->get('form')->enctype($view);
@ -103,6 +108,16 @@ class FormHelperTableLayoutTest extends AbstractTableLayoutTest
return (string) $this->engine->get('form')->rest($view, $vars); return (string) $this->engine->get('form')->rest($view, $vars);
} }
protected function renderStart(FormView $view, array $vars = array())
{
return (string) $this->engine->get('form')->start($view, $vars);
}
protected function renderEnd(FormView $view, array $vars = array())
{
return (string) $this->engine->get('form')->end($view, $vars);
}
protected function setTheme(FormView $view, array $themes) protected function setTheme(FormView $view, array $themes)
{ {
$this->engine->get('form')->setTheme($view, $themes); $this->engine->get('form')->setTheme($view, $themes);

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Form; namespace Symfony\Component\Form;
use Symfony\Component\Form\Exception\AlreadyBoundException; use Symfony\Component\Form\Exception\AlreadyBoundException;
use Symfony\Component\Form\Exception\BadMethodCallException;
/** /**
* A form button. * A form button.
@ -342,6 +343,18 @@ class Button implements \IteratorAggregate, FormInterface
return true; return true;
} }
/**
* Unsupported method.
*
* @param mixed $request
*
* @throws BadMethodCallException
*/
public function process($request = null)
{
throw new BadMethodCallException('Buttons cannot be processed. Call process() on the root form instead.');
}
/** /**
* Binds data to the button. * Binds data to the button.
* *

View File

@ -456,6 +456,42 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface
throw new \BadMethodCallException('Buttons do not support form factories.'); throw new \BadMethodCallException('Buttons do not support form factories.');
} }
/**
* Unsupported method.
*
* @param string $action
*
* @throws \BadMethodCallException
*/
public function setAction($action)
{
throw new \BadMethodCallException('Buttons do not support actions.');
}
/**
* Unsupported method.
*
* @param string $method
*
* @throws \BadMethodCallException
*/
public function setMethod($method)
{
throw new \BadMethodCallException('Buttons do not support methods.');
}
/**
* Unsupported method.
*
* @param FormProcessorInterface $formProcessor
*
* @throws \BadMethodCallException
*/
public function setFormProcessor(FormProcessorInterface $formProcessor)
{
throw new \BadMethodCallException('Buttons do not support form processors.');
}
/** /**
* Builds and returns the button configuration. * Builds and returns the button configuration.
* *
@ -693,6 +729,36 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface
return null; return null;
} }
/**
* Unsupported method.
*
* @return null Always returns null.
*/
public function getAction()
{
return null;
}
/**
* Unsupported method.
*
* @return null Always returns null.
*/
public function getMethod()
{
return null;
}
/**
* Unsupported method.
*
* @return null Always returns null.
*/
public function getFormProcessor()
{
return null;
}
/** /**
* Returns all options passed during the construction of the button. * Returns all options passed during the construction of the button.
* *

View File

@ -6,6 +6,9 @@ CHANGELOG
------ ------
* changed FormRenderer::humanize() to humanize also camel cased field name * changed FormRenderer::humanize() to humanize also camel cased field name
* added FormProcessorInterface and FormInterface::process()
* deprecated passing a Request instance to FormInterface::bind()
* added options "method" and "action" to FormType
2.2.0 2.2.0
----- -----

View File

@ -53,6 +53,8 @@ class FormType extends BaseType
->setData(isset($options['data']) ? $options['data'] : null) ->setData(isset($options['data']) ? $options['data'] : null)
->setDataLocked(isset($options['data'])) ->setDataLocked(isset($options['data']))
->setDataMapper($options['compound'] ? new PropertyPathMapper($this->propertyAccessor) : null) ->setDataMapper($options['compound'] ? new PropertyPathMapper($this->propertyAccessor) : null)
->setMethod($options['method'])
->setAction($options['action'])
; ;
if ($options['trim']) { if ($options['trim']) {
@ -93,6 +95,8 @@ class FormType extends BaseType
'size' => null, 'size' => null,
'label_attr' => $options['label_attr'], 'label_attr' => $options['label_attr'],
'compound' => $form->getConfig()->getCompound(), 'compound' => $form->getConfig()->getCompound(),
'method' => $form->getConfig()->getMethod(),
'action' => $form->getConfig()->getAction(),
)); ));
} }
@ -167,6 +171,10 @@ class FormType extends BaseType
'label_attr' => array(), 'label_attr' => array(),
'virtual' => false, 'virtual' => false,
'compound' => true, 'compound' => true,
'method' => 'POST',
// According to RFC 2396 (http://www.ietf.org/rfc/rfc2396.txt)
// section 4.2., empty URIs are considered same-document references
'action' => '',
)); ));
$resolver->setAllowedTypes(array( $resolver->setAllowedTypes(array(

View File

@ -19,6 +19,9 @@ use Symfony\Component\HttpFoundation\Request;
/** /**
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
*
* @deprecated Deprecated since version 2.3, to be removed in 3.0. Pass the
* Request instance to {@link Form::process()} instead.
*/ */
class BindRequestListener implements EventSubscriberInterface class BindRequestListener implements EventSubscriberInterface
{ {
@ -40,6 +43,9 @@ class BindRequestListener implements EventSubscriberInterface
return; return;
} }
// Uncomment this as soon as the deprecation note should be shown
// trigger_error('Passing a Request instance to Form::bind() is deprecated since version 2.3 and will be disabled in 3.0. Call Form::process($request) instead.', E_USER_DEPRECATED);
$name = $form->getConfig()->getName(); $name = $form->getConfig()->getName();
$default = $form->getConfig()->getCompound() ? array() : null; $default = $form->getConfig()->getCompound() ? array() : null;

View File

@ -0,0 +1,80 @@
<?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\HttpFoundation;
use Symfony\Component\Form\Exception\InvalidArgumentException;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormProcessorInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* A form processor using the {@link Request} class of the HttpFoundation
* component.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class RequestFormProcessor implements FormProcessorInterface
{
/**
* {@inheritdoc}
*/
public function processForm(FormInterface $form, $request = null)
{
if (!$request instanceof Request) {
throw new UnexpectedTypeException($request, 'Symfony\Component\HttpFoundation\Request');
}
$name = $form->getName();
$method = $form->getConfig()->getMethod();
if ($method !== $request->getMethod()) {
return;
}
if ('GET' === $method) {
if ('' === $name) {
$data = $request->query->all();
} else {
// Don't bind GET requests if the form's name does not exist
// in the request
if (!$request->query->has($name)) {
return;
}
$data = $request->query->get($name);
}
} else {
if ('' === $name) {
$params = $request->request->all();
$files = $request->files->all();
} else {
$default = $form->getConfig()->getCompound() ? array() : null;
$params = $request->request->get($name, $default);
$files = $request->files->get($name, $default);
}
if (is_array($params) && is_array($files)) {
$data = array_replace_recursive($params, $files);
} else {
$data = $params ?: $files;
}
}
// Don't auto-bind the form unless at least one field is submitted.
if ('' === $name && count(array_intersect_key($data, $form->all())) <= 0) {
return;
}
$form->bind($data);
}
}

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Form\Extension\HttpFoundation\Type;
use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\HttpFoundation\EventListener\BindRequestListener; use Symfony\Component\Form\Extension\HttpFoundation\EventListener\BindRequestListener;
use Symfony\Component\Form\Extension\HttpFoundation\RequestFormProcessor;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
/** /**
@ -25,9 +26,15 @@ class FormTypeHttpFoundationExtension extends AbstractTypeExtension
*/ */
private $listener; private $listener;
/**
* @var RequestFormProcessor
*/
private $processor;
public function __construct() public function __construct()
{ {
$this->listener = new BindRequestListener(); $this->listener = new BindRequestListener();
$this->processor = new RequestFormProcessor();
} }
/** /**
@ -36,6 +43,7 @@ class FormTypeHttpFoundationExtension extends AbstractTypeExtension
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
$builder->addEventSubscriber($this->listener); $builder->addEventSubscriber($this->listener);
$builder->setFormProcessor($this->processor);
} }
/** /**

View File

@ -404,6 +404,16 @@ class Form implements \IteratorAggregate, FormInterface
return $this->extraData; return $this->extraData;
} }
/**
* {@inheritdoc}
*/
public function process($request = null)
{
$this->config->getFormProcessor()->processForm($this, $request);
return $this;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -579,7 +589,7 @@ class Form implements \IteratorAggregate, FormInterface
public function isValid() public function isValid()
{ {
if (!$this->bound) { if (!$this->bound) {
throw new \LogicException('You cannot call isValid() on a form that is not bound.'); return false;
} }
if (count($this->errors) > 0) { if (count($this->errors) > 0) {

View File

@ -12,7 +12,7 @@
namespace Symfony\Component\Form; namespace Symfony\Component\Form;
use Symfony\Component\Form\Exception\BadMethodCallException; use Symfony\Component\Form\Exception\BadMethodCallException;
use Symfony\Component\Form\Exception\Exception; use Symfony\Component\Form\Exception\InvalidArgumentException;
use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\PropertyAccess\PropertyPath; use Symfony\Component\PropertyAccess\PropertyPath;
use Symfony\Component\PropertyAccess\PropertyPathInterface; use Symfony\Component\PropertyAccess\PropertyPathInterface;
@ -27,6 +27,26 @@ use Symfony\Component\EventDispatcher\ImmutableEventDispatcher;
*/ */
class FormConfigBuilder implements FormConfigBuilderInterface class FormConfigBuilder implements FormConfigBuilderInterface
{ {
/**
* Caches a globally unique {@link NativeFormProcessor} instance.
*
* @var NativeFormProcessor
*/
private static $nativeFormProcessor;
/**
* The accepted request methods.
*
* @var array
*/
private static $allowedMethods = array(
'GET',
'PUT',
'POST',
'DELETE',
'PATCH'
);
/** /**
* @var Boolean * @var Boolean
*/ */
@ -137,6 +157,21 @@ class FormConfigBuilder implements FormConfigBuilderInterface
*/ */
private $formFactory; private $formFactory;
/**
* @var string
*/
private $action;
/**
* @var string
*/
private $method = 'POST';
/**
* @var FormProcessorInterface
*/
private $formProcessor;
/** /**
* @var array * @var array
*/ */
@ -264,6 +299,10 @@ class FormConfigBuilder implements FormConfigBuilderInterface
*/ */
public function getEventDispatcher() public function getEventDispatcher()
{ {
if ($this->locked && !$this->dispatcher instanceof ImmutableEventDispatcher) {
$this->dispatcher = new ImmutableEventDispatcher($this->dispatcher);
}
return $this->dispatcher; return $this->dispatcher;
} }
@ -435,6 +474,37 @@ class FormConfigBuilder implements FormConfigBuilderInterface
return $this->formFactory; return $this->formFactory;
} }
/**
* {@inheritdoc}
*/
public function getAction()
{
return $this->action;
}
/**
* {@inheritdoc}
*/
public function getMethod()
{
return $this->method;
}
/**
* {@inheritdoc}
*/
public function getFormProcessor()
{
if (null === $this->formProcessor) {
if (null === self::$nativeFormProcessor) {
self::$nativeFormProcessor = new NativeFormProcessor();
}
$this->formProcessor = self::$nativeFormProcessor;
}
return $this->formProcessor;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -687,6 +757,58 @@ class FormConfigBuilder implements FormConfigBuilderInterface
return $this; return $this;
} }
/**
* {@inheritdoc}
*/
public function setAction($action)
{
if ($this->locked) {
throw new BadMethodCallException('The config builder cannot be modified anymore.');
}
$this->action = $action;
return $this;
}
/**
* {@inheritdoc}
*/
public function setMethod($method)
{
if ($this->locked) {
throw new BadMethodCallException('The config builder cannot be modified anymore.');
}
$upperCaseMethod = strtoupper($method);
if (!in_array($upperCaseMethod, self::$allowedMethods)) {
throw new InvalidArgumentException(sprintf(
'The form method is "%s", but should be one of "%s".',
$method,
implode('", "', self::$allowedMethods)
));
}
$this->method = $upperCaseMethod;
return $this;
}
/**
* {@inheritdoc}
*/
public function setFormProcessor(FormProcessorInterface $formProcessor)
{
if ($this->locked) {
throw new BadMethodCallException('The config builder cannot be modified anymore.');
}
$this->formProcessor = $formProcessor;
return $this;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -700,10 +822,6 @@ class FormConfigBuilder implements FormConfigBuilderInterface
$config = clone $this; $config = clone $this;
$config->locked = true; $config->locked = true;
if (!$config->dispatcher instanceof ImmutableEventDispatcher) {
$config->dispatcher = new ImmutableEventDispatcher($config->dispatcher);
}
return $config; return $config;
} }

View File

@ -237,6 +237,31 @@ interface FormConfigBuilderInterface extends FormConfigInterface
*/ */
public function setFormFactory(FormFactoryInterface $formFactory); public function setFormFactory(FormFactoryInterface $formFactory);
/**
* Sets the target URL of the form.
*
* @param string $action The target URL of the form.
*
* @return self The configuration object.
*/
public function setAction($action);
/**
* Sets the HTTP method used by the form.
*
* @param string $method The HTTP method of the form.
*
* @return self The configuration object.
*/
public function setMethod($method);
/**
* @param FormProcessorInterface $formProcessor
*
* @return self The configuration object.
*/
public function setFormProcessor(FormProcessorInterface $formProcessor);
/** /**
* Builds and returns the form configuration. * Builds and returns the form configuration.
* *

View File

@ -190,6 +190,25 @@ interface FormConfigInterface
*/ */
public function getFormFactory(); public function getFormFactory();
/**
* Returns the target URL of the form.
*
* @return string The target URL of the form.
*/
public function getAction();
/**
* Returns the HTTP method used by the form.
*
* @return string The HTTP method of the form.
*/
public function getMethod();
/**
* @return FormProcessorInterface The form processor.
*/
public function getFormProcessor();
/** /**
* Returns all options passed during the construction of the form. * Returns all options passed during the construction of the form.
* *

View File

@ -182,6 +182,8 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable
/** /**
* Returns whether the form and all children are valid. * Returns whether the form and all children are valid.
* *
* If the form is not bound, this method always returns false.
*
* @return Boolean * @return Boolean
*/ */
public function isValid(); public function isValid();
@ -224,6 +226,19 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable
*/ */
public function isSynchronized(); public function isSynchronized();
/**
* Processes the given request and binds the form if it was submitted.
*
* Internally, the request is forwarded to a {@link FormProcessorInterface}
* instance. This instance determines the allowed value of the
* $request parameter.
*
* @param mixed $request The request to check.
*
* @return FormInterface The form instance.
*/
public function process($request = null);
/** /**
* Binds data to the form, transforms and validates it. * Binds data to the form, transforms and validates it.
* *

View File

@ -0,0 +1,28 @@
<?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;
/**
* Binds forms from requests if they were submitted.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface FormProcessorInterface
{
/**
* Binds a form from a request if it was submitted.
*
* @param FormInterface $form The form to bind.
* @param mixed $request The current request.
*/
public function processForm(FormInterface $form, $request = null);
}

View File

@ -87,19 +87,31 @@ class FormRenderer implements FormRendererInterface
*/ */
public function renderBlock(FormView $view, $blockName, array $variables = array()) public function renderBlock(FormView $view, $blockName, array $variables = array())
{ {
if (0 == count($this->variableStack)) {
throw new Exception('This method should only be called while rendering a form element.');
}
$viewCacheKey = $view->vars[self::CACHE_KEY_VAR];
$scopeVariables = end($this->variableStack[$viewCacheKey]);
$resource = $this->engine->getResourceForBlockName($view, $blockName); $resource = $this->engine->getResourceForBlockName($view, $blockName);
if (!$resource) { if (!$resource) {
throw new Exception(sprintf('No block "%s" found while rendering the form.', $blockName)); throw new Exception(sprintf('No block "%s" found while rendering the form.', $blockName));
} }
$viewCacheKey = $view->vars[self::CACHE_KEY_VAR];
// The variables are cached globally for a view (instead of for the
// current suffix)
if (!isset($this->variableStack[$viewCacheKey])) {
$this->variableStack[$viewCacheKey] = array();
// The default variable scope contains all view variables, merged with
// the variables passed explicitly to the helper
$scopeVariables = $view->vars;
$varInit = true;
} else {
// Reuse the current scope and merge it with the explicitly passed variables
$scopeVariables = end($this->variableStack[$viewCacheKey]);
$varInit = false;
}
// Merge the passed with the existing attributes // Merge the passed with the existing attributes
if (isset($variables['attr']) && isset($scopeVariables['attr'])) { if (isset($variables['attr']) && isset($scopeVariables['attr'])) {
$variables['attr'] = array_replace($scopeVariables['attr'], $variables['attr']); $variables['attr'] = array_replace($scopeVariables['attr'], $variables['attr']);
@ -122,6 +134,10 @@ class FormRenderer implements FormRendererInterface
// Clear the stack // Clear the stack
array_pop($this->variableStack[$viewCacheKey]); array_pop($this->variableStack[$viewCacheKey]);
if ($varInit) {
unset($this->variableStack[$viewCacheKey]);
}
return $html; return $html;
} }
@ -191,6 +207,8 @@ class FormRenderer implements FormRendererInterface
// The variables are cached globally for a view (instead of for the // The variables are cached globally for a view (instead of for the
// current suffix) // current suffix)
if (!isset($this->variableStack[$viewCacheKey])) { if (!isset($this->variableStack[$viewCacheKey])) {
$this->variableStack[$viewCacheKey] = array();
// The default variable scope contains all view variables, merged with // The default variable scope contains all view variables, merged with
// the variables passed explicitly to the helper // the variables passed explicitly to the helper
$scopeVariables = $view->vars; $scopeVariables = $view->vars;

View File

@ -0,0 +1,194 @@
<?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;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormProcessorInterface;
/**
* A form processor using PHP's super globals $_GET, $_POST and $_SERVER.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class NativeFormProcessor implements FormProcessorInterface
{
/**
* The allowed keys of the $_FILES array.
*
* @var array
*/
private static $fileKeys = array(
'error',
'name',
'size',
'tmp_name',
'type',
);
/**
* {@inheritdoc}
*/
public function processForm(FormInterface $form, $request = null)
{
if (null !== $request) {
throw new UnexpectedTypeException($request, 'null');
}
$name = $form->getName();
$method = $form->getConfig()->getMethod();
if ($method !== self::getRequestMethod()) {
return;
}
if ('GET' === $method) {
if ('' === $name) {
$data = $_GET;
} else {
// Don't bind GET requests if the form's name does not exist
// in the request
if (!isset($_GET[$name])) {
return;
}
$data = $_GET[$name];
}
} else {
$fixedFiles = array();
foreach ($_FILES as $name => $file) {
$fixedFiles[$name] = self::stripEmptyFiles(self::fixPhpFilesArray($file));
}
if ('' === $name) {
$params = $_POST;
$files = $fixedFiles;
} else {
$default = $form->getConfig()->getCompound() ? array() : null;
$params = isset($_POST[$name]) ? $_POST[$name] : $default;
$files = isset($fixedFiles[$name]) ? $fixedFiles[$name] : $default;
}
if (is_array($params) && is_array($files)) {
$data = array_replace_recursive($params, $files);
} else {
$data = $params ?: $files;
}
}
// Don't auto-bind the form unless at least one field is submitted.
if ('' === $name && count(array_intersect_key($data, $form->all())) <= 0) {
return;
}
$form->bind($data);
}
/**
* Returns the method used to submit the request to the server.
*
* @return string The request method.
*/
private static function getRequestMethod()
{
$method = isset($_SERVER['REQUEST_METHOD'])
? strtoupper($_SERVER['REQUEST_METHOD'])
: 'GET';
if ('POST' === $method && isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
$method = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']);
}
return $method;
}
/**
* Fixes a malformed PHP $_FILES array.
*
* PHP has a bug that the format of the $_FILES array differs, depending on
* whether the uploaded file fields had normal field names or array-like
* field names ("normal" vs. "parent[child]").
*
* This method fixes the array to look like the "normal" $_FILES array.
*
* It's safe to pass an already converted array, in which case this method
* just returns the original array unmodified.
*
* This method is identical to {@link Symfony\Component\HttpFoundation\FileBag::fixPhpFilesArray}
* and should be kept as such in order to port fixes quickly and easily.
*
* @param array $data
*
* @return array
*/
private static function fixPhpFilesArray($data)
{
if (!is_array($data)) {
return $data;
}
$keys = array_keys($data);
sort($keys);
if (self::$fileKeys !== $keys || !isset($data['name']) || !is_array($data['name'])) {
return $data;
}
$files = $data;
foreach (self::$fileKeys as $k) {
unset($files[$k]);
}
foreach (array_keys($data['name']) as $key) {
$files[$key] = self::fixPhpFilesArray(array(
'error' => $data['error'][$key],
'name' => $data['name'][$key],
'type' => $data['type'][$key],
'tmp_name' => $data['tmp_name'][$key],
'size' => $data['size'][$key]
));
}
return $files;
}
/**
* Sets empty uploaded files to NULL in the given uploaded files array.
*
* @param mixed $data The file upload data.
*
* @return array|null Returns the stripped upload data.
*/
private static function stripEmptyFiles($data)
{
if (!is_array($data)) {
return $data;
}
$keys = array_keys($data);
sort($keys);
if (self::$fileKeys === $keys) {
if (UPLOAD_ERR_NO_FILE === $data['error']) {
return null;
}
return $data;
}
foreach ($data as $key => $value) {
$data[$key] = self::stripEmptyFiles($value);
}
return $data;
}
}

View File

@ -2,6 +2,8 @@
namespace Symfony\Component\Form\Test; namespace Symfony\Component\Form\Test;
use Symfony\Component\Form\FormEvent;
class DeprecationErrorHandler class DeprecationErrorHandler
{ {
public static function handle($errorNumber, $message, $file, $line, $context) public static function handle($errorNumber, $message, $file, $line, $context)
@ -21,4 +23,11 @@ class DeprecationErrorHandler
return false; return false;
} }
public static function preBind($listener, FormEvent $event)
{
set_error_handler(array('Symfony\Component\Form\Test\DeprecationErrorHandler', 'handle'));
$listener->preBind($event);
restore_error_handler();
}
} }

View File

@ -378,6 +378,50 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest
} }
public function testForm() public function testForm()
{
$form = $this->factory->createNamedBuilder('name', 'form')
->setMethod('PUT')
->setAction('http://example.com')
->add('firstName', 'text')
->add('lastName', 'text')
->getForm();
// include ampersands everywhere to validate escaping
$html = $this->renderForm($form->createView(), array(
'id' => 'my&id',
'attr' => array('class' => 'my&class'),
));
$this->assertMatchesXpath($html,
'/form
[
./input[@type="hidden"][@name="_method"][@value="PUT"]
/following-sibling::div
[
./div
[
./label[@for="name_firstName"]
/following-sibling::input[@type="text"][@id="name_firstName"]
]
/following-sibling::div
[
./label[@for="name_lastName"]
/following-sibling::input[@type="text"][@id="name_lastName"]
]
/following-sibling::input[@type="hidden"][@id="name__token"]
]
[count(.//input)=3]
[@id="my&id"]
[@class="my&class"]
]
[@method="post"]
[@action="http://example.com"]
[@class="my&class"]
'
);
}
public function testFormWidget()
{ {
$form = $this->factory->createNamedBuilder('name', 'form') $form = $this->factory->createNamedBuilder('name', 'form')
->add('firstName', 'text') ->add('firstName', 'text')
@ -642,4 +686,50 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest
' '
); );
} }
public function testFormEndWithRest()
{
$view = $this->factory->createNamedBuilder('name', 'form')
->add('field1', 'text')
->add('field2', 'text')
->getForm()
->createView();
$this->renderWidget($view['field1']);
// Rest should only contain field2
$html = $this->renderEnd($view);
// Insert the start tag, the end tag should be rendered by the helper
$this->assertMatchesXpath('<form>' . $html,
'/form
[
./div
[
./label[@for="name_field2"]
/following-sibling::input[@type="text"][@id="name_field2"]
]
/following-sibling::input
[@type="hidden"]
[@id="name__token"]
]
'
);
}
public function testFormEndWithoutRest()
{
$view = $this->factory->createNamedBuilder('name', 'form')
->add('field1', 'text')
->add('field2', 'text')
->getForm()
->createView();
$this->renderWidget($view['field1']);
// Rest should only contain field2, but isn't rendered
$html = $this->renderEnd($view, array('render_rest' => false));
$this->assertEquals('</form>', $html);
}
} }

View File

@ -0,0 +1,280 @@
<?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;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
abstract class AbstractFormProcessorTest extends \PHPUnit_Framework_TestCase
{
/**
* @var \Symfony\Component\Form\FormProcessorInterface
*/
protected $processor;
protected $request;
protected function setUp()
{
$this->processor = $this->getFormProcessor();
$this->request = null;
}
public function methodExceptGetProvider()
{
return array(
array('POST'),
array('PUT'),
array('DELETE'),
array('PATCH'),
);
}
public function methodProvider()
{
return array_merge(array(
array('GET'),
), $this->methodExceptGetProvider());
}
/**
* @dataProvider methodProvider
*/
public function testBindIfNameInRequest($method)
{
$form = $this->getMockForm('param1', $method);
$this->setRequestData($method, array(
'param1' => 'DATA',
));
$form->expects($this->once())
->method('bind')
->with('DATA');
$this->processor->processForm($form, $this->request);
}
/**
* @dataProvider methodProvider
*/
public function testDoNotBindIfWrongRequestMethod($method)
{
$form = $this->getMockForm('param1', $method);
$otherMethod = 'POST' === $method ? 'PUT' : 'POST';
$this->setRequestData($otherMethod, array(
'param1' => 'DATA',
));
$form->expects($this->never())
->method('bind');
$this->processor->processForm($form, $this->request);
}
/**
* @dataProvider methodExceptGetProvider
*/
public function testBindSimpleFormWithNullIfNameNotInRequestAndNotGetRequest($method)
{
$form = $this->getMockForm('param1', $method, false);
$this->setRequestData($method, array(
'paramx' => array(),
));
$form->expects($this->once())
->method('bind')
->with($this->identicalTo(null));
$this->processor->processForm($form, $this->request);
}
/**
* @dataProvider methodExceptGetProvider
*/
public function testBindCompoundFormWithArrayIfNameNotInRequestAndNotGetRequest($method)
{
$form = $this->getMockForm('param1', $method, true);
$this->setRequestData($method, array(
'paramx' => array(),
));
$form->expects($this->once())
->method('bind')
->with($this->identicalTo(array()));
$this->processor->processForm($form, $this->request);
}
public function testDoNotBindIfNameNotInRequestAndGetRequest()
{
$form = $this->getMockForm('param1', 'GET');
$this->setRequestData('GET', array(
'paramx' => array(),
));
$form->expects($this->never())
->method('bind');
$this->processor->processForm($form, $this->request);
}
/**
* @dataProvider methodProvider
*/
public function testBindFormWithEmptyNameIfAtLeastOneFieldInRequest($method)
{
$form = $this->getMockForm('', $method);
$form->expects($this->any())
->method('all')
->will($this->returnValue(array(
'param1' => $this->getMockForm('param1'),
'param2' => $this->getMockForm('param2'),
)));
$this->setRequestData($method, $requestData = array(
'param1' => 'submitted value',
'paramx' => 'submitted value',
));
$form->expects($this->once())
->method('bind')
->with($requestData);
$this->processor->processForm($form, $this->request);
}
/**
* @dataProvider methodProvider
*/
public function testDoNotBindFormWithEmptyNameIfNoFieldInRequest($method)
{
$form = $this->getMockForm('', $method);
$form->expects($this->any())
->method('all')
->will($this->returnValue(array(
'param1' => $this->getMockForm('param1'),
'param2' => $this->getMockForm('param2'),
)));
$this->setRequestData($method, array(
'paramx' => 'submitted value',
));
$form->expects($this->never())
->method('bind');
$this->processor->processForm($form, $this->request);
}
/**
* @dataProvider methodExceptGetProvider
*/
public function testMergeParamsAndFiles($method)
{
$form = $this->getMockForm('param1', $method);
$file = $this->getMockFile();
$this->setRequestData($method, array(
'param1' => array(
'field1' => 'DATA',
),
), array(
'param1' => array(
'field2' => $file,
),
));
$form->expects($this->once())
->method('bind')
->with(array(
'field1' => 'DATA',
'field2' => $file,
));
$this->processor->processForm($form, $this->request);
}
/**
* @dataProvider methodExceptGetProvider
*/
public function testParamTakesPrecedenceOverFile($method)
{
$form = $this->getMockForm('param1', $method);
$file = $this->getMockFile();
$this->setRequestData($method, array(
'param1' => 'DATA',
), array(
'param1' => $file,
));
$form->expects($this->once())
->method('bind')
->with('DATA');
$this->processor->processForm($form, $this->request);
}
/**
* @dataProvider methodExceptGetProvider
*/
public function testBindFileIfNoParam($method)
{
$form = $this->getMockForm('param1', $method);
$file = $this->getMockFile();
$this->setRequestData($method, array(
'param1' => null,
), array(
'param1' => $file,
));
$form->expects($this->once())
->method('bind')
->with($file);
$this->processor->processForm($form, $this->request);
}
abstract protected function setRequestData($method, $data, $files = array());
abstract protected function getFormProcessor();
abstract protected function getMockFile();
protected function getMockForm($name, $method = null, $compound = true)
{
$config = $this->getMock('Symfony\Component\Form\FormConfigInterface');
$config->expects($this->any())
->method('getMethod')
->will($this->returnValue($method));
$config->expects($this->any())
->method('getCompound')
->will($this->returnValue($compound));
$form = $this->getMock('Symfony\Component\Form\Test\FormInterface');
$form->expects($this->any())
->method('getName')
->will($this->returnValue($name));
$form->expects($this->any())
->method('getConfig')
->will($this->returnValue($config));
return $form;
}
}

View File

@ -19,8 +19,6 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase
{ {
protected $csrfProvider; protected $csrfProvider;
protected $factory;
protected function setUp() protected function setUp()
{ {
if (!extension_loaded('intl')) { if (!extension_loaded('intl')) {
@ -44,7 +42,6 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase
protected function tearDown() protected function tearDown()
{ {
$this->csrfProvider = null; $this->csrfProvider = null;
$this->factory = null;
parent::tearDown(); parent::tearDown();
} }
@ -102,6 +99,8 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase
$this->assertMatchesXpath($html, $xpath); $this->assertMatchesXpath($html, $xpath);
} }
abstract protected function renderForm(FormView $view, array $vars = array());
abstract protected function renderEnctype(FormView $view); abstract protected function renderEnctype(FormView $view);
abstract protected function renderLabel(FormView $view, $label = null, array $vars = array()); abstract protected function renderLabel(FormView $view, $label = null, array $vars = array());
@ -114,6 +113,10 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase
abstract protected function renderRest(FormView $view, array $vars = array()); abstract protected function renderRest(FormView $view, array $vars = array());
abstract protected function renderStart(FormView $view, array $vars = array());
abstract protected function renderEnd(FormView $view, array $vars = array());
abstract protected function setTheme(FormView $view, array $themes); abstract protected function setTheme(FormView $view, array $themes);
public function testEnctype() public function testEnctype()
@ -1744,9 +1747,7 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase
$this->assertMatchesXpath($html, $this->assertMatchesXpath($html,
'//div[@id="name_items"][@data-prototype] '//div[@id="name_items"][@data-prototype]
| |
//table[@id="name_items"][@data-prototype] //table[@id="name_items"][@data-prototype]'
'
); );
} }
@ -1796,4 +1797,76 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase
'/button[@type="reset"][@name="name"]' '/button[@type="reset"][@name="name"]'
); );
} }
public function testStartTag()
{
$form = $this->factory->create('form', null, array(
'method' => 'get',
'action' => 'http://example.com/directory'
));
$html = $this->renderStart($form->createView());
$this->assertSame('<form method="get" action="http://example.com/directory">', $html);
}
public function testStartTagForPutRequest()
{
$form = $this->factory->create('form', null, array(
'method' => 'put',
'action' => 'http://example.com/directory'
));
$html = $this->renderStart($form->createView());
$this->assertMatchesXpath($html . '</form>',
'/form
[./input[@type="hidden"][@name="_method"][@value="PUT"]]
[@method="post"]
[@action="http://example.com/directory"]'
);
}
public function testStartTagWithOverriddenVars()
{
$form = $this->factory->create('form', null, array(
'method' => 'put',
'action' => 'http://example.com/directory',
));
$html = $this->renderStart($form->createView(), array(
'method' => 'post',
'action' => 'http://foo.com/directory'
));
$this->assertSame('<form method="post" action="http://foo.com/directory">', $html);
}
public function testStartTagForMultipartForm()
{
$form = $this->factory->createBuilder('form', null, array(
'method' => 'get',
'action' => 'http://example.com/directory'
))
->add('file', 'file')
->getForm();
$html = $this->renderStart($form->createView());
$this->assertSame('<form method="get" action="http://example.com/directory" enctype="multipart/form-data">', $html);
}
public function testStartTagWithExtraAttributes()
{
$form = $this->factory->create('form', null, array(
'method' => 'get',
'action' => 'http://example.com/directory'
));
$html = $this->renderStart($form->createView(), array(
'attr' => array('class' => 'foobar'),
));
$this->assertSame('<form method="get" action="http://example.com/directory" class="foobar">', $html);
}
} }

View File

@ -224,6 +224,58 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
} }
public function testForm() public function testForm()
{
$view = $this->factory->createNamedBuilder('name', 'form')
->setMethod('PUT')
->setAction('http://example.com')
->add('firstName', 'text')
->add('lastName', 'text')
->getForm()
->createView();
$html = $this->renderForm($view, array(
'id' => 'my&id',
'attr' => array('class' => 'my&class'),
));
$this->assertMatchesXpath($html,
'/form
[
./input[@type="hidden"][@name="_method"][@value="PUT"]
/following-sibling::table
[
./tr
[
./td
[./label[@for="name_firstName"]]
/following-sibling::td
[./input[@id="name_firstName"]]
]
/following-sibling::tr
[
./td
[./label[@for="name_lastName"]]
/following-sibling::td
[./input[@id="name_lastName"]]
]
/following-sibling::tr[@style="display: none"]
[./td[@colspan="2"]/input
[@type="hidden"]
[@id="name__token"]
]
]
[count(.//input)=3]
[@id="my&id"]
[@class="my&class"]
]
[@method="post"]
[@action="http://example.com"]
[@class="my&class"]
'
);
}
public function testFormWidget()
{ {
$view = $this->factory->createNamedBuilder('name', 'form') $view = $this->factory->createNamedBuilder('name', 'form')
->add('firstName', 'text') ->add('firstName', 'text')
@ -400,4 +452,58 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
' '
); );
} }
public function testFormEndWithRest()
{
$view = $this->factory->createNamedBuilder('name', 'form')
->add('field1', 'text')
->add('field2', 'text')
->getForm()
->createView();
$this->renderWidget($view['field1']);
// Rest should only contain field2
$html = $this->renderEnd($view);
// Insert the start tag, the end tag should be rendered by the helper
// Unfortunately this is not valid HTML, because the surrounding table
// tag is missing. If someone renders a form with table layout
// manually, she should call form_rest() explicitly within the <table>
// tag.
$this->assertMatchesXpath('<form>' . $html,
'/form
[
./tr
[
./td
[./label[@for="name_field2"]]
/following-sibling::td
[./input[@id="name_field2"]]
]
/following-sibling::tr[@style="display: none"]
[./td[@colspan="2"]/input
[@type="hidden"]
[@id="name__token"]
]
]
'
);
}
public function testFormEndWithoutRest()
{
$view = $this->factory->createNamedBuilder('name', 'form')
->add('field1', 'text')
->add('field2', 'text')
->getForm()
->createView();
$this->renderWidget($view['field1']);
// Rest should only contain field2, but isn't rendered
$html = $this->renderEnd($view, array('render_rest' => false));
$this->assertEquals('</form>', $html);
}
} }

View File

@ -11,9 +11,8 @@
namespace Symfony\Component\Form\Tests; namespace Symfony\Component\Form\Tests;
use Symfony\Component\Form\Form; use Symfony\Component\Form\Extension\HttpFoundation\RequestFormProcessor;
use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormError;
use Symfony\Component\Form\Extension\HttpFoundation\EventListener\BindRequestListener;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Form\Tests\Fixtures\FixedDataTransformer; use Symfony\Component\Form\Tests\Fixtures\FixedDataTransformer;
@ -421,14 +420,15 @@ class CompoundFormTest extends AbstractFormTest
)); ));
$form = $this->getBuilder('author') $form = $this->getBuilder('author')
->setMethod($method)
->setCompound(true) ->setCompound(true)
->setDataMapper($this->getDataMapper()) ->setDataMapper($this->getDataMapper())
->addEventSubscriber(new BindRequestListener()) ->setFormProcessor(new RequestFormProcessor())
->getForm(); ->getForm();
$form->add($this->getBuilder('name')->getForm()); $form->add($this->getBuilder('name')->getForm());
$form->add($this->getBuilder('image')->getForm()); $form->add($this->getBuilder('image')->getForm());
$form->bind($request); $form->process($request);
$file = new UploadedFile($path, 'upload.png', 'image/png', 123, UPLOAD_ERR_OK); $file = new UploadedFile($path, 'upload.png', 'image/png', 123, UPLOAD_ERR_OK);
@ -470,14 +470,15 @@ class CompoundFormTest extends AbstractFormTest
)); ));
$form = $this->getBuilder('') $form = $this->getBuilder('')
->setMethod($method)
->setCompound(true) ->setCompound(true)
->setDataMapper($this->getDataMapper()) ->setDataMapper($this->getDataMapper())
->addEventSubscriber(new BindRequestListener()) ->setFormProcessor(new RequestFormProcessor())
->getForm(); ->getForm();
$form->add($this->getBuilder('name')->getForm()); $form->add($this->getBuilder('name')->getForm());
$form->add($this->getBuilder('image')->getForm()); $form->add($this->getBuilder('image')->getForm());
$form->bind($request); $form->process($request);
$file = new UploadedFile($path, 'upload.png', 'image/png', 123, UPLOAD_ERR_OK); $file = new UploadedFile($path, 'upload.png', 'image/png', 123, UPLOAD_ERR_OK);
@ -515,10 +516,11 @@ class CompoundFormTest extends AbstractFormTest
)); ));
$form = $this->getBuilder('image') $form = $this->getBuilder('image')
->addEventSubscriber(new BindRequestListener()) ->setMethod($method)
->setFormProcessor(new RequestFormProcessor())
->getForm(); ->getForm();
$form->bind($request); $form->process($request);
$file = new UploadedFile($path, 'upload.png', 'image/png', 123, UPLOAD_ERR_OK); $file = new UploadedFile($path, 'upload.png', 'image/png', 123, UPLOAD_ERR_OK);
@ -548,10 +550,11 @@ class CompoundFormTest extends AbstractFormTest
)); ));
$form = $this->getBuilder('name') $form = $this->getBuilder('name')
->addEventSubscriber(new BindRequestListener()) ->setMethod($method)
->setFormProcessor(new RequestFormProcessor())
->getForm(); ->getForm();
$form->bind($request); $form->process($request);
$this->assertEquals('Bernhard', $form->getData()); $this->assertEquals('Bernhard', $form->getData());
@ -576,14 +579,15 @@ class CompoundFormTest extends AbstractFormTest
)); ));
$form = $this->getBuilder('author') $form = $this->getBuilder('author')
->setMethod('GET')
->setCompound(true) ->setCompound(true)
->setDataMapper($this->getDataMapper()) ->setDataMapper($this->getDataMapper())
->addEventSubscriber(new BindRequestListener()) ->setFormProcessor(new RequestFormProcessor())
->getForm(); ->getForm();
$form->add($this->getBuilder('firstName')->getForm()); $form->add($this->getBuilder('firstName')->getForm());
$form->add($this->getBuilder('lastName')->getForm()); $form->add($this->getBuilder('lastName')->getForm());
$form->bind($request); $form->process($request);
$this->assertEquals('Bernhard', $form['firstName']->getData()); $this->assertEquals('Bernhard', $form['firstName']->getData());
$this->assertEquals('Schussek', $form['lastName']->getData()); $this->assertEquals('Schussek', $form['lastName']->getData());
@ -606,14 +610,15 @@ class CompoundFormTest extends AbstractFormTest
)); ));
$form = $this->getBuilder('') $form = $this->getBuilder('')
->setMethod('GET')
->setCompound(true) ->setCompound(true)
->setDataMapper($this->getDataMapper()) ->setDataMapper($this->getDataMapper())
->addEventSubscriber(new BindRequestListener()) ->setFormProcessor(new RequestFormProcessor())
->getForm(); ->getForm();
$form->add($this->getBuilder('firstName')->getForm()); $form->add($this->getBuilder('firstName')->getForm());
$form->add($this->getBuilder('lastName')->getForm()); $form->add($this->getBuilder('lastName')->getForm());
$form->bind($request); $form->process($request);
$this->assertEquals('Bernhard', $form['firstName']->getData()); $this->assertEquals('Bernhard', $form['firstName']->getData());
$this->assertEquals('Schussek', $form['lastName']->getData()); $this->assertEquals('Schussek', $form['lastName']->getData());

View File

@ -15,6 +15,7 @@ use Symfony\Component\Form\Extension\HttpFoundation\EventListener\BindRequestLis
use Symfony\Component\Form\Form; use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormConfigBuilder; use Symfony\Component\Form\FormConfigBuilder;
use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\Test\DeprecationErrorHandler;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\File\UploadedFile;
@ -101,7 +102,7 @@ class BindRequestListenerTest extends \PHPUnit_Framework_TestCase
$event = new FormEvent($form, $request); $event = new FormEvent($form, $request);
$listener = new BindRequestListener(); $listener = new BindRequestListener();
$listener->preBind($event); DeprecationErrorHandler::preBind($listener, $event);
$this->assertEquals(array( $this->assertEquals(array(
'name' => 'Bernhard', 'name' => 'Bernhard',
@ -128,7 +129,7 @@ class BindRequestListenerTest extends \PHPUnit_Framework_TestCase
$event = new FormEvent($form, $request); $event = new FormEvent($form, $request);
$listener = new BindRequestListener(); $listener = new BindRequestListener();
$listener->preBind($event); DeprecationErrorHandler::preBind($listener, $event);
$this->assertEquals(array( $this->assertEquals(array(
'name' => 'Bernhard', 'name' => 'Bernhard',
@ -157,7 +158,7 @@ class BindRequestListenerTest extends \PHPUnit_Framework_TestCase
$event = new FormEvent($form, $request); $event = new FormEvent($form, $request);
$listener = new BindRequestListener(); $listener = new BindRequestListener();
$listener->preBind($event); DeprecationErrorHandler::preBind($listener, $event);
// Default to empty array // Default to empty array
$this->assertEquals(array(), $event->getData()); $this->assertEquals(array(), $event->getData());
@ -183,7 +184,7 @@ class BindRequestListenerTest extends \PHPUnit_Framework_TestCase
$event = new FormEvent($form, $request); $event = new FormEvent($form, $request);
$listener = new BindRequestListener(); $listener = new BindRequestListener();
$listener->preBind($event); DeprecationErrorHandler::preBind($listener, $event);
// Default to null // Default to null
$this->assertNull($event->getData()); $this->assertNull($event->getData());
@ -206,7 +207,7 @@ class BindRequestListenerTest extends \PHPUnit_Framework_TestCase
$event = new FormEvent($form, $request); $event = new FormEvent($form, $request);
$listener = new BindRequestListener(); $listener = new BindRequestListener();
$listener->preBind($event); DeprecationErrorHandler::preBind($listener, $event);
$this->assertEquals(array( $this->assertEquals(array(
'name' => 'Bernhard', 'name' => 'Bernhard',
@ -230,7 +231,7 @@ class BindRequestListenerTest extends \PHPUnit_Framework_TestCase
$event = new FormEvent($form, $request); $event = new FormEvent($form, $request);
$listener = new BindRequestListener(); $listener = new BindRequestListener();
$listener->preBind($event); DeprecationErrorHandler::preBind($listener, $event);
$this->assertEquals(array( $this->assertEquals(array(
'name' => 'Bernhard', 'name' => 'Bernhard',
@ -256,7 +257,7 @@ class BindRequestListenerTest extends \PHPUnit_Framework_TestCase
$event = new FormEvent($form, $request); $event = new FormEvent($form, $request);
$listener = new BindRequestListener(); $listener = new BindRequestListener();
$listener->preBind($event); DeprecationErrorHandler::preBind($listener, $event);
$this->assertEquals(array(), $event->getData()); $this->assertEquals(array(), $event->getData());
} }
@ -278,7 +279,7 @@ class BindRequestListenerTest extends \PHPUnit_Framework_TestCase
$event = new FormEvent($form, $request); $event = new FormEvent($form, $request);
$listener = new BindRequestListener(); $listener = new BindRequestListener();
$listener->preBind($event); DeprecationErrorHandler::preBind($listener, $event);
$this->assertNull($event->getData()); $this->assertNull($event->getData());
} }

View File

@ -0,0 +1,54 @@
<?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\HttpFoundation;
use Symfony\Component\Form\Extension\HttpFoundation\RequestFormProcessor;
use Symfony\Component\Form\Tests\AbstractFormProcessorTest;
use Symfony\Component\HttpFoundation\Request;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class RequestFormProcessorTest extends AbstractFormProcessorTest
{
/**
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
*/
public function testRequestShouldNotBeNull()
{
$this->processor->processForm($this->getMockForm('name', 'GET'));
}
/**
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
*/
public function testRequestShouldBeInstanceOfRequest()
{
$this->processor->processForm($this->getMockForm('name', 'GET'), new \stdClass());
}
protected function setRequestData($method, $data, $files = array())
{
$this->request = Request::create('http://localhost', $method, $data, array(), $files);
}
protected function getFormProcessor()
{
return new RequestFormProcessor();
}
protected function getMockFile()
{
return $this->getMockBuilder('Symfony\Component\HttpFoundation\File\UploadedFile')
->disableOriginalConstructor()
->getMock();
}
}

View File

@ -12,12 +12,11 @@
namespace Symfony\Component\Form\Tests; namespace Symfony\Component\Form\Tests;
use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Form\FormConfigBuilder;
/** /**
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
*/ */
use Symfony\Component\Form\FormConfigBuilder;
class FormConfigTest extends \PHPUnit_Framework_TestCase class FormConfigTest extends \PHPUnit_Framework_TestCase
{ {
public function getHtml4Ids() public function getHtml4Ids()
@ -90,4 +89,59 @@ class FormConfigTest extends \PHPUnit_Framework_TestCase
} }
} }
} }
public function testGetFormProcessorCreatesNativeFormProcessorIfNotSet()
{
$config = $this->getConfigBuilder()->getFormConfig();
$this->assertInstanceOf('Symfony\Component\Form\NativeFormProcessor', $config->getFormProcessor());
}
public function testGetFormProcessorReusesNativeFormProcessorInstance()
{
$config1 = $this->getConfigBuilder()->getFormConfig();
$config2 = $this->getConfigBuilder()->getFormConfig();
$this->assertSame($config1->getFormProcessor(), $config2->getFormProcessor());
}
public function testSetMethodAllowsGet()
{
$this->getConfigBuilder()->setMethod('GET');
}
public function testSetMethodAllowsPost()
{
$this->getConfigBuilder()->setMethod('POST');
}
public function testSetMethodAllowsPut()
{
$this->getConfigBuilder()->setMethod('PUT');
}
public function testSetMethodAllowsDelete()
{
$this->getConfigBuilder()->setMethod('DELETE');
}
public function testSetMethodAllowsPatch()
{
$this->getConfigBuilder()->setMethod('PATCH');
}
/**
* @expectedException \Symfony\Component\Form\Exception\FormException
*/
public function testSetMethodDoesNotAllowOtherValues()
{
$this->getConfigBuilder()->setMethod('foo');
}
private function getConfigBuilder($name = 'name')
{
$dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
return new FormConfigBuilder($name, null, $dispatcher);
}
} }

View File

@ -0,0 +1,219 @@
<?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;
use Symfony\Component\Form\NativeFormProcessor;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class NativeFormProcessorTest extends AbstractFormProcessorTest
{
private static $serverBackup;
public static function setUpBeforeClass()
{
self::$serverBackup = $_SERVER;
}
protected function setUp()
{
parent::setUp();
$_GET = array();
$_POST = array();
$_FILES = array();
$_SERVER = array(
// PHPUnit needs this entry
'SCRIPT_NAME' => self::$serverBackup['SCRIPT_NAME'],
);
}
protected function tearDown()
{
parent::tearDown();
$_GET = array();
$_POST = array();
$_FILES = array();
$_SERVER = self::$serverBackup;
}
/**
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
*/
public function testRequestShouldBeNull()
{
$this->processor->processForm($this->getMockForm('name', 'GET'), 'request');
}
public function testMethodOverrideHeaderTakesPrecedenceIfPost()
{
$form = $this->getMockForm('param1', 'PUT');
$this->setRequestData('POST', array(
'param1' => 'DATA',
));
$_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] = 'PUT';
$form->expects($this->once())
->method('bind')
->with('DATA');
$this->processor->processForm($form, $this->request);
}
public function testConvertEmptyUploadedFilesToNull()
{
$form = $this->getMockForm('param1', 'POST', false);
$this->setRequestData('POST', array(), array('param1' => array(
'name' => '',
'type' => '',
'tmp_name' => '',
'error' => UPLOAD_ERR_NO_FILE,
'size' => 0
)));
$form->expects($this->once())
->method('bind')
->with($this->identicalTo(null));
$this->processor->processForm($form, $this->request);
}
public function testFixBuggyFilesArray()
{
$form = $this->getMockForm('param1', 'POST', false);
$this->setRequestData('POST', array(), array('param1' => array(
'name' => array(
'field' => 'upload.txt',
),
'type' => array(
'field' => 'text/plain',
),
'tmp_name' => array(
'field' => 'owfdskjasdfsa',
),
'error' => array(
'field' => UPLOAD_ERR_OK,
),
'size' => array(
'field' => 100,
),
)));
$form->expects($this->once())
->method('bind')
->with(array(
'field' => array(
'name' => 'upload.txt',
'type' => 'text/plain',
'tmp_name' => 'owfdskjasdfsa',
'error' => UPLOAD_ERR_OK,
'size' => 100,
),
));
$this->processor->processForm($form, $this->request);
}
public function testFixBuggyNestedFilesArray()
{
$form = $this->getMockForm('param1', 'POST');
$this->setRequestData('POST', array(), array('param1' => array(
'name' => array(
'field' => array('subfield' => 'upload.txt'),
),
'type' => array(
'field' => array('subfield' => 'text/plain'),
),
'tmp_name' => array(
'field' => array('subfield' => 'owfdskjasdfsa'),
),
'error' => array(
'field' => array('subfield' => UPLOAD_ERR_OK),
),
'size' => array(
'field' => array('subfield' => 100),
),
)));
$form->expects($this->once())
->method('bind')
->with(array(
'field' => array(
'subfield' => array(
'name' => 'upload.txt',
'type' => 'text/plain',
'tmp_name' => 'owfdskjasdfsa',
'error' => UPLOAD_ERR_OK,
'size' => 100,
),
),
));
$this->processor->processForm($form, $this->request);
}
public function testMethodOverrideHeaderIgnoredIfNotPost()
{
$form = $this->getMockForm('param1', 'POST');
$this->setRequestData('GET', array(
'param1' => 'DATA',
));
$_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] = 'PUT';
$form->expects($this->never())
->method('bind');
$this->processor->processForm($form, $this->request);
}
protected function setRequestData($method, $data, $files = array())
{
if ('GET' === $method) {
$_GET = $data;
$_FILES = array();
} else {
$_POST = $data;
$_FILES = $files;
}
$_SERVER = array(
'REQUEST_METHOD' => $method,
// PHPUnit needs this entry
'SCRIPT_NAME' => self::$serverBackup['SCRIPT_NAME'],
);
}
protected function getFormProcessor()
{
return new NativeFormProcessor();
}
protected function getMockFile()
{
return array(
'name' => 'upload.txt',
'type' => 'text/plain',
'tmp_name' => 'owfdskjasdfsa',
'error' => UPLOAD_ERR_OK,
'size' => 100,
);
}
}

View File

@ -275,12 +275,9 @@ class SimpleFormTest extends AbstractFormTest
$this->assertTrue($form->isValid()); $this->assertTrue($form->isValid());
} }
/**
* @expectedException \LogicException
*/
public function testNotValidIfNotBound() public function testNotValidIfNotBound()
{ {
$this->form->isValid(); $this->assertFalse($this->form->isValid());
} }
public function testNotValidIfErrors() public function testNotValidIfErrors()
@ -845,6 +842,21 @@ class SimpleFormTest extends AbstractFormTest
$parent->bind('not-an-array'); $parent->bind('not-an-array');
} }
public function testProcessForwardsToFormProcessor()
{
$processor = $this->getMock('Symfony\Component\Form\FormProcessorInterface');
$form = $this->getBuilder()
->setFormProcessor($processor)
->getForm();
$processor->expects($this->once())
->method('processForm')
->with($this->identicalTo($form), 'REQUEST');
$this->assertSame($form, $form->process('REQUEST'));
}
protected function createForm() protected function createForm()
{ {
return $this->getBuilder()->getForm(); return $this->getBuilder()->getForm();