merged branch bschussek/issue5383 (PR #6528)

This PR was merged into the master branch.

Discussion
----------

[2.3] [Form] Support buttons in forms

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

The general idea of this PR is to be able to add buttons to forms like so:

```php
$builder->add('clickme', 'submit');
```

You can then check in the controller whether the button was clicked:

```php
if ($form->get('clickme')->isClicked()) {
   // do stuff
}
```

Button-specific validation groups are also supported:

```php
$builder->add('clickme', 'submit', array(
    'validation_groups' => 'OnlyClickMe',
));
```

The validation group will then override the one defined in the form if that button is clicked.

This PR also introduces the disabling of form validation by passing the value `false` in the option `validation_groups`:

```php
$builder->add('clickme', 'submit', array(
    'validation_groups' => false,
));
```

The same can be achieved (already before this PR) by passing an empty array:
```php
$builder->add('clickme', 'submit', array(
    'validation_groups' => array(),
));
```

See the linked documentation for more information.

Commits
-------

faf8d7a [Form] Added upgrade information about setting "validation_groups" => false
d504732 [Form] Added leading backslashes to @exceptionMessage doc blocks
c8afa88 [Form] Removed deprecated code scheduled for removal in 2.3
36ca056 [Form] Simplified Twig code
ce29c70 [Form] Fixed incorrect doc comment
0bc7129 [Form] Fixed invalid use of FormException
600007b [Form] The option "validation_groups" can now be set to false to disable validation. This is identical to setting it to an empty array.
277d6df [Form] Fixed concatenation operator CS (see 7c47e34928)
7b07925 [Form] Changed isset() to array_key_exists() to be consistent with ParameterBag
7b438a8 [Form] Made submit buttons able to convey validation groups
cc2118d [Form] Implemented support for buttons
This commit is contained in:
Fabien Potencier 2013-04-17 19:40:27 +02:00
commit 5a158ea08b
62 changed files with 2404 additions and 395 deletions

37
UPGRADE-2.3.md Normal file
View File

@ -0,0 +1,37 @@
UPGRADE FROM 2.2 to 2.3
=======================
### Form
* Although this was not officially supported nor documented, it was possible to
set the option "validation_groups" to false, resulting in the group "Default"
being validated. Now, if you set "validation_groups" to false, the validation
of a form will be skipped (except for a few integrity checks on the form).
If you want to validate a form in group "Default", you should either
explicitly set "validation_groups" to "Default" or alternatively set it to
null.
Before:
```
// equivalent notations for validating in group "Default"
"validation_groups" => null
"validation_groups" => "Default"
"validation_groups" => false
// notation for skipping validation
"validation_groups" => array()
```
After:
```
// equivalent notations for validating in group "Default"
"validation_groups" => null
"validation_groups" => "Default"
// equivalent notations for skipping validation
"validation_groups" => false
"validation_groups" => array()
```

View File

@ -218,6 +218,29 @@
{% endspaceless %}
{% endblock email_widget %}
{% block button_widget %}
{% spaceless %}
{% if label is empty %}
{% set label = name|humanize %}
{% endif %}
<button type="{{ type|default('button') }}" {{ block('button_attributes') }}>{{ label|trans({}, translation_domain) }}</button>
{% endspaceless %}
{% endblock button_widget %}
{% block submit_widget %}
{% spaceless %}
{% set type = type|default('submit') %}
{{ block('button_widget') }}
{% endspaceless %}
{% endblock submit_widget %}
{% block reset_widget %}
{% spaceless %}
{% set type = type|default('reset') %}
{{ block('button_widget') }}
{% endspaceless %}
{% endblock reset_widget %}
{# Labels #}
{% block form_label %}
@ -237,6 +260,8 @@
{% endspaceless %}
{% endblock form_label %}
{% block button_label %}{% endblock %}
{# Rows #}
{% block repeated_row %}
@ -259,6 +284,14 @@
{% endspaceless %}
{% endblock form_row %}
{% block button_row %}
{% spaceless %}
<div>
{{ form_widget(form) }}
</div>
{% endspaceless %}
{% endblock button_row %}
{% block hidden_row %}
{{ form_widget(form) }}
{% endblock hidden_row %}
@ -317,14 +350,9 @@
{% endspaceless %}
{% endblock widget_container_attributes %}
{# Deprecated in Symfony 2.1, to be removed in 2.3 #}
{% block generic_label %}{{ block('form_label') }}{% endblock %}
{% block widget_choice_options %}{{ block('choice_widget_options') }}{% endblock %}
{% block field_widget %}{{ block('form_widget_simple') }}{% endblock %}
{% block field_label %}{{ block('form_label') }}{% endblock %}
{% block field_row %}{{ block('form_row') }}{% endblock %}
{% block field_enctype %}{{ block('form_enctype') }}{% endblock %}
{% block field_errors %}{{ block('form_errors') }}{% endblock %}
{% block field_rest %}{{ block('form_rest') }}{% endblock %}
{% block field_rows %}{{ block('form_rows') }}{% endblock %}
{% block button_attributes %}
{% spaceless %}
id="{{ id }}" name="{{ full_name }}"{% if disabled %} disabled="disabled"{% endif %}
{% for attrname, attrvalue in attr %}{{ attrname }}="{{ attrvalue }}" {% endfor %}
{% endspaceless %}
{% endblock button_attributes %}

View File

@ -14,6 +14,17 @@
{% endspaceless %}
{% endblock form_row %}
{% block button_row %}
{% spaceless %}
<tr>
<td></td>
<td colspan="2">
{{ form_widget(form) }}
</td>
</tr>
{% endspaceless %}
{% endblock button_row %}
{% block hidden_row %}
{% spaceless %}
<tr style="display: none">

View File

@ -140,6 +140,15 @@
<service id="form.type.url" class="Symfony\Component\Form\Extension\Core\Type\UrlType">
<tag name="form.type" alias="url" />
</service>
<service id="form.type.button" class="Symfony\Component\Form\Extension\Core\Type\ButtonType">
<tag name="form.type" alias="button" />
</service>
<service id="form.type.submit" class="Symfony\Component\Form\Extension\Core\Type\SubmitType">
<tag name="form.type" alias="submit" />
</service>
<service id="form.type.reset" class="Symfony\Component\Form\Extension\Core\Type\ResetType">
<tag name="form.type" alias="reset" />
</service>
<!-- FormTypeHttpFoundationExtension -->
<service id="form.type_extension.form.http_foundation" class="Symfony\Component\Form\Extension\HttpFoundation\Type\FormTypeHttpFoundationExtension">
@ -154,5 +163,8 @@
<service id="form.type_extension.repeated.validator" class="Symfony\Component\Form\Extension\Validator\Type\RepeatedTypeValidatorExtension">
<tag name="form.type_extension" alias="repeated" />
</service>
<service id="form.type_extension.submit.validator" class="Symfony\Component\Form\Extension\Validator\Type\SubmitTypeValidatorExtension">
<tag name="form.type_extension" alias="submit" />
</service>
</services>
</container>

View File

@ -0,0 +1,6 @@
id="<?php echo $view->escape($id) ?>"
name="<?php echo $view->escape($full_name) ?>"
<?php if ($disabled): ?>disabled="disabled" <?php endif ?>
<?php foreach ($attr as $k => $v): ?>
<?php printf('%s="%s" ', $view->escape($k), $view->escape($v)) ?>
<?php endforeach; ?>

View File

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

View File

@ -0,0 +1,4 @@
<?php if (!$label) { $label = $view['form']->humanize($name); } ?>
<button type="<?php echo isset($type) ? $view->escape($type) : 'button' ?>" <?php echo $view['form']->block($form, 'button_attributes') ?>>
<?php $view->escape($view['translator']->trans($label, array(), $translation_domain)) ?>
</button>

View File

@ -1 +0,0 @@
<?php echo $view['form']->block($form, 'form_enctype') ?>

View File

@ -1 +0,0 @@
<?php echo $view['form']->block($form, 'form_errors') ?>

View File

@ -1 +0,0 @@
<?php echo $view['form']->block($form, 'form_label') ?>

View File

@ -1 +0,0 @@
<?php echo $view['form']->block($form, 'form_rest') ?>

View File

@ -1 +0,0 @@
<?php echo $view['form']->block($form, 'form_row') ?>

View File

@ -1 +0,0 @@
<?php echo $view['form']->block($form, 'form_rows') ?>

View File

@ -1 +0,0 @@
<?php echo $view['form']->block($form, 'form_widget_simple') ?>

View File

@ -0,0 +1 @@
<?php echo $view['form']->block($form, 'button_widget', array('type' => isset($type) ? $type : 'reset')) ?>

View File

@ -0,0 +1 @@
<?php echo $view['form']->block($form, 'button_widget', array('type' => isset($type) ? $type : 'submit')) ?>

View File

@ -0,0 +1,6 @@
<tr>
<td></td>
<td>
<?php echo $view['form']->widget($form) ?>
</td>
</tr>

View File

@ -25,7 +25,7 @@ class ArrayNodeTest extends \PHPUnit_Framework_TestCase
}
/**
* @expectedException Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedExceptionMessage Unrecognized options "foo" under "root"
*/
public function testExceptionThrownOnUnrecognizedChild()

View File

@ -0,0 +1,412 @@
<?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\AlreadyBoundException;
/**
* A form button.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class Button implements \IteratorAggregate, FormInterface
{
/**
* @var FormInterface
*/
private $parent;
/**
* @var FormConfigInterface
*/
private $config;
/**
* @var Boolean
*/
private $bound = false;
/**
* Creates a new button from a form configuration.
*
* @param FormConfigInterface $config The button's configuration.
*/
public function __construct(FormConfigInterface $config)
{
$this->config = $config;
}
/**
* Unsupported method.
*
* @param mixed $offset
*
* @return Boolean Always returns false.
*/
public function offsetExists($offset)
{
return false;
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param mixed $offset
*
* @throws \BadMethodCallException
*/
public function offsetGet($offset)
{
throw new \BadMethodCallException('Buttons cannot have children.');
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param mixed $offset
* @param mixed $value
*
* @throws \BadMethodCallException
*/
public function offsetSet($offset, $value)
{
throw new \BadMethodCallException('Buttons cannot have children.');
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param mixed $offset
*
* @throws \BadMethodCallException
*/
public function offsetUnset($offset)
{
throw new \BadMethodCallException('Buttons cannot have children.');
}
/**
* {@inheritdoc}
*/
public function setParent(FormInterface $parent = null)
{
$this->parent = $parent;
}
/**
* {@inheritdoc}
*/
public function getParent()
{
return $this->parent;
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param int|string|FormInterface $child
* @param null $type
* @param array $options
*
* @throws \BadMethodCallException
*/
public function add($child, $type = null, array $options = array())
{
throw new \BadMethodCallException('Buttons cannot have children.');
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param string $name
*
* @throws \BadMethodCallException
*/
public function get($name)
{
throw new \BadMethodCallException('Buttons cannot have children.');
}
/**
* Unsupported method.
*
* @param string $name
*
* @return Boolean Always returns false.
*/
public function has($name)
{
return false;
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param string $name
*
* @throws \BadMethodCallException
*/
public function remove($name)
{
throw new \BadMethodCallException('Buttons cannot have children.');
}
/**
* {@inheritdoc}
*/
public function all()
{
return array();
}
/**
* {@inheritdoc}
*/
public function getErrors()
{
return array();
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param string $modelData
*
* @throws \BadMethodCallException
*/
public function setData($modelData)
{
throw new \BadMethodCallException('Buttons cannot have data.');
}
/**
* Unsupported method.
*
* @return null Always returns null.
*/
public function getData()
{
return null;
}
/**
* Unsupported method.
*
* @return null Always returns null.
*/
public function getNormData()
{
return null;
}
/**
* Unsupported method.
*
* @return null Always returns null.
*/
public function getViewData()
{
return null;
}
/**
* Unsupported method.
*
* @return array Always returns an empty array.
*/
public function getExtraData()
{
return array();
}
/**
* Returns the button's configuration.
*
* @return FormConfigInterface The configuration.
*/
public function getConfig()
{
return $this->config;
}
/**
* Returns whether the button is submitted.
*
* @return Boolean true if the button was submitted.
*/
public function isBound()
{
return $this->bound;
}
/**
* Returns the name by which the button is identified in forms.
*
* @return string The name of the button.
*/
public function getName()
{
return $this->config->getName();
}
/**
* Unsupported method.
*
* @return null Always returns null.
*/
public function getPropertyPath()
{
return null;
}
/**
* Unsupported method.
*
* @param FormError $error
*
* @throws \BadMethodCallException
*/
public function addError(FormError $error)
{
throw new \BadMethodCallException('Buttons cannot have errors.');
}
/**
* Unsupported method.
*
* @return Boolean Always returns true.
*/
public function isValid()
{
return true;
}
/**
* Unsupported method.
*
* @return Boolean Always returns false.
*/
public function isRequired()
{
return false;
}
/**
* {@inheritdoc}
*/
public function isDisabled()
{
return $this->config->getDisabled();
}
/**
* Unsupported method.
*
* @return Boolean Always returns true.
*/
public function isEmpty()
{
return true;
}
/**
* Unsupported method.
*
* @return Boolean Always returns true.
*/
public function isSynchronized()
{
return true;
}
/**
* Binds data to the button.
*
* @param null|string $submittedData The data
*
* @return Button The button instance
*
* @throws Exception\AlreadyBoundException If the form has already been bound.
*/
public function bind($submittedData)
{
if ($this->bound) {
throw new AlreadyBoundException('A form can only be bound once');
}
$this->bound = true;
return $this;
}
/**
* {@inheritdoc}
*/
public function getRoot()
{
return $this->parent ? $this->parent->getRoot() : $this;
}
/**
* {@inheritdoc}
*/
public function isRoot()
{
return null === $this->parent;
}
/**
* {@inheritdoc}
*/
public function createView(FormView $parent = null)
{
if (null === $parent && $this->parent) {
$parent = $this->parent->createView();
}
return $this->config->getType()->createView($this, $parent);
}
/**
* Unsupported method.
*
* @return integer Always returns 0.
*/
public function count()
{
return 0;
}
/**
* Unsupported method.
*
* @return \EmptyIterator Always returns an empty iterator.
*/
public function getIterator()
{
return new \EmptyIterator();
}
}

View File

@ -0,0 +1,750 @@
<?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\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\Exception\FormException;
use Symfony\Component\Form\Exception\InvalidArgumentException;
/**
* A builder for {@link Button} instances.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface
{
/**
* @var Boolean
*/
protected $locked = false;
/**
* @var Boolean
*/
private $disabled;
/**
* @var ResolvedFormTypeInterface
*/
private $type;
/**
* @var string
*/
private $name;
/**
* @var array
*/
private $attributes = array();
/**
* @var array
*/
private $options;
/**
* Creates a new button builder.
*
* @param string $name The name of the button.
* @param array $options The button's options.
*
* @throws FormException If the name is empty.
*/
public function __construct($name, array $options)
{
if (empty($name) && 0 != $name) {
throw new InvalidArgumentException('Buttons cannot have empty names.');
}
$this->name = (string) $name;
$this->options = $options;
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param string|integer|FormBuilderInterface $child
* @param string|FormTypeInterface $type
* @param array $options
*
* @throws \BadMethodCallException
*/
public function add($child, $type = null, array $options = array())
{
throw new \BadMethodCallException('Buttons cannot have children.');
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param string $name
* @param string|FormTypeInterface $type
* @param array $options
*
* @throws \BadMethodCallException
*/
public function create($name, $type = null, array $options = array())
{
throw new \BadMethodCallException('Buttons cannot have children.');
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param string $name
*
* @throws \BadMethodCallException
*/
public function get($name)
{
throw new \BadMethodCallException('Buttons cannot have children.');
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param string $name
*
* @throws \BadMethodCallException
*/
public function remove($name)
{
throw new \BadMethodCallException('Buttons cannot have children.');
}
/**
* Unsupported method.
*
* @param string $name
*
* @return Boolean Always returns false.
*/
public function has($name)
{
return false;
}
/**
* Returns the children.
*
* @return array Always returns an empty array.
*/
public function all()
{
return array();
}
/**
* Creates the button.
*
* @return Button The button
*/
public function getForm()
{
return new Button($this->getFormConfig());
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param string $eventName
* @param callable $listener
* @param integer $priority
*
* @throws \BadMethodCallException
*/
public function addEventListener($eventName, $listener, $priority = 0)
{
throw new \BadMethodCallException('Buttons do not support event listeners.');
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param EventSubscriberInterface $subscriber
*
* @throws \BadMethodCallException
*/
public function addEventSubscriber(EventSubscriberInterface $subscriber)
{
throw new \BadMethodCallException('Buttons do not support event subscribers.');
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param DataTransformerInterface $viewTransformer
* @param Boolean $forcePrepend
*
* @throws \BadMethodCallException
*/
public function addViewTransformer(DataTransformerInterface $viewTransformer, $forcePrepend = false)
{
throw new \BadMethodCallException('Buttons do not support data transformers.');
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @throws \BadMethodCallException
*/
public function resetViewTransformers()
{
throw new \BadMethodCallException('Buttons do not support data transformers.');
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param DataTransformerInterface $modelTransformer
* @param Boolean $forceAppend
*
* @throws \BadMethodCallException
*/
public function addModelTransformer(DataTransformerInterface $modelTransformer, $forceAppend = false)
{
throw new \BadMethodCallException('Buttons do not support data transformers.');
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @throws \BadMethodCallException
*/
public function resetModelTransformers()
{
throw new \BadMethodCallException('Buttons do not support data transformers.');
}
/**
* {@inheritdoc}
*/
public function setAttribute($name, $value)
{
$this->attributes[$name] = $value;
}
/**
* {@inheritdoc}
*/
public function setAttributes(array $attributes)
{
$this->attributes = $attributes;
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param DataMapperInterface $dataMapper
*
* @throws \BadMethodCallException
*/
public function setDataMapper(DataMapperInterface $dataMapper = null)
{
throw new \BadMethodCallException('Buttons do not support data mappers.');
}
/**
* Set whether the button is disabled.
*
* @param Boolean $disabled Whether the button is disabled
*
* @return ButtonBuilder The button builder.
*/
public function setDisabled($disabled)
{
$this->disabled = $disabled;
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param mixed $emptyData
*
* @throws \BadMethodCallException
*/
public function setEmptyData($emptyData)
{
throw new \BadMethodCallException('Buttons do not support empty data.');
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param Boolean $errorBubbling
*
* @throws \BadMethodCallException
*/
public function setErrorBubbling($errorBubbling)
{
throw new \BadMethodCallException('Buttons do not support error bubbling.');
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param Boolean $required
*
* @throws \BadMethodCallException
*/
public function setRequired($required)
{
throw new \BadMethodCallException('Buttons cannot be required.');
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param null $propertyPath
*
* @throws \BadMethodCallException
*/
public function setPropertyPath($propertyPath)
{
throw new \BadMethodCallException('Buttons do not support property paths.');
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param Boolean $mapped
*
* @throws \BadMethodCallException
*/
public function setMapped($mapped)
{
throw new \BadMethodCallException('Buttons do not support data mapping.');
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param Boolean $byReference
*
* @throws \BadMethodCallException
*/
public function setByReference($byReference)
{
throw new \BadMethodCallException('Buttons do not support data mapping.');
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param Boolean $virtual
*
* @throws \BadMethodCallException
*/
public function setVirtual($virtual)
{
throw new \BadMethodCallException('Buttons cannot be virtual.');
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param Boolean $compound
*
* @throws \BadMethodCallException
*/
public function setCompound($compound)
{
throw new \BadMethodCallException('Buttons cannot be compound.');
}
/**
* Sets the type of the button.
*
* @param ResolvedFormTypeInterface $type The type of the button.
*
* @return ButtonBuilder The button builder.
*/
public function setType(ResolvedFormTypeInterface $type)
{
$this->type = $type;
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param array $data
*
* @throws \BadMethodCallException
*/
public function setData($data)
{
throw new \BadMethodCallException('Buttons do not support data.');
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param Boolean $locked
*
* @throws \BadMethodCallException
*/
public function setDataLocked($locked)
{
throw new \BadMethodCallException('Buttons do not support data locking.');
}
/**
* Unsupported method.
*
* This method should not be invoked.
*
* @param FormFactoryInterface $formFactory
*
* @return void
*
* @throws \BadMethodCallException
*/
public function setFormFactory(FormFactoryInterface $formFactory)
{
throw new \BadMethodCallException('Buttons do not support form factories.');
}
/**
* Builds and returns the button configuration.
*
* @return FormConfigInterface
*/
public function getFormConfig()
{
// This method should be idempotent, so clone the builder
$config = clone $this;
$config->locked = true;
return $config;
}
/**
* Unsupported method.
*
* @return null Always returns null.
*/
public function getEventDispatcher()
{
return null;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->name;
}
/**
* Unsupported method.
*
* @return null Always returns null.
*/
public function getPropertyPath()
{
return null;
}
/**
* Unsupported method.
*
* @return Boolean Always returns false.
*/
public function getMapped()
{
return false;
}
/**
* Unsupported method.
*
* @return Boolean Always returns false.
*/
public function getByReference()
{
return false;
}
/**
* Unsupported method.
*
* @return Boolean Always returns false.
*/
public function getVirtual()
{
return false;
}
/**
* Unsupported method.
*
* @return Boolean Always returns false.
*/
public function getCompound()
{
return false;
}
/**
* Returns the form type used to construct the button.
*
* @return ResolvedFormTypeInterface The button's type.
*/
public function getType()
{
return $this->type;
}
/**
* Unsupported method.
*
* @return array Always returns an empty array.
*/
public function getViewTransformers()
{
return array();
}
/**
* Unsupported method.
*
* @return array Always returns an empty array.
*/
public function getModelTransformers()
{
return array();
}
/**
* Unsupported method.
*
* @return null Always returns null.
*/
public function getDataMapper()
{
return null;
}
/**
* Unsupported method.
*
* @return Boolean Always returns false.
*/
public function getRequired()
{
return false;
}
/**
* Returns whether the button is disabled.
*
* @return Boolean Whether the button is disabled.
*/
public function getDisabled()
{
return $this->disabled;
}
/**
* Unsupported method.
*
* @return Boolean Always returns false.
*/
public function getErrorBubbling()
{
return false;
}
/**
* Unsupported method.
*
* @return null Always returns null.
*/
public function getEmptyData()
{
return null;
}
/**
* Returns additional attributes of the button.
*
* @return array An array of key-value combinations.
*/
public function getAttributes()
{
return $this->attributes;
}
/**
* Returns whether the attribute with the given name exists.
*
* @param string $name The attribute name.
*
* @return Boolean Whether the attribute exists.
*/
public function hasAttribute($name)
{
return array_key_exists($name, $this->attributes);
}
/**
* Returns the value of the given attribute.
*
* @param string $name The attribute name.
* @param mixed $default The value returned if the attribute does not exist.
*
* @return mixed The attribute value.
*/
public function getAttribute($name, $default = null)
{
return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default;
}
/**
* Unsupported method.
*
* @return null Always returns null.
*/
public function getData()
{
return null;
}
/**
* Unsupported method.
*
* @return null Always returns null.
*/
public function getDataClass()
{
return null;
}
/**
* Unsupported method.
*
* @return Boolean Always returns false.
*/
public function getDataLocked()
{
return false;
}
/**
* Unsupported method.
*
* @return null Always returns null.
*/
public function getFormFactory()
{
return null;
}
/**
* Returns all options passed during the construction of the button.
*
* @return array The passed options.
*/
public function getOptions()
{
return $this->options;
}
/**
* Returns whether a specific option exists.
*
* @param string $name The option name,
*
* @return Boolean Whether the option exists.
*/
public function hasOption($name)
{
return array_key_exists($name, $this->options);
}
/**
* Returns the value of a specific option.
*
* @param string $name The option name.
* @param mixed $default The value returned if the option does not exist.
*
* @return mixed The option value.
*/
public function getOption($name, $default = null)
{
return array_key_exists($name, $this->options) ? $this->options[$name] : $default;
}
/**
* Unsupported method.
*
* @return integer Always returns 0.
*/
public function count()
{
return 0;
}
/**
* Unsupported method.
*
* @return \EmptyIterator Always returns an empty iterator.
*/
public function getIterator()
{
return new \EmptyIterator();
}
}

View File

@ -0,0 +1,21 @@
<?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;
/**
* A type that should be converted into a {@link Button} instance.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface ButtonTypeInterface extends FormTypeInterface
{
}

View File

@ -28,6 +28,8 @@ CHANGELOG
* added an optional PropertyAccessorInterface parameter to FormType,
ObjectChoiceList and PropertyPathMapper
* [BC BREAK] PropertyPathMapper and FormType now have a constructor
* [BC BREAK] setting the option "validation_groups" to ``false`` now disables validation
instead of assuming group "Default"
2.1.0
-----

View File

@ -0,0 +1,27 @@
<?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;
/**
* A clickable form element.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface ClickableInterface
{
/**
* Returns whether this element was clicked.
*
* @return Boolean Whether this element was clicked.
*/
public function isClicked();
}

View File

@ -0,0 +1,21 @@
<?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;
/**
* Base InvalidArgumentException for the Form component.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}

View File

@ -50,6 +50,9 @@ class CoreExtension extends AbstractExtension
new Type\TimezoneType(),
new Type\UrlType(),
new Type\FileType(),
new Type\ButtonType(),
new Type\SubmitType(),
new Type\ResetType(),
);
}
}

View File

@ -0,0 +1,121 @@
<?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\Core\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
/**
* Encapsulates common logic of {@link FormType} and {@link ButtonType}.
*
* This type does not appear in the form's type inheritance chain and as such
* cannot be extended (via {@link FormTypeExtension}s) nor themed.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
abstract class BaseType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->setDisabled($options['disabled']);
}
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$name = $form->getName();
$blockName = $options['block_name'] ?: $form->getName();
$translationDomain = $options['translation_domain'];
if ($view->parent) {
if ('' !== ($parentFullName = $view->parent->vars['full_name'])) {
$id = sprintf('%s_%s', $view->parent->vars['id'], $name);
$fullName = sprintf('%s[%s]', $parentFullName, $name);
$uniqueBlockPrefix = sprintf('%s_%s', $view->parent->vars['unique_block_prefix'], $blockName);
} else {
$id = $name;
$fullName = $name;
$uniqueBlockPrefix = '_'.$blockName;
}
if (!$translationDomain) {
$translationDomain = $view->parent->vars['translation_domain'];
}
} else {
$id = $name;
$fullName = $name;
$uniqueBlockPrefix = '_'.$blockName;
// 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');
}
$blockPrefixes = array();
for ($type = $form->getConfig()->getType(); null !== $type; $type = $type->getParent()) {
array_unshift($blockPrefixes, $type->getName());
}
$blockPrefixes[] = $uniqueBlockPrefix;
if (!$translationDomain) {
$translationDomain = 'messages';
}
$view->vars = array_replace($view->vars, array(
'form' => $view,
'id' => $id,
'name' => $name,
'full_name' => $fullName,
'disabled' => $form->isDisabled(),
'label' => $options['label'],
'multipart' => false,
'attr' => $options['attr'],
'block_prefixes' => $blockPrefixes,
'unique_block_prefix' => $uniqueBlockPrefix,
'translation_domain' => $translationDomain,
// Using the block name here speeds up performance in collection
// forms, where each entry has the same full block name.
// Including the type is important too, because if rows of a
// collection form have different types (dynamically), they should
// be rendered differently.
// https://github.com/symfony/symfony/issues/5038
'cache_key' => $uniqueBlockPrefix.'_'.$form->getConfig()->getType()->getName(),
));
}
/**
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'block_name' => null,
'disabled' => false,
'label' => null,
'attr' => array(),
'translation_domain' => null,
));
$resolver->setAllowedTypes(array(
'attr' => 'array',
));
}
}

View File

@ -0,0 +1,38 @@
<?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\Core\Type;
use Symfony\Component\Form\ButtonTypeInterface;
/**
* A form button.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ButtonType extends BaseType implements ButtonTypeInterface
{
/**
* {@inheritdoc}
*/
public function getParent()
{
return null;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'button';
}
}

View File

@ -11,7 +11,6 @@
namespace Symfony\Component\Form\Extension\Core\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
@ -23,7 +22,7 @@ use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
class FormType extends AbstractType
class FormType extends BaseType
{
/**
* @var PropertyAccessorInterface
@ -40,9 +39,10 @@ class FormType extends AbstractType
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$builder
->setRequired($options['required'])
->setDisabled($options['disabled'])
->setErrorBubbling($options['error_bubbling'])
->setEmptyData($options['empty_data'])
->setPropertyPath($options['property_path'])
@ -65,85 +65,34 @@ class FormType extends AbstractType
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
parent::buildView($view, $form, $options);
$name = $form->getName();
$blockName = $options['block_name'] ?: $form->getName();
$readOnly = $options['read_only'];
$translationDomain = $options['translation_domain'];
if ($view->parent) {
if ('' === $name) {
throw new Exception('Form node with empty name can be used only as root form node.');
}
if ('' !== ($parentFullName = $view->parent->vars['full_name'])) {
$id = sprintf('%s_%s', $view->parent->vars['id'], $name);
$fullName = sprintf('%s[%s]', $parentFullName, $name);
$uniqueBlockPrefix = sprintf('%s_%s', $view->parent->vars['unique_block_prefix'], $blockName);
} else {
$id = $name;
$fullName = $name;
$uniqueBlockPrefix = '_'.$blockName;
}
// Complex fields are read-only if they themselves or their parents are.
if (!$readOnly) {
$readOnly = $view->parent->vars['read_only'];
}
if (!$translationDomain) {
$translationDomain = $view->parent->vars['translation_domain'];
}
} else {
$id = $name;
$fullName = $name;
$uniqueBlockPrefix = '_'.$blockName;
// 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');
}
$blockPrefixes = array();
for ($type = $form->getConfig()->getType(); null !== $type; $type = $type->getParent()) {
array_unshift($blockPrefixes, $type->getName());
}
$blockPrefixes[] = $uniqueBlockPrefix;
if (!$translationDomain) {
$translationDomain = 'messages';
}
$view->vars = array_replace($view->vars, array(
'form' => $view,
'id' => $id,
'name' => $name,
'full_name' => $fullName,
'read_only' => $readOnly,
'errors' => $form->getErrors(),
'valid' => $form->isBound() ? $form->isValid() : true,
'value' => $form->getViewData(),
'data' => $form->getNormData(),
'disabled' => $form->isDisabled(),
'required' => $form->isRequired(),
'max_length' => $options['max_length'],
'pattern' => $options['pattern'],
'size' => null,
'label' => $options['label'],
'multipart' => false,
'attr' => $options['attr'],
'label_attr' => $options['label_attr'],
'compound' => $form->getConfig()->getCompound(),
'block_prefixes' => $blockPrefixes,
'unique_block_prefix' => $uniqueBlockPrefix,
'translation_domain' => $translationDomain,
// Using the block name here speeds up performance in collection
// forms, where each entry has the same full block name.
// Including the type is important too, because if rows of a
// collection form have different types (dynamically), they should
// be rendered differently.
// https://github.com/symfony/symfony/issues/5038
'cache_key' => $uniqueBlockPrefix.'_'.$form->getConfig()->getType()->getName(),
'read_only' => $readOnly,
'errors' => $form->getErrors(),
'valid' => $form->isBound() ? $form->isValid() : true,
'value' => $form->getViewData(),
'data' => $form->getNormData(),
'required' => $form->isRequired(),
'max_length' => $options['max_length'],
'pattern' => $options['pattern'],
'size' => null,
'label_attr' => $options['label_attr'],
'compound' => $form->getConfig()->getCompound(),
));
}
@ -169,6 +118,8 @@ class FormType extends AbstractType
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
parent::setDefaultOptions($resolver);
// Derive "data_class" option from passed "data" object
$dataClass = function (Options $options) {
return isset($options['data']) && is_object($options['data']) ? get_class($options['data']) : null;
@ -202,29 +153,23 @@ class FormType extends AbstractType
));
$resolver->setDefaults(array(
'block_name' => null,
'data_class' => $dataClass,
'empty_data' => $emptyData,
'trim' => true,
'required' => true,
'read_only' => false,
'disabled' => false,
'max_length' => null,
'pattern' => null,
'property_path' => null,
'mapped' => true,
'by_reference' => true,
'error_bubbling' => $errorBubbling,
'label' => null,
'attr' => array(),
'label_attr' => array(),
'virtual' => false,
'compound' => true,
'translation_domain' => null,
));
$resolver->setAllowedTypes(array(
'attr' => 'array',
'label_attr' => 'array',
));
}

View File

@ -0,0 +1,39 @@
<?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\Core\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\ButtonTypeInterface;
/**
* A reset button.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ResetType extends AbstractType implements ButtonTypeInterface
{
/**
* {@inheritdoc}
*/
public function getParent()
{
return 'button';
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'reset';
}
}

View File

@ -0,0 +1,46 @@
<?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\Core\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\SubmitButtonTypeInterface;
/**
* A submit button.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class SubmitType extends AbstractType implements SubmitButtonTypeInterface
{
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['clicked'] = $form->isClicked();
}
/**
* {@inheritdoc}
*/
public function getParent()
{
return 'button';
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'submit';
}
}

View File

@ -11,6 +11,7 @@
namespace Symfony\Component\Form\Extension\Validator\Constraints;
use Symfony\Component\Form\ClickableInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\Extension\Validator\Util\ServerParams;
use Symfony\Component\Validator\Constraint;
@ -171,15 +172,21 @@ class FormValidator extends ConstraintValidator
*/
private static function getValidationGroups(FormInterface $form)
{
$button = self::findClickedButton($form->getRoot());
if (null !== $button) {
$groups = $button->getConfig()->getOption('validation_groups');
if (null !== $groups) {
return self::resolveValidationGroups($groups, $form);
}
}
do {
$groups = $form->getConfig()->getOption('validation_groups');
if (null !== $groups) {
if (is_callable($groups)) {
$groups = call_user_func($groups, $form);
}
return (array) $groups;
return self::resolveValidationGroups($groups, $form);
}
$form = $form->getParent();
@ -187,4 +194,43 @@ class FormValidator extends ConstraintValidator
return array(Constraint::DEFAULT_GROUP);
}
/**
* Extracts a clicked button from a form tree, if one exists.
*
* @param FormInterface $form The root form.
*
* @return ClickableInterface|null The clicked button or null.
*/
private static function findClickedButton(FormInterface $form)
{
if ($form instanceof ClickableInterface && $form->isClicked()) {
return $form;
}
foreach ($form as $child) {
if (null !== ($button = self::findClickedButton($child))) {
return $button;
}
}
return null;
}
/**
* Post-processes the validation groups option for a given form.
*
* @param array|callable $groups The validation groups.
* @param FormInterface $form The validated form.
*
* @return array The validation groups.
*/
private static function resolveValidationGroups($groups, FormInterface $form)
{
if (is_callable($groups)) {
$groups = call_user_func($groups, $form);
}
return (array) $groups;
}
}

View File

@ -0,0 +1,56 @@
<?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\Validator\Type;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
/**
* Encapsulates common logic of {@link FormTypeValidatorExtension} and
* {@link SubmitTypeValidatorExtension}.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
abstract class BaseValidatorExtension extends AbstractTypeExtension
{
/**
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
// Make sure that validation groups end up as null, closure or array
$validationGroupsNormalizer = function (Options $options, $groups) {
if (false === $groups) {
return array();
}
if (empty($groups)) {
return null;
}
if (is_callable($groups)) {
return $groups;
}
return (array) $groups;
};
$resolver->setDefaults(array(
'validation_groups' => null,
));
$resolver->setNormalizers(array(
'validation_groups' => $validationGroupsNormalizer,
));
}
}

View File

@ -22,7 +22,7 @@ use Symfony\Component\OptionsResolver\OptionsResolverInterface;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class FormTypeValidatorExtension extends AbstractTypeExtension
class FormTypeValidatorExtension extends BaseValidatorExtension
{
/**
* @var ValidatorInterface
@ -53,18 +53,7 @@ class FormTypeValidatorExtension extends AbstractTypeExtension
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
// Make sure that validation groups end up as null, closure or array
$validationGroupsNormalizer = function (Options $options, $groups) {
if (empty($groups)) {
return null;
}
if (is_callable($groups)) {
return $groups;
}
return (array) $groups;
};
parent::setDefaultOptions($resolver);
// Constraint should always be converted to an array
$constraintsNormalizer = function (Options $options, $constraints) {
@ -73,8 +62,8 @@ class FormTypeValidatorExtension extends AbstractTypeExtension
$resolver->setDefaults(array(
'error_mapping' => array(),
'validation_groups' => null,
'constraints' => null,
'validation_constraint' => null,
'constraints' => array(),
'cascade_validation' => false,
'invalid_message' => 'This value is not valid.',
'invalid_message_parameters' => array(),
@ -83,7 +72,6 @@ class FormTypeValidatorExtension extends AbstractTypeExtension
));
$resolver->setNormalizers(array(
'validation_groups' => $validationGroupsNormalizer,
'constraints' => $constraintsNormalizer,
));
}

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\Extension\Validator\Type;
use Symfony\Component\Form\AbstractTypeExtension;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class SubmitTypeValidatorExtension extends AbstractTypeExtension
{
/**
* {@inheritdoc}
*/
public function getExtendedType()
{
return 'submit';
}
}

View File

@ -51,6 +51,7 @@ class ValidatorExtension extends AbstractExtension
return array(
new Type\FormTypeValidatorExtension($this->validator),
new Type\RepeatedTypeValidatorExtension(),
new Type\SubmitTypeValidatorExtension(),
);
}
}

View File

@ -37,13 +37,6 @@ class FormBuilder extends FormConfigBuilder implements \IteratorAggregate, FormB
*/
private $unresolvedChildren = array();
/**
* The parent of this builder.
*
* @var FormBuilder
*/
private $parent;
/**
* Creates a new form builder.
*
@ -70,7 +63,6 @@ class FormBuilder extends FormConfigBuilder implements \IteratorAggregate, FormB
}
if ($child instanceof self) {
$child->setParent($this);
$this->children[$child->getName()] = $child;
// In case an unresolved child with the same name exists
@ -111,10 +103,10 @@ class FormBuilder extends FormConfigBuilder implements \IteratorAggregate, FormB
}
if (null !== $type) {
return $this->getFormFactory()->createNamedBuilder($name, $type, null, $options, $this);
return $this->getFormFactory()->createNamedBuilder($name, $type, null, $options);
}
return $this->getFormFactory()->createBuilderForProperty($this->getDataClass(), $name, null, $options, $this);
return $this->getFormFactory()->createBuilderForProperty($this->getDataClass(), $name, null, $options);
}
/**
@ -149,9 +141,6 @@ class FormBuilder extends FormConfigBuilder implements \IteratorAggregate, FormB
unset($this->unresolvedChildren[$name]);
if (array_key_exists($name, $this->children)) {
if ($this->children[$name] instanceof self) {
$this->children[$name]->setParent(null);
}
unset($this->children[$name]);
}
@ -211,7 +200,6 @@ class FormBuilder extends FormConfigBuilder implements \IteratorAggregate, FormB
{
$config = parent::getFormConfig();
$config->parent = null;
$config->children = array();
$config->unresolvedChildren = array();
@ -238,44 +226,6 @@ class FormBuilder extends FormConfigBuilder implements \IteratorAggregate, FormB
return $form;
}
/**
* {@inheritdoc}
*/
public function getParent()
{
if ($this->locked) {
throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
}
return $this->parent;
}
/**
* {@inheritdoc}
*/
public function setParent(FormBuilderInterface $parent = null)
{
if ($this->locked) {
throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
}
$this->parent = $parent;
return $this;
}
/**
* {@inheritdoc}
*/
public function hasParent()
{
if ($this->locked) {
throw new BadMethodCallException('FormBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
}
return null !== $this->parent;
}
/**
* {@inheritdoc}
*/

View File

@ -392,7 +392,7 @@ class FormConfigBuilder implements FormConfigBuilderInterface
*/
public function hasAttribute($name)
{
return isset($this->attributes[$name]);
return array_key_exists($name, $this->attributes);
}
/**
@ -400,7 +400,7 @@ class FormConfigBuilder implements FormConfigBuilderInterface
*/
public function getAttribute($name, $default = null)
{
return isset($this->attributes[$name]) ? $this->attributes[$name] : $default;
return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default;
}
/**
@ -448,7 +448,7 @@ class FormConfigBuilder implements FormConfigBuilderInterface
*/
public function hasOption($name)
{
return isset($this->options[$name]);
return array_key_exists($name, $this->options);
}
/**
@ -456,7 +456,7 @@ class FormConfigBuilder implements FormConfigBuilderInterface
*/
public function getOption($name, $default = null)
{
return isset($this->options[$name]) ? $this->options[$name] : $default;
return array_key_exists($name, $this->options) ? $this->options[$name] : $default;
}
/**

View File

@ -1,5 +1,4 @@
<?php
/*
* This file is part of the Symfony package.
*

View File

@ -34,43 +34,43 @@ class FormFactory implements FormFactoryInterface
/**
* {@inheritdoc}
*/
public function create($type = 'form', $data = null, array $options = array(), FormBuilderInterface $parent = null)
public function create($type = 'form', $data = null, array $options = array())
{
return $this->createBuilder($type, $data, $options, $parent)->getForm();
return $this->createBuilder($type, $data, $options)->getForm();
}
/**
* {@inheritdoc}
*/
public function createNamed($name, $type = 'form', $data = null, array $options = array(), FormBuilderInterface $parent = null)
public function createNamed($name, $type = 'form', $data = null, array $options = array())
{
return $this->createNamedBuilder($name, $type, $data, $options, $parent)->getForm();
return $this->createNamedBuilder($name, $type, $data, $options)->getForm();
}
/**
* {@inheritdoc}
*/
public function createForProperty($class, $property, $data = null, array $options = array(), FormBuilderInterface $parent = null)
public function createForProperty($class, $property, $data = null, array $options = array())
{
return $this->createBuilderForProperty($class, $property, $data, $options, $parent)->getForm();
return $this->createBuilderForProperty($class, $property, $data, $options)->getForm();
}
/**
* {@inheritdoc}
*/
public function createBuilder($type = 'form', $data = null, array $options = array(), FormBuilderInterface $parent = null)
public function createBuilder($type = 'form', $data = null, array $options = array())
{
$name = $type instanceof FormTypeInterface || $type instanceof ResolvedFormTypeInterface
? $type->getName()
: $type;
return $this->createNamedBuilder($name, $type, $data, $options, $parent);
return $this->createNamedBuilder($name, $type, $data, $options);
}
/**
* {@inheritdoc}
*/
public function createNamedBuilder($name, $type = 'form', $data = null, array $options = array(), FormBuilderInterface $parent = null)
public function createNamedBuilder($name, $type = 'form', $data = null, array $options = array())
{
if (null !== $data && !array_key_exists('data', $options)) {
$options['data'] = $data;
@ -84,16 +84,16 @@ class FormFactory implements FormFactoryInterface
throw new UnexpectedTypeException($type, 'string, Symfony\Component\Form\ResolvedFormTypeInterface or Symfony\Component\Form\FormTypeInterface');
}
return $type->createBuilder($this, $name, $options, $parent);
return $type->createBuilder($this, $name, $options);
}
/**
* {@inheritdoc}
*/
public function createBuilderForProperty($class, $property, $data = null, array $options = array(), FormBuilderInterface $parent = null)
public function createBuilderForProperty($class, $property, $data = null, array $options = array())
{
if (null === $guesser = $this->registry->getTypeGuesser()) {
return $this->createNamedBuilder($property, 'text', $data, $options, $parent);
return $this->createNamedBuilder($property, 'text', $data, $options);
}
$typeGuess = $guesser->guessType($class, $property);
@ -123,7 +123,7 @@ class FormFactory implements FormFactoryInterface
$options = array_merge($typeGuess->getOptions(), $options);
}
return $this->createNamedBuilder($property, $type, $data, $options, $parent);
return $this->createNamedBuilder($property, $type, $data, $options);
}
/**

View File

@ -24,13 +24,12 @@ interface FormFactoryInterface
* @param string|FormTypeInterface $type The type of the form
* @param mixed $data The initial data
* @param array $options The options
* @param FormBuilderInterface $parent The parent builder
*
* @return FormInterface The form named after the type
*
* @throws Exception\FormException if any given option is not applicable to the given type
*/
public function create($type = 'form', $data = null, array $options = array(), FormBuilderInterface $parent = null);
public function create($type = 'form', $data = null, array $options = array());
/**
* Returns a form.
@ -41,30 +40,28 @@ interface FormFactoryInterface
* @param string|FormTypeInterface $type The type of the form
* @param mixed $data The initial data
* @param array $options The options
* @param FormBuilderInterface $parent The parent builder
*
* @return FormInterface The form
*
* @throws Exception\FormException if any given option is not applicable to the given type
*/
public function createNamed($name, $type = 'form', $data = null, array $options = array(), FormBuilderInterface $parent = null);
public function createNamed($name, $type = 'form', $data = null, array $options = array());
/**
* Returns a form for a property of a class.
*
* @see createBuilderForProperty()
*
* @param string $class The fully qualified class name
* @param string $property The name of the property to guess for
* @param mixed $data The initial data
* @param array $options The options for the builder
* @param FormBuilderInterface $parent The parent builder
* @param string $class The fully qualified class name
* @param string $property The name of the property to guess for
* @param mixed $data The initial data
* @param array $options The options for the builder
*
* @return FormInterface The form named after the property
*
* @throws Exception\FormException if any given option is not applicable to the form type
*/
public function createForProperty($class, $property, $data = null, array $options = array(), FormBuilderInterface $parent = null);
public function createForProperty($class, $property, $data = null, array $options = array());
/**
* Returns a form builder.
@ -72,13 +69,12 @@ interface FormFactoryInterface
* @param string|FormTypeInterface $type The type of the form
* @param mixed $data The initial data
* @param array $options The options
* @param FormBuilderInterface $parent The parent builder
*
* @return FormBuilderInterface The form builder
*
* @throws Exception\FormException if any given option is not applicable to the given type
*/
public function createBuilder($type = 'form', $data = null, array $options = array(), FormBuilderInterface $parent = null);
public function createBuilder($type = 'form', $data = null, array $options = array());
/**
* Returns a form builder.
@ -87,13 +83,12 @@ interface FormFactoryInterface
* @param string|FormTypeInterface $type The type of the form
* @param mixed $data The initial data
* @param array $options The options
* @param FormBuilderInterface $parent The parent builder
*
* @return FormBuilderInterface The form builder
*
* @throws Exception\FormException if any given option is not applicable to the given type
*/
public function createNamedBuilder($name, $type = 'form', $data = null, array $options = array(), FormBuilderInterface $parent = null);
public function createNamedBuilder($name, $type = 'form', $data = null, array $options = array());
/**
* Returns a form builder for a property of a class.
@ -101,15 +96,14 @@ interface FormFactoryInterface
* If any of the 'max_length', 'required' and type options can be guessed,
* and are not provided in the options argument, the guessed value is used.
*
* @param string $class The fully qualified class name
* @param string $property The name of the property to guess for
* @param mixed $data The initial data
* @param array $options The options for the builder
* @param FormBuilderInterface $parent The parent builder
* @param string $class The fully qualified class name
* @param string $property The name of the property to guess for
* @param mixed $data The initial data
* @param array $options The options for the builder
*
* @return FormBuilderInterface The form builder named after the property
*
* @throws Exception\FormException if any given option is not applicable to the form type
*/
public function createBuilderForProperty($class, $property, $data = null, array $options = array(), FormBuilderInterface $parent = null);
public function createBuilderForProperty($class, $property, $data = null, array $options = array());
}

View File

@ -150,9 +150,9 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable
public function getConfig();
/**
* Returns whether the field is bound.
* Returns whether the form is submitted.
*
* @return Boolean true if the form is bound to input values, false otherwise
* @return Boolean true if the form is submitted, false otherwise
*/
public function isBound();

View File

@ -104,16 +104,15 @@ class ResolvedFormType implements ResolvedFormTypeInterface
/**
* {@inheritdoc}
*/
public function createBuilder(FormFactoryInterface $factory, $name, array $options = array(), FormBuilderInterface $parent = null)
public function createBuilder(FormFactoryInterface $factory, $name, array $options = array())
{
$options = $this->getOptionsResolver()->resolve($options);
// Should be decoupled from the specific option at some point
$dataClass = isset($options['data_class']) ? $options['data_class'] : null;
$builder = new FormBuilder($name, $dataClass, new EventDispatcher(), $factory, $options);
$builder = $this->newBuilder($name, $dataClass, $factory, $options);
$builder->setType($this);
$builder->setParent($parent);
$this->buildForm($builder, $options);
@ -127,7 +126,7 @@ class ResolvedFormType implements ResolvedFormTypeInterface
{
$options = $form->getConfig()->getOptions();
$view = new FormView($parent);
$view = $this->newView($parent);
$this->buildView($view, $form, $options);
@ -243,4 +242,43 @@ class ResolvedFormType implements ResolvedFormTypeInterface
return $this->optionsResolver;
}
/**
* Creates a new builder instance.
*
* Override this method if you want to customize the builder class.
*
* @param string $name The name of the builder.
* @param string $dataClass The data class.
* @param FormFactoryInterface $factory The current form factory.
* @param array $options The builder options.
*
* @return FormBuilderInterface The new builder instance.
*/
protected function newBuilder($name, $dataClass, FormFactoryInterface $factory, array $options)
{
if ($this->innerType instanceof ButtonTypeInterface) {
return new ButtonBuilder($name, $options);
}
if ($this->innerType instanceof SubmitButtonTypeInterface) {
return new SubmitButtonBuilder($name, $options);
}
return new FormBuilder($name, $dataClass, new EventDispatcher(), $factory, $options);
}
/**
* Creates a new view instance.
*
* Override this method if you want to customize the view class.
*
* @param FormView|null $parent The parent view, if available.
*
* @return FormView A new view instance.
*/
protected function newView(FormView $parent = null)
{
return new FormView($parent);
}
}

View File

@ -52,11 +52,10 @@ interface ResolvedFormTypeInterface
* @param FormFactoryInterface $factory The form factory.
* @param string $name The name for the builder.
* @param array $options The builder options.
* @param FormBuilderInterface $parent The parent builder object or null.
*
* @return FormBuilderInterface The created form builder.
*/
public function createBuilder(FormFactoryInterface $factory, $name, array $options = array(), FormBuilderInterface $parent = null);
public function createBuilder(FormFactoryInterface $factory, $name, array $options = array());
/**
* Creates a new form view for a form of this type.

View File

@ -0,0 +1,51 @@
<?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;
/**
* A button that submits the form.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class SubmitButton extends Button implements ClickableInterface
{
/**
* @var Boolean
*/
private $clicked = false;
/**
* {@inheritdoc}
*/
public function isClicked()
{
return $this->clicked;
}
/**
* Binds data to the button.
*
* @param null|string $submittedData The data
*
* @return SubmitButton The button instance
*
* @throws Exception\AlreadyBoundException If the form has already been bound.
*/
public function bind($submittedData)
{
parent::bind($submittedData);
$this->clicked = null !== $submittedData;
return $this;
}
}

View File

@ -0,0 +1,30 @@
<?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;
/**
* A builder for {@link SubmitButton} instances.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class SubmitButtonBuilder extends ButtonBuilder
{
/**
* Creates the button.
*
* @return Button The button
*/
public function getForm()
{
return new SubmitButton($this->getFormConfig());
}
}

View File

@ -0,0 +1,21 @@
<?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;
/**
* A type that should be converted into a {@link SubmitButton} instance.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface SubmitButtonTypeInterface extends FormTypeInterface
{
}

View File

@ -82,6 +82,22 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest
);
}
public function testButtonRow()
{
$form = $this->factory->createNamed('name', 'button');
$view = $form->createView();
$html = $this->renderRow($view);
$this->assertMatchesXpath($html,
'/div
[
./button[@type="button"][@name="name"]
]
[count(//label)=0]
'
);
}
public function testRest()
{
$view = $this->factory->createNamedBuilder('name', 'form')

View File

@ -1762,4 +1762,38 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase
//input[@type="text"][@id="child"][@name="child"]'
, 2);
}
public function testButton()
{
$form = $this->factory->createNamed('name', 'button');
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/button[@type="button"][@name="name"]'
);
}
public function testButtonLabelIsEmpty()
{
$form = $this->factory->createNamed('name', 'button');
$this->assertSame('', $this->renderLabel($form->createView()));
}
public function testSubmit()
{
$form = $this->factory->createNamed('name', 'submit');
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/button[@type="submit"][@name="name"]'
);
}
public function testReset()
{
$form = $this->factory->createNamed('name', 'reset');
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/button[@type="reset"][@name="name"]'
);
}
}

View File

@ -125,6 +125,25 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
);
}
public function testButtonRow()
{
$form = $this->factory->createNamed('name', 'button');
$view = $form->createView();
$html = $this->renderRow($view);
$this->assertMatchesXpath($html,
'/tr
[
./td
[.=""]
/following-sibling::td
[./button[@type="button"][@name="name"]]
]
[count(//label)=0]
'
);
}
public function testRest()
{
$view = $this->factory->createNamedBuilder('name', 'form')

View File

@ -0,0 +1,129 @@
<?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;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
abstract class BaseTypeTest extends TypeTestCase
{
public function testPassDisabledAsOption()
{
$form = $this->factory->create($this->getTestedType(), null, array('disabled' => true));
$this->assertTrue($form->isDisabled());
}
public function testPassIdAndNameToView()
{
$form = $this->factory->createNamed('name', $this->getTestedType());
$view = $form->createView();
$this->assertEquals('name', $view->vars['id']);
$this->assertEquals('name', $view->vars['name']);
$this->assertEquals('name', $view->vars['full_name']);
}
public function testStripLeadingUnderscoresAndDigitsFromId()
{
$form = $this->factory->createNamed('_09name', $this->getTestedType());
$view = $form->createView();
$this->assertEquals('name', $view->vars['id']);
$this->assertEquals('_09name', $view->vars['name']);
$this->assertEquals('_09name', $view->vars['full_name']);
}
public function testPassIdAndNameToViewWithParent()
{
$parent = $this->factory->createNamed('parent', 'form');
$parent->add($this->factory->createNamed('child', $this->getTestedType()));
$view = $parent->createView();
$this->assertEquals('parent_child', $view['child']->vars['id']);
$this->assertEquals('child', $view['child']->vars['name']);
$this->assertEquals('parent[child]', $view['child']->vars['full_name']);
}
public function testPassIdAndNameToViewWithGrandParent()
{
$parent = $this->factory->createNamed('parent', 'form');
$parent->add($this->factory->createNamed('child', 'form'));
$parent['child']->add($this->factory->createNamed('grand_child', $this->getTestedType()));
$view = $parent->createView();
$this->assertEquals('parent_child_grand_child', $view['child']['grand_child']->vars['id']);
$this->assertEquals('grand_child', $view['child']['grand_child']->vars['name']);
$this->assertEquals('parent[child][grand_child]', $view['child']['grand_child']->vars['full_name']);
}
public function testPassTranslationDomainToView()
{
$form = $this->factory->create($this->getTestedType(), null, array(
'translation_domain' => 'domain',
));
$view = $form->createView();
$this->assertSame('domain', $view->vars['translation_domain']);
}
public function testInheritTranslationDomainFromParent()
{
$parent = $this->factory->createNamed('parent', 'form', null, array(
'translation_domain' => 'domain',
));
$child = $this->factory->createNamed('child', $this->getTestedType());
$view = $parent->add($child)->createView();
$this->assertEquals('domain', $view['child']->vars['translation_domain']);
}
public function testPreferOwnTranslationDomain()
{
$parent = $this->factory->createNamed('parent', 'form', null, array(
'translation_domain' => 'parent_domain',
));
$child = $this->factory->createNamed('child', $this->getTestedType(), null, array(
'translation_domain' => 'domain',
));
$view = $parent->add($child)->createView();
$this->assertEquals('domain', $view['child']->vars['translation_domain']);
}
public function testDefaultTranslationDomain()
{
$parent = $this->factory->createNamed('parent', 'form');
$child = $this->factory->createNamed('child', $this->getTestedType());
$view = $parent->add($child)->createView();
$this->assertEquals('messages', $view['child']->vars['translation_domain']);
}
public function testPassLabelToView()
{
$form = $this->factory->createNamed('__test___field', $this->getTestedType(), null, array('label' => 'My label'));
$view = $form->createView();
$this->assertSame('My label', $view->vars['label']);
}
public function testPassMultipartFalseToView()
{
$form = $this->factory->create($this->getTestedType());
$view = $form->createView();
$this->assertFalse($view->vars['multipart']);
}
abstract protected function getTestedType();
}

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\Tests\Extension\Core\Type;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ButtonTypeTest extends BaseTypeTest
{
public function testCreateButtonInstances()
{
$this->assertInstanceOf('Symfony\Component\Form\Button', $this->factory->create('button'));
}
protected function getTestedType()
{
return 'button';
}
}

View File

@ -50,8 +50,13 @@ class FormTest_AuthorWithoutRefSetter
}
}
class FormTypeTest extends TypeTestCase
class FormTypeTest extends BaseTypeTest
{
public function testCreateFormInstances()
{
$this->assertInstanceOf('Symfony\Component\Form\Form', $this->factory->create('form'));
}
public function testPassRequiredAsOption()
{
$form = $this->factory->create('form', null, array('required' => false));
@ -63,13 +68,6 @@ class FormTypeTest extends TypeTestCase
$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')
@ -102,49 +100,6 @@ class FormTypeTest extends TypeTestCase
$this->assertEquals('reverse[ a ]', $form->getData());
}
public function testPassIdAndNameToView()
{
$form = $this->factory->createNamed('name', 'form');
$view = $form->createView();
$this->assertEquals('name', $view->vars['id']);
$this->assertEquals('name', $view->vars['name']);
$this->assertEquals('name', $view->vars['full_name']);
}
public function testStripLeadingUnderscoresAndDigitsFromId()
{
$form = $this->factory->createNamed('_09name', 'form');
$view = $form->createView();
$this->assertEquals('name', $view->vars['id']);
$this->assertEquals('_09name', $view->vars['name']);
$this->assertEquals('_09name', $view->vars['full_name']);
}
public function testPassIdAndNameToViewWithParent()
{
$parent = $this->factory->createNamed('parent', 'form');
$parent->add($this->factory->createNamed('child', 'form'));
$view = $parent->createView();
$this->assertEquals('parent_child', $view['child']->vars['id']);
$this->assertEquals('child', $view['child']->vars['name']);
$this->assertEquals('parent[child]', $view['child']->vars['full_name']);
}
public function testPassIdAndNameToViewWithGrandParent()
{
$parent = $this->factory->createNamed('parent', 'form');
$parent->add($this->factory->createNamed('child', 'form'));
$parent['child']->add($this->factory->createNamed('grand_child', 'form'));
$view = $parent->createView();
$this->assertEquals('parent_child_grand_child', $view['child']['grand_child']->vars['id']);
$this->assertEquals('grand_child', $view['child']['grand_child']->vars['name']);
$this->assertEquals('parent[child][grand_child]', $view['child']['grand_child']->vars['full_name']);
}
public function testNonReadOnlyFormWithReadOnlyParentBeingReadOnly()
{
$parent = $this->factory->createNamed('parent', 'form', null, array('read_only' => true));
@ -180,57 +135,6 @@ class FormTypeTest extends TypeTestCase
$this->assertSame(10, $view->vars['max_length']);
}
public function testPassTranslationDomainToView()
{
$form = $this->factory->create('form', null, array('translation_domain' => 'test'));
$view = $form->createView();
$this->assertSame('test', $view->vars['translation_domain']);
}
public function testNonTranslationDomainFormWithTranslationDomainParentBeingTranslationDomain()
{
$parent = $this->factory->createNamed('parent', 'form', null, array('translation_domain' => 'test'));
$child = $this->factory->createNamed('child', 'form');
$view = $parent->add($child)->createView();
$this->assertEquals('test', $view['child']->vars['translation_domain']);
}
public function testTranslationDomainFormWithNonTranslationDomainParentBeingTranslationDomain()
{
$parent = $this->factory->createNamed('parent', 'form');
$child = $this->factory->createNamed('child', 'form', null, array('translation_domain' => 'test'));
$view = $parent->add($child)->createView();
$this->assertEquals('test', $view['child']->vars['translation_domain']);
}
public function testNonTranslationDomainFormWithNonTranslationDomainParentBeingTranslationDomainDefault()
{
$parent = $this->factory->createNamed('parent', 'form');
$child = $this->factory->createNamed('child', 'form');
$view = $parent->add($child)->createView();
$this->assertEquals('messages', $view['child']->vars['translation_domain']);
}
public function testPassLabelToView()
{
$form = $this->factory->createNamed('__test___field', 'form', null, array('label' => 'My label'));
$view = $form->createView();
$this->assertSame('My label', $view->vars['label']);
}
public function testDefaultTranslationDomain()
{
$form = $this->factory->create('form');
$view = $form->createView();
$this->assertSame('messages', $view->vars['translation_domain']);
}
public function testBindWithEmptyDataCreatesObjectIfClassAvailable()
{
$form = $this->factory->create('form', null, array(
@ -404,6 +308,7 @@ class FormTypeTest extends TypeTestCase
$this->assertEquals('', $form->getName());
}
public function testSubformDoesntCallSetters()
{
$author = new FormTest_AuthorWithoutRefSetter(new Author());
@ -523,14 +428,6 @@ class FormTypeTest extends TypeTestCase
$this->assertSame($ref2, $author['referenceCopy']);
}
public function testPassMultipartFalseToView()
{
$form = $this->factory->create('form');
$view = $form->createView();
$this->assertFalse($view->vars['multipart']);
}
public function testPassMultipartTrueIfAnyChildIsMultipartToView()
{
$form = $this->factory->create('form');
@ -661,4 +558,9 @@ class FormTypeTest extends TypeTestCase
$this->assertSame('0', $view->vars['label']);
}
protected function getTestedType()
{
return 'form';
}
}

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\Core\Type;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class SubmitTypeTest extends TypeTestCase
{
public function testCreateSubmitButtonInstances()
{
$this->assertInstanceOf('Symfony\Component\Form\SubmitButton', $this->factory->create('submit'));
}
public function testNotClickedByDefault()
{
$button = $this->factory->create('submit');
$this->assertFalse($button->isClicked());
}
public function testNotClickedIfBoundWithNull()
{
$button = $this->factory->create('submit');
$button->bind(null);
$this->assertFalse($button->isClicked());
}
public function testClickedIfBoundWithEmptyString()
{
$button = $this->factory->create('submit');
$button->bind('');
$this->assertTrue($button->isClicked());
}
public function testClickedIfBoundWithUnemptyString()
{
$button = $this->factory->create('submit');
$button->bind('foo');
$this->assertTrue($button->isClicked());
}
}

View File

@ -17,6 +17,7 @@ use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\Extension\Validator\Constraints\Form;
use Symfony\Component\Form\Extension\Validator\Constraints\FormValidator;
use Symfony\Component\Form\SubmitButtonBuilder;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\NotNull;
use Symfony\Component\Validator\Constraints\NotBlank;
@ -169,6 +170,51 @@ class FormValidatorTest extends \PHPUnit_Framework_TestCase
$this->validator->validate($form, new Form());
}
public function testDontValidateIfNoValidationGroups()
{
$context = $this->getMockExecutionContext();
$object = $this->getMock('\stdClass');
$form = $this->getBuilder('name', '\stdClass', array(
'validation_groups' => array(),
))
->setData($object)
->getForm();
$form->setData($object);
$context->expects($this->never())
->method('validate');
$this->validator->initialize($context);
$this->validator->validate($form, new Form());
}
public function testDontValidateConstraintsIfNoValidationGroups()
{
$context = $this->getMockExecutionContext();
$object = $this->getMock('\stdClass');
$constraint1 = $this->getMock('Symfony\Component\Validator\Constraint');
$constraint2 = $this->getMock('Symfony\Component\Validator\Constraint');
$options = array(
'validation_groups' => array(),
'constraints' => array($constraint1, $constraint2),
);
$form = $this->getBuilder('name', '\stdClass', $options)
->setData($object)
->getForm();
// Launch transformer
$form->bind(array());
$context->expects($this->never())
->method('validate');
$this->validator->initialize($context);
$this->validator->validate($form, new Form());
}
public function testDontValidateIfNotSynchronized()
{
$context = $this->getMockExecutionContext();
@ -208,6 +254,46 @@ class FormValidatorTest extends \PHPUnit_Framework_TestCase
$this->validator->validate($form, new Form());
}
public function testAddInvalidErrorEvenIfNoValidationGroups()
{
$context = $this->getMockExecutionContext();
$object = $this->getMock('\stdClass');
$form = $this->getBuilder('name', '\stdClass', array(
'invalid_message' => 'invalid_message_key',
// Invalid message parameters must be supported, because the
// invalid message can be a translation key
// see https://github.com/symfony/symfony/issues/5144
'invalid_message_parameters' => array('{{ foo }}' => 'bar'),
'validation_groups' => array(),
))
->setData($object)
->addViewTransformer(new CallbackTransformer(
function ($data) { return $data; },
function () { throw new TransformationFailedException(); }
))
->getForm();
// Launch transformer
$form->bind('foo');
$context->expects($this->never())
->method('validate');
$context->expects($this->once())
->method('addViolation')
->with(
'invalid_message_key',
array('{{ value }}' => 'foo', '{{ foo }}' => 'bar'),
'foo'
);
$context->expects($this->never())
->method('addViolationAt');
$this->validator->initialize($context);
$this->validator->validate($form, new Form());
}
public function testDontValidateConstraintsIfNotSynchronized()
{
$context = $this->getMockExecutionContext();
@ -313,6 +399,62 @@ class FormValidatorTest extends \PHPUnit_Framework_TestCase
$this->validator->validate($form, new Form());
}
public function testUseValidationGroupOfClickedButton()
{
$context = $this->getMockExecutionContext();
$object = $this->getMock('\stdClass');
$parent = $this->getBuilder('parent', null, array('cascade_validation' => true))
->setCompound(true)
->setDataMapper($this->getDataMapper())
->getForm();
$form = $this->getForm('name', '\stdClass', array(
'validation_groups' => 'form_group',
));
$parent->add($form);
$parent->add($this->getClickedSubmitButton('submit', array(
'validation_groups' => 'button_group',
)));
$form->setData($object);
$context->expects($this->once())
->method('validate')
->with($object, 'data', 'button_group', true);
$this->validator->initialize($context);
$this->validator->validate($form, new Form());
}
public function testDontUseValidationGroupOfUnclickedButton()
{
$context = $this->getMockExecutionContext();
$object = $this->getMock('\stdClass');
$parent = $this->getBuilder('parent', null, array('cascade_validation' => true))
->setCompound(true)
->setDataMapper($this->getDataMapper())
->getForm();
$form = $this->getForm('name', '\stdClass', array(
'validation_groups' => 'form_group',
));
$parent->add($form);
$parent->add($this->getSubmitButton('submit', array(
'validation_groups' => 'button_group',
)));
$form->setData($object);
$context->expects($this->once())
->method('validate')
->with($object, 'data', 'form_group', true);
$this->validator->initialize($context);
$this->validator->validate($form, new Form());
}
public function testUseInheritedValidationGroup()
{
$context = $this->getMockExecutionContext();
@ -561,9 +703,21 @@ class FormValidatorTest extends \PHPUnit_Framework_TestCase
return new FormBuilder($name, $dataClass, $this->dispatcher, $this->factory, $options);
}
private function getForm($name = 'name', $dataClass = null)
private function getForm($name = 'name', $dataClass = null, array $options = array())
{
return $this->getBuilder($name, $dataClass)->getForm();
return $this->getBuilder($name, $dataClass, $options)->getForm();
}
private function getSubmitButton($name = 'name', array $options = array())
{
$builder = new SubmitButtonBuilder($name, $options);
return $builder->getForm();
}
private function getClickedSubmitButton($name = 'name', array $options = array())
{
return $this->getSubmitButton($name, $options)->bind('');
}
/**

View File

@ -40,6 +40,15 @@ class FormTypeValidatorExtensionTest extends TypeTestCase
$this->assertEquals(array('group1', 'group2'), $form->getConfig()->getOption('validation_groups'));
}
public function testValidationGroupsCanBeSetToFalse()
{
$form = $this->factory->create('form', null, array(
'validation_groups' => false,
));
$this->assertEquals(array(), $form->getConfig()->getOption('validation_groups'));
}
public function testValidationGroupsCanBeSetToCallback()
{
$form = $this->factory->create('form', null, array(

View File

@ -781,7 +781,7 @@ class ViolationMapperTest extends \PHPUnit_Framework_TestCase
} else {
$this->assertCount(0, $parent->getErrors(), $parent->getName().' should not have an error, but has one');
$this->assertCount(0, $child->getErrors(), $childName.' should not have an error, but has one');
$this->assertEquals(array($this->getFormError()), $grandChild->getErrors(), $grandChildName. ' should have an error, but has none');
$this->assertEquals(array($this->getFormError()), $grandChild->getErrors(), $grandChildName.' should have an error, but has none');
}
}
@ -1253,7 +1253,7 @@ class ViolationMapperTest extends \PHPUnit_Framework_TestCase
} else {
$this->assertCount(0, $parent->getErrors(), $parent->getName().' should not have an error, but has one');
$this->assertCount(0, $child->getErrors(), $childName.' should not have an error, but has one');
$this->assertEquals(array($this->getFormError()), $grandChild->getErrors(), $grandChildName. ' should have an error, but has none');
$this->assertEquals(array($this->getFormError()), $grandChild->getErrors(), $grandChildName.' should have an error, but has none');
}
}
@ -1442,7 +1442,7 @@ class ViolationMapperTest extends \PHPUnit_Framework_TestCase
$this->assertCount(0, $errorChild->getErrors(), $errorName.' should not have an error, but has one');
$this->assertCount(0, $parent->getErrors(), $parent->getName().' should not have an error, but has one');
$this->assertCount(0, $child->getErrors(), $childName.' should not have an error, but has one');
$this->assertEquals(array($this->getFormError()), $grandChild->getErrors(), $grandChildName. ' should have an error, but has none');
$this->assertEquals(array($this->getFormError()), $grandChild->getErrors(), $grandChildName.' should have an error, but has none');
}
}
@ -1497,7 +1497,7 @@ class ViolationMapperTest extends \PHPUnit_Framework_TestCase
} else {
$this->assertCount(0, $parent->getErrors(), $parent->getName().' should not have an error, but has one');
$this->assertCount(0, $child->getErrors(), $childName.' should not have an error, but has one');
$this->assertEquals(array($this->getFormError()), $grandChild->getErrors(), $grandChildName. ' should have an error, but has none');
$this->assertEquals(array($this->getFormError()), $grandChild->getErrors(), $grandChildName.' should have an error, but has none');
}
}
}

View File

@ -200,62 +200,19 @@ class FormBuilderTest extends \PHPUnit_Framework_TestCase
$this->assertNotSame($builder, $this->builder);
}
public function testGetParent()
{
$this->assertNull($this->builder->getParent());
}
public function testGetParentForAddedBuilder()
{
$builder = new FormBuilder('name', null, $this->dispatcher, $this->factory);
$this->builder->add($builder);
$this->assertSame($this->builder, $builder->getParent());
}
public function testGetParentForRemovedBuilder()
{
$builder = new FormBuilder('name', null, $this->dispatcher, $this->factory);
$this->builder->add($builder);
$this->builder->remove('name');
$this->assertNull($builder->getParent());
}
public function testGetParentForCreatedBuilder()
{
$this->builder = new FormBuilder('name', 'stdClass', $this->dispatcher, $this->factory);
$this->factory
->expects($this->once())
->method('createNamedBuilder')
->with('bar', 'text', null, array(), $this->builder)
;
$this->factory
->expects($this->once())
->method('createBuilderForProperty')
->with('stdClass', 'foo', null, array(), $this->builder)
;
$this->builder->create('foo');
$this->builder->create('bar', 'text');
}
public function testGetFormConfigErasesReferences()
{
$builder = new FormBuilder('name', null, $this->dispatcher, $this->factory);
$builder->setParent(new FormBuilder('parent', null, $this->dispatcher, $this->factory));
$builder->add(new FormBuilder('child', null, $this->dispatcher, $this->factory));
$config = $builder->getFormConfig();
$reflClass = new \ReflectionClass($config);
$parent = $reflClass->getProperty('parent');
$children = $reflClass->getProperty('children');
$unresolvedChildren = $reflClass->getProperty('unresolvedChildren');
$parent->setAccessible(true);
$children->setAccessible(true);
$unresolvedChildren->setAccessible(true);
$this->assertNull($parent->getValue($config));
$this->assertEmpty($children->getValue($config));
$this->assertEmpty($unresolvedChildren->getValue($config));
}

View File

@ -171,25 +171,6 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
$this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', $resolvedType, null, $options));
}
public function testCreateNamedBuilderWithParentBuilder()
{
$options = array('a' => '1', 'b' => '2');
$parentBuilder = $this->getMockFormBuilder();
$resolvedType = $this->getMockResolvedType();
$this->registry->expects($this->once())
->method('getType')
->with('type')
->will($this->returnValue($resolvedType));
$resolvedType->expects($this->once())
->method('createBuilder')
->with($this->factory, 'name', $options, $parentBuilder)
->will($this->returnValue('BUILDER'));
$this->assertSame('BUILDER', $this->factory->createNamedBuilder('name', 'type', null, $options, $parentBuilder));
}
public function testCreateNamedBuilderFillsDataOption()
{
$givenOptions = array('a' => '1', 'b' => '2');
@ -228,7 +209,7 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
}
/**
* @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
* @expectedExceptionMessage Expected argument of type "string, Symfony\Component\Form\ResolvedFormTypeInterface or Symfony\Component\Form\FormTypeInterface", "stdClass" given
*/
public function testCreateNamedBuilderThrowsUnderstandableException()

View File

@ -134,10 +134,8 @@ class ResolvedFormTypeTest extends \PHPUnit_Framework_TestCase
->will($this->returnCallback($assertIndex(7)));
$factory = $this->getMockFormFactory();
$parentBuilder = $this->getBuilder('parent');
$builder = $resolvedType->createBuilder($factory, 'name', $givenOptions, $parentBuilder);
$builder = $resolvedType->createBuilder($factory, 'name', $givenOptions);
$this->assertSame($parentBuilder, $builder->getParent());
$this->assertSame($resolvedType, $builder->getType());
}

View File

@ -89,7 +89,7 @@ class XliffFileLoaderTest extends \PHPUnit_Framework_TestCase
}
/**
* @expectedException Symfony\Component\Translation\Exception\InvalidResourceException
* @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
* @expectedExceptionMessage Document types are not allowed.
*/
public function testDocTypeIsNotAllowed()

View File

@ -82,7 +82,7 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase
}
/**
* @expectedException Symfony\Component\Validator\Exception\MappingException
* @expectedException \Symfony\Component\Validator\Exception\MappingException
* @expectedExceptionMessage Document types are not allowed.
*/
public function testDocTypeIsNotAllowed()