Merge remote branch 'bschussek/form'

* bschussek/form: (22 commits)
  Fix merge error (function "guess" was in there twice)
  [Form] Added test case for bf2f9d2a02
  [Form] Form::isBound() and Form::isValid() work correctly now for read-only forms
  [Locale] Improved error reporting and added stubs for intl_is_failure(), intl_get_error_code() and intl_get_error_message()
  [Form] Implemented fix for 361c67f54f
  [Form] Add test for the handling of array values in the constraint violation
  [Form] Further simplified PropertyPath code
  [Form] Added test for 6c337d1cc0
  [Form] Removed unused option "pattern" of date and time type
  [Form] Renamed view variable "name" to "full_name"
  [Form] Renamed collection option "type_options" to "options" to be consistent with the repeated type
  [Form] CSRF documentation and a few CS changes
  [Form] Move CSRF options from types to the CSRF extension
  [Form] Added a search form field type
  [Form] Optimization of PropertyPath
  [Form] replace assertEquals by assertFalse, assertTrue, assertNull
  [Form] fix file permissions to 644 again ;)
  [Form] add tests for type_options in collectionType
  fix file permissions to 644
  [Form] add type_options for CollectionType to be abble to set options to type
  ...
This commit is contained in:
Fabien Potencier 2011-05-19 16:25:30 +02:00
commit 9fe1c3ae0e
59 changed files with 730 additions and 156 deletions

View File

@ -167,6 +167,9 @@ beta1 to beta2
* Form: Renamed option value "text" of "widget" option of the "date" type was
renamed to "single-text". "text" indicates to use separate text boxes now
(like for the "time" type).
* Form: Renamed view variable "name" to "full_name". The variable "name" now
contains the local, short name (equivalent to $form->getName()).
PR12 to beta1
-------------

View File

@ -180,4 +180,12 @@ class DoctrineOrmTypeGuesser implements FormTypeGuesserInterface
}
}
}
/**
* @inheritDoc
*/
public function guessMinLength($class, $property)
{
return;
}
}

View File

@ -129,6 +129,9 @@
<service id="form.type.repeated" class="Symfony\Component\Form\Extension\Core\Type\RepeatedType">
<tag name="form.type" alias="repeated" />
</service>
<service id="form.type.search" class="Symfony\Component\Form\Extension\Core\Type\SearchType">
<tag name="form.type" alias="search" />
</service>
<service id="form.type.textarea" class="Symfony\Component\Form\Extension\Core\Type\TextareaType">
<tag name="form.type" alias="textarea" />
</service>

View File

@ -1,6 +1,6 @@
<input type="checkbox"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
name="<?php echo $view->escape($full_name) ?>"
<?php if ($value): ?>value="<?php echo $view->escape($value) ?>"<?php endif ?>
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>

View File

@ -8,7 +8,7 @@
<?php else: ?>
<select
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
name="<?php echo $view->escape($full_name) ?>"
<?php if ($read_only): ?> disabled="disabled"<?php endif ?>
<?php if ($multiple): ?> multiple="multiple"<?php endif ?>
>

View File

@ -1,7 +1,7 @@
<?php if ($widget == 'single-text'): ?>
<input type="text"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
name="<?php echo $view->escape($full_name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>

View File

@ -1,6 +1,6 @@
<input type="email"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
name="<?php echo $view->escape($full_name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($max_length): ?>maxlength="<?php echo $view->escape($max_length) ?>"<?php endif ?>
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>

View File

@ -1,6 +1,6 @@
<input
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
name="<?php echo $view->escape($full_name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>

View File

@ -1,6 +1,6 @@
<input type="hidden"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
name="<?php echo $view->escape($full_name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
/>

View File

@ -1,6 +1,6 @@
<input type="number"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
name="<?php echo $view->escape($full_name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>

View File

@ -1,8 +1,9 @@
<input type="text"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
name="<?php echo $view->escape($full_name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>
<?php if ($max_length): ?>maxlength="<?php echo $max_length ?>"<?php endif ?>
<?php if ($pattern): ?>pattern="<?php echo $pattern ?>"<?php endif ?>
/>

View File

@ -1,8 +1,9 @@
<input type="password"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
name="<?php echo $view->escape($full_name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>
<?php if ($max_length): ?>maxlength="<?php echo $max_length ?>"<?php endif ?>
<?php if ($pattern): ?>pattern="<?php echo $pattern ?>"<?php endif ?>
/>

View File

@ -1,6 +1,6 @@
<input type="radio"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
name="<?php echo $view->escape($full_name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>

View File

@ -0,0 +1,8 @@
<input type="search"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>
<?php if ($max_length): ?>maxlength="<?php echo $max_length ?>"<?php endif ?>
/>

View File

@ -1,8 +1,9 @@
<input type="text"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
name="<?php echo $view->escape($full_name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>
<?php if ($max_length): ?>maxlength="<?php echo $max_length ?>"<?php endif ?>
<?php if ($pattern): ?>pattern="<?php echo $pattern ?>"<?php endif ?>
/>

View File

@ -1,6 +1,6 @@
<textarea
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
name="<?php echo $view->escape($full_name) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>
><?php echo $view->escape($value) ?></textarea>

View File

@ -1,6 +1,6 @@
<input type="url"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
name="<?php echo $view->escape($full_name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>

View File

@ -43,7 +43,7 @@
{% block attributes %}
{% spaceless %}
id="{{ id }}" name="{{ name }}"{% if read_only %} disabled="disabled"{% endif %}{% if required %} required="required"{% endif %}{% if max_length %} maxlength="{{ max_length }}"{% endif %}
id="{{ id }}" name="{{ full_name }}"{% if read_only %} disabled="disabled"{% endif %}{% if required %} required="required"{% endif %}{% if max_length %} maxlength="{{ max_length }}"{% endif %}{% if pattern %} pattern="{{ pattern }}"{% endif %}
{% for attrname,attrvalue in attr %}{{attrname}}="{{attrvalue}}" {% endfor %}
{% endspaceless %}
{% endblock attributes %}
@ -208,6 +208,13 @@
{% endspaceless %}
{% endblock url_widget %}
{% block search_widget %}
{% spaceless %}
{% set type = type|default('search') %}
{{ block('field_widget') }}
{% endspaceless %}
{% endblock search_widget %}
{% block percent_widget %}
{% spaceless %}
{% set type = type|default('text') %}

View File

@ -52,6 +52,7 @@ class CoreExtension extends AbstractExtension
new Type\PercentType(),
new Type\RadioType(),
new Type\RepeatedType(),
new Type\SearchType(),
new Type\TextareaType(),
new Type\TextType(),
new Type\TimeType(),

View File

@ -35,6 +35,11 @@ class ResizeFormListener implements EventSubscriberInterface
*/
private $type;
/**
* @var array
*/
private $options;
/**
* Whether children could be added to the group
* @var Boolean
@ -47,12 +52,13 @@ class ResizeFormListener implements EventSubscriberInterface
*/
private $allowDelete;
public function __construct(FormFactoryInterface $factory, $type, $allowAdd = false, $allowDelete = false)
public function __construct(FormFactoryInterface $factory, $type, array $options = array(), $allowAdd = false, $allowDelete = false)
{
$this->factory = $factory;
$this->type = $type;
$this->allowAdd = $allowAdd;
$this->allowDelete = $allowDelete;
$this->options = $options;
}
public static function getSubscribedEvents()
@ -86,9 +92,9 @@ class ResizeFormListener implements EventSubscriberInterface
// Then add all rows again in the correct order
foreach ($data as $name => $value) {
$form->add($this->factory->createNamed($this->type, $name, null, array(
$form->add($this->factory->createNamed($this->type, $name, null, array_replace(array(
'property_path' => '['.$name.']',
)));
), $this->options)));
}
}
@ -118,9 +124,9 @@ class ResizeFormListener implements EventSubscriberInterface
if ($this->allowAdd) {
foreach ($data as $name => $value) {
if (!$form->has($name)) {
$form->add($this->factory->createNamed($this->type, $name, null, array(
$form->add($this->factory->createNamed($this->type, $name, null, array_replace(array(
'property_path' => '['.$name.']',
)));
), $this->options)));
}
}
}
@ -149,4 +155,4 @@ class ResizeFormListener implements EventSubscriberInterface
$event->setData($data);
}
}
}

View File

@ -106,7 +106,7 @@ class ChoiceType extends AbstractType
// Add "[]" to the name in case a select tag with multiple options is
// displayed. Otherwise only one of the selected options is sent in the
// POST request.
$view->set('name', $view->get('name').'[]');
$view->set('full_name', $view->get('full_name').'[]');
}
}
@ -121,7 +121,6 @@ class ChoiceType extends AbstractType
'choice_list' => null,
'choices' => array(),
'preferred_choices' => array(),
'csrf_protection' => false,
'empty_data' => $multiple || $expanded ? array() : '',
'error_bubbling' => false,
);

View File

@ -22,15 +22,16 @@ class CollectionType extends AbstractType
public function buildForm(FormBuilder $builder, array $options)
{
if ($options['allow_add'] && $options['prototype']) {
$builder->add('$$name$$', $options['type'], array(
$builder->add('$$name$$', $options['type'], array_replace(array(
'property_path' => false,
'required' => false,
));
'required' => false,
), $options['options']));
}
$listener = new ResizeFormListener(
$builder->getFormFactory(),
$options['type'],
$options['options'],
$options['allow_add'],
$options['allow_delete']
);
@ -57,6 +58,7 @@ class CollectionType extends AbstractType
'allow_delete' => false,
'prototype' => true,
'type' => 'text',
'options' => array(),
);
}

View File

@ -37,9 +37,6 @@ class DateTimeType extends AbstractType
'with_seconds',
)));
if (isset($options['date_pattern'])) {
$dateOptions['pattern'] = $options['date_pattern'];
}
if (isset($options['date_widget'])) {
$dateOptions['widget'] = $options['date_widget'];
}
@ -49,9 +46,6 @@ class DateTimeType extends AbstractType
$dateOptions['input'] = 'array';
if (isset($options['time_pattern'])) {
$timeOptions['pattern'] = $options['time_pattern'];
}
if (isset($options['time_widget'])) {
$timeOptions['widget'] = $options['time_widget'];
}
@ -103,10 +97,8 @@ class DateTimeType extends AbstractType
// Don't modify \DateTime classes by reference, we treat
// them like immutable value objects
'by_reference' => false,
'date_pattern' => null,
'date_widget' => null,
'date_format' => null,
'time_pattern' => null,
'time_widget' => null,
/* Defaults for date field */
'years' => range(date('Y') - 5, date('Y') + 5),

View File

@ -119,11 +119,9 @@ class DateType extends AbstractType
'days' => range(1, 31),
'widget' => 'choice',
'input' => 'datetime',
'pattern' => null,
'format' => \IntlDateFormatter::MEDIUM,
'data_timezone' => null,
'user_timezone' => null,
'csrf_protection' => false,
// Don't modify \DateTime classes by reference, we treat
// them like immutable value objects
'by_reference' => false,

View File

@ -44,6 +44,7 @@ class FieldType extends AbstractType
->setAttribute('property_path', $options['property_path'])
->setAttribute('error_mapping', $options['error_mapping'])
->setAttribute('max_length', $options['max_length'])
->setAttribute('pattern', $options['pattern'])
->setAttribute('label', $options['label'] ?: $this->humanize($builder->getName()))
->setData($options['data'])
->addValidator(new DefaultValidator())
@ -58,7 +59,7 @@ class FieldType extends AbstractType
{
if ($view->hasParent()) {
$parentId = $view->getParent()->get('id');
$parentName = $view->getParent()->get('name');
$parentName = $view->getParent()->get('full_name');
$id = sprintf('%s_%s', $parentId, $form->getName());
$name = sprintf('%s[%s]', $parentName, $form->getName());
} else {
@ -69,12 +70,14 @@ class FieldType extends AbstractType
$view
->set('form', $view)
->set('id', $id)
->set('name', $name)
->set('name', $form->getName())
->set('full_name', $name)
->set('errors', $form->getErrors())
->set('value', $form->getClientData())
->set('read_only', $form->isReadOnly())
->set('required', $form->isRequired())
->set('max_length', $form->getAttribute('max_length'))
->set('pattern', $form->getAttribute('pattern'))
->set('size', null)
->set('label', $form->getAttribute('label'))
->set('multipart', false)
@ -97,6 +100,7 @@ class FieldType extends AbstractType
'required' => true,
'read_only' => false,
'max_length' => null,
'pattern' => null,
'property_path' => null,
'by_reference' => true,
'error_bubbling' => false,

View File

@ -52,13 +52,13 @@ class FileType extends AbstractType
{
$view->set('multipart', true);
$view['file']->set('type', 'file');
$view['file']->set('value', '');
}
public function getDefaultOptions(array $options)
{
return array(
'type' => 'string',
'csrf_protection' => false,
);
}

View File

@ -35,7 +35,7 @@ class RadioType extends AbstractType
;
if ($view->hasParent()) {
$view->set('name', $view->getParent()->get('name'));
$view->set('full_name', $view->getParent()->get('full_name'));
}
}

View File

@ -36,7 +36,6 @@ class RepeatedType extends AbstractType
'options' => array(),
'first_name' => 'first',
'second_name' => 'second',
'csrf_protection' => false,
'error_bubbling' => false,
);
}

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\Extension\Core\Type;
use Symfony\Component\Form\AbstractType;
class SearchType extends AbstractType
{
public function getParent(array $options)
{
return 'text';
}
public function getName()
{
return 'search';
}
}

View File

@ -94,10 +94,8 @@ class TimeType extends AbstractType
'widget' => 'choice',
'input' => 'datetime',
'with_seconds' => false,
'pattern' => null,
'data_timezone' => null,
'user_timezone' => null,
'csrf_protection' => false,
// Don't modify \DateTime classes by reference, we treat
// them like immutable value objects
'by_reference' => false,

View File

@ -15,15 +15,26 @@ use Symfony\Component\Form\Extension\Csrf\Type;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
use Symfony\Component\Form\AbstractExtension;
/**
* This extension protects forms by using a CSRF token
*/
class CsrfExtension extends AbstractExtension
{
private $csrfProvider;
/**
* Constructor.
*
* @param CsrfProviderInterface $csrfProvider The CSRF provider
*/
public function __construct(CsrfProviderInterface $csrfProvider)
{
$this->csrfProvider = $csrfProvider;
}
/**
* {@inheritDoc}
*/
protected function loadTypes()
{
return array(
@ -31,10 +42,18 @@ class CsrfExtension extends AbstractExtension
);
}
/**
* {@inheritDoc}
*/
protected function loadTypeExtensions()
{
return array(
new Type\ChoiceTypeCsrfExtension(),
new Type\DateTypeCsrfExtension(),
new Type\FileTypeCsrfExtension(),
new Type\FormTypeCsrfExtension(),
new Type\RepeatedTypeCsrfExtension(),
new Type\TimeTypeCsrfExtension(),
);
}
}

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\Extension\Csrf\Type;
use Symfony\Component\Form\AbstractTypeExtension;
class ChoiceTypeCsrfExtension extends AbstractTypeExtension
{
public function getDefaultOptions(array $options)
{
return array('csrf_protection' => false);
}
public function getExtendedType()
{
return 'choice';
}
}

View File

@ -22,11 +22,25 @@ class CsrfType extends AbstractType
{
private $csrfProvider;
/**
* Constructor.
*
* @param CsrfProviderInterface $csrfProvider The provider to use to generate the token
*/
public function __construct(CsrfProviderInterface $csrfProvider)
{
$this->csrfProvider = $csrfProvider;
}
/**
* Builds the CSRF field.
*
* A validator is added to check the token value when the CSRF field is added to
* a root form
*
* @param FormBuilder $builder The form builder
* @param array $options The options
*/
public function buildForm(FormBuilder $builder, array $options)
{
$csrfProvider = $options['csrf_provider'];
@ -47,20 +61,31 @@ class CsrfType extends AbstractType
;
}
/**
* {@inheritDoc}
*/
public function getDefaultOptions(array $options)
{
return array(
'csrf_provider' => $this->csrfProvider,
'intention' => null,
'intention' => null,
'property_path' => false,
);
}
/**
* {@inheritDoc}
*/
public function getParent(array $options)
{
return 'hidden';
}
/**
* Returns the name of this form.
*
* @return string 'csrf'
*/
public function getName()
{
return 'csrf';

View File

@ -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\Extension\Csrf\Type;
use Symfony\Component\Form\AbstractTypeExtension;
class DateTypeCsrfExtension extends AbstractTypeExtension
{
public function getDefaultOptions(array $options)
{
return array('csrf_protection' => false);
}
public function getExtendedType()
{
return 'date';
}
}

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\Extension\Csrf\Type;
use Symfony\Component\Form\AbstractTypeExtension;
class FileTypeCsrfExtension extends AbstractTypeExtension
{
public function getDefaultOptions(array $options)
{
return array('csrf_protection' => false);
}
public function getExtendedType()
{
return 'file';
}
}

View File

@ -27,6 +27,12 @@ class FormTypeCsrfExtension extends AbstractTypeExtension
$this->fieldName = $fieldName;
}
/**
* Adds a CSRF field to the form when the CSRF protection is enabled.
*
* @param FormBuilder $builder The form builder
* @param array $options The options
*/
public function buildForm(FormBuilder $builder, array $options)
{
if ($options['csrf_protection']) {
@ -36,11 +42,19 @@ class FormTypeCsrfExtension extends AbstractTypeExtension
$csrfOptions['csrf_provider'] = $options['csrf_provider'];
}
$builder->add($options['csrf_field_name'], 'csrf', $csrfOptions)
->setAttribute('csrf_field_name', $options['csrf_field_name']);
$builder
->add($options['csrf_field_name'], 'csrf', $csrfOptions)
->setAttribute('csrf_field_name', $options['csrf_field_name'])
;
}
}
/**
* Removes CSRF fields from all the form views except the root one.
*
* @param FormView $view The form view
* @param FormInterface $form The form
*/
public function buildViewBottomUp(FormView $view, FormInterface $form)
{
if ($view->hasParent() && $form->hasAttribute('csrf_field_name')) {
@ -52,16 +66,22 @@ class FormTypeCsrfExtension extends AbstractTypeExtension
}
}
/**
* {@inheritDoc}
*/
public function getDefaultOptions(array $options)
{
return array(
'csrf_protection' => $this->enabled,
'csrf_field_name' => $this->fieldName,
'csrf_provider' => null,
'intention' => 'unknown',
'csrf_protection' => $this->enabled,
'csrf_field_name' => $this->fieldName,
'csrf_provider' => null,
'intention' => 'unknown',
);
}
/**
* {@inheritDoc}
*/
public function getExtendedType()
{
return 'form';

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\Extension\Csrf\Type;
use Symfony\Component\Form\AbstractTypeExtension;
class RepeatedTypeCsrfExtension extends AbstractTypeExtension
{
public function getDefaultOptions(array $options)
{
return array('csrf_protection' => false);
}
public function getExtendedType()
{
return 'repeated';
}
}

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\Extension\Csrf\Type;
use Symfony\Component\Form\AbstractTypeExtension;
class TimeTypeCsrfExtension extends AbstractTypeExtension
{
public function getDefaultOptions(array $options)
{
return array('csrf_protection' => false);
}
public function getExtendedType()
{
return 'time';
}
}

View File

@ -63,6 +63,18 @@ class ValidatorTypeGuesser implements FormTypeGuesserInterface
});
}
/**
* @inheritDoc
*/
public function guessMinLength($class, $property)
{
$guesser = $this;
return $this->guess($class, $property, function (Constraint $constraint) use ($guesser) {
return $guesser->guessMinLengthForConstraint($constraint);
});
}
/**
* Guesses a field class name for a given constraint
*
@ -266,6 +278,28 @@ class ValidatorTypeGuesser implements FormTypeGuesserInterface
}
}
/**
* Guesses a field's minimum length based on the given constraint
*
* @param Constraint $constraint The constraint to guess for
* @return Guess The guess for the minimum length
*/
public function guessMinLengthForConstraint(Constraint $constraint)
{
switch (get_class($constraint)) {
case 'Symfony\Component\Validator\Constraints\MinLength':
return new ValueGuess(
$constraint->limit,
Guess::HIGH_CONFIDENCE
);
case 'Symfony\Component\Validator\Constraints\Min':
return new ValueGuess(
strlen((string)$constraint->limit),
Guess::HIGH_CONFIDENCE
);
}
}
/**
* Iterates over the constraints of a property, executes a constraints on
* them and returns the best guess

View File

@ -24,13 +24,6 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
*
* A form is composed of a validator schema and a widget form schema.
*
* Form also takes care of CSRF protection by default.
*
* A CSRF secret can be any random string. If set to false, it disables the
* CSRF protection, and if set to null, it forces the form to use the global
* CSRF secret. If the global CSRF secret is also null, then a random one
* is generated on the fly.
*
* To implement your own form fields, you need to have a thorough understanding
* of the data flow within a form field. A form field stores its data in three
* different representations:
@ -459,6 +452,8 @@ class Form implements \IteratorAggregate, FormInterface
public function bind($clientData)
{
if ($this->readOnly) {
$this->bound = true;
return $this;
}

View File

@ -305,6 +305,7 @@ class FormFactory implements FormFactoryInterface
$typeGuess = $this->guesser->guessType($class, $property);
$maxLengthGuess = $this->guesser->guessMaxLength($class, $property);
$minLengthGuess = $this->guesser->guessMinLength($class, $property);
$requiredGuess = $this->guesser->guessRequired($class, $property);
$type = $typeGuess ? $typeGuess->getType() : 'text';
@ -313,6 +314,14 @@ class FormFactory implements FormFactoryInterface
$options = array_merge(array('max_length' => $maxLengthGuess->getValue()), $options);
}
if ($minLengthGuess) {
if ($maxLengthGuess) {
$options = array_merge(array('pattern' => '.{'.$minLengthGuess->getValue().','.$maxLengthGuess->getValue().'}'), $options);
} else {
$options = array_merge(array('pattern' => '.{'.$minLengthGuess->getValue().',}'), $options);
}
}
if ($requiredGuess) {
$options = array_merge(array('required' => $requiredGuess->getValue()), $options);
}

View File

@ -61,6 +61,13 @@ class FormTypeGuesserChain implements FormTypeGuesserInterface
});
}
public function guessMinLength($class, $property)
{
return $this->guess(function ($guesser) use ($class, $property) {
return $guesser->guessMinLength($class, $property);
});
}
/**
* Executes a closure for each guesser and returns the best guess from the
* return values

View File

@ -42,4 +42,14 @@ interface FormTypeGuesserInterface
* @return Guess A guess for the field's maximum length
*/
function guessMaxLength($class, $property);
/**
* Returns a guess about the field's minimum length
*
* @param string $class The fully qualified class name
* @param string $property The name of the property to guess for
* @return Guess A guess for the field's minimum length
*/
function guessMinLength($class, $property);
}

View File

@ -142,7 +142,7 @@ class PropertyPath implements \IteratorAggregate
*/
public function isProperty($index)
{
return !$this->isIndex($index);
return !$this->isIndex[$index];
}
/**
@ -186,7 +186,25 @@ class PropertyPath implements \IteratorAggregate
*/
public function getValue($objectOrArray)
{
return $this->readPropertyPath($objectOrArray, 0);
for ($i = 0; $i < $this->length; ++$i) {
if (is_object($objectOrArray)) {
$value = $this->readProperty($objectOrArray, $i);
// arrays need to be treated separately (due to PHP bug?)
// http://bugs.php.net/bug.php?id=52133
} else if (is_array($objectOrArray)){
$property = $this->elements[$i];
if (!array_key_exists($property, $objectOrArray)) {
$objectOrArray[$property] = $i + 1 < $this->length ? array() : null;
}
$value =& $objectOrArray[$property];
} else {
throw new UnexpectedTypeException($objectOrArray, 'object or array');
}
$objectOrArray =& $value;
}
return $value;
}
/**
@ -219,78 +237,30 @@ class PropertyPath implements \IteratorAggregate
*/
public function setValue(&$objectOrArray, $value)
{
$this->writePropertyPath($objectOrArray, 0, $value);
}
for ($i = 0, $l = $this->length - 1; $i < $l; ++$i) {
/**
* Recursive implementation of getValue()
*
* @param object|array $objectOrArray The object or array to traverse
* @param integer $currentIndex The current index in the property path
* @return mixed The value at the end of the path
*/
protected function readPropertyPath(&$objectOrArray, $currentIndex)
{
if (!is_object($objectOrArray) && !is_array($objectOrArray)) {
throw new UnexpectedTypeException($objectOrArray, 'object or array');
}
$property = $this->elements[$currentIndex];
if (is_object($objectOrArray)) {
$value = $this->readProperty($objectOrArray, $currentIndex);
// arrays need to be treated separately (due to PHP bug?)
// http://bugs.php.net/bug.php?id=52133
} else {
if (!array_key_exists($property, $objectOrArray)) {
$objectOrArray[$property] = $currentIndex + 1 < $this->length ? array() : null;
}
$value =& $objectOrArray[$property];
}
++$currentIndex;
if ($currentIndex < $this->length) {
return $this->readPropertyPath($value, $currentIndex);
}
return $value;
}
/**
* Recursive implementation of setValue()
*
* @param object|array $objectOrArray The object or array to traverse
* @param integer $currentIndex The current index in the property path
* @param mixed $value The value to set at the end of the
* property path
*/
protected function writePropertyPath(&$objectOrArray, $currentIndex, $value)
{
if (!is_object($objectOrArray) && !is_array($objectOrArray)) {
throw new UnexpectedTypeException($objectOrArray, 'object or array');
}
$property = $this->elements[$currentIndex];
if ($currentIndex + 1 < $this->length) {
if (is_object($objectOrArray)) {
$nestedObject = $this->readProperty($objectOrArray, $currentIndex);
$nestedObject = $this->readProperty($objectOrArray, $i);
// arrays need to be treated separately (due to PHP bug?)
// http://bugs.php.net/bug.php?id=52133
} else {
} else if (is_array($objectOrArray)) {
$property = $this->elements[$i];
if (!array_key_exists($property, $objectOrArray)) {
$objectOrArray[$property] = array();
}
$nestedObject =& $objectOrArray[$property];
} else {
throw new UnexpectedTypeException($objectOrArray, 'object or array');
}
$this->writePropertyPath($nestedObject, $currentIndex + 1, $value);
} else {
$this->writeProperty($objectOrArray, $currentIndex, $value);
$objectOrArray =& $nestedObject;
}
if (!is_object($objectOrArray) && !is_array($objectOrArray)) {
throw new UnexpectedTypeException($objectOrArray, 'object or array');
}
$this->writeProperty($objectOrArray, $i, $value);
}
/**
@ -311,9 +281,10 @@ class PropertyPath implements \IteratorAggregate
return $object[$property];
} else {
$camelProp = $this->camelize($property);
$reflClass = new \ReflectionClass($object);
$getter = 'get'.$this->camelize($property);
$isser = 'is'.$this->camelize($property);
$getter = 'get'.$camelProp;
$isser = 'is'.$camelProp;
if ($reflClass->hasMethod($getter)) {
if (!$reflClass->getMethod($getter)->isPublic()) {

View File

@ -0,0 +1,45 @@
<?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.
*/
/**
* Stub implementation for the intl_is_failure function of the intl extension
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
* @param integer $errorCode The error code returned by intl_get_error_code()
* @return Boolean Whether the error code indicates an error
* @see Symfony\Component\Locale\Stub\StubIntl::isFailure
*/
function intl_is_failure($errorCode) {
return \Symfony\Component\Locale\Stub\StubIntl::isFailure($errorCode);
}
/**
* Stub implementation for the intl_get_error_code function of the intl extension
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
* @return Boolean The error code of the last intl function call or
* StubIntl::U_ZERO_ERROR if no error occurred
* @see Symfony\Component\Locale\Stub\StubIntl::getErrorCode
*/
function intl_get_error_code() {
return \Symfony\Component\Locale\Stub\StubIntl::getErrorCode();
}
/**
* Stub implementation for the intl_get_error_code function of the intl extension
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
* @return Boolean The error message of the last intl function call or
* "U_ZERO_ERROR" if no error occurred
* @see Symfony\Component\Locale\Stub\StubIntl::getErrorMessage
*/
function intl_get_error_message() {
return \Symfony\Component\Locale\Stub\StubIntl::getErrorMessage();
}

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Locale\Stub\DateFormat;
use Symfony\Component\Locale\Exception\NotImplementedException;
use Symfony\Component\Locale\Stub\StubIntl;
use Symfony\Component\Locale\Stub\DateFormat\MonthTransformer;
/**
@ -275,6 +276,8 @@ class FullTransformer
// If month is false, return immediately (intl behavior)
if (false === $month) {
StubIntl::setErrorCode(StubIntl::U_PARSE_ERROR);
return false;
}

View File

@ -0,0 +1,112 @@
<?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\Locale\Stub;
/**
* Provides fake static versions of the global functions in the intl extension
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
abstract class StubIntl
{
/**
* Indicates that no error occurred
* @var integer
*/
const U_ZERO_ERROR = 0;
/**
* Indicates that an invalid argument was passed
* @var integer
*/
const U_ILLEGAL_ARGUMENT_ERROR = 1;
/**
* Indicates that the parse() operation failed
* @var integer
*/
const U_PARSE_ERROR = 9;
/**
* All known error codes
* @var array
*/
private static $errorCodes = array(
self::U_ZERO_ERROR,
self::U_ILLEGAL_ARGUMENT_ERROR,
self::U_PARSE_ERROR,
);
/**
* The error messages of all known error codes
* @var array
*/
private static $errorMessages = array(
self::U_ZERO_ERROR => 'U_ZERO_ERROR',
self::U_ILLEGAL_ARGUMENT_ERROR => 'datefmt_format: takes either an array or an integer timestamp value : U_ILLEGAL_ARGUMENT_ERROR',
self::U_PARSE_ERROR => 'Date parsing failed: U_PARSE_ERROR',
);
/**
* The error code of the last operation
* @var integer
*/
private static $errorCode = self::U_ZERO_ERROR;
/**
* Returns whether the error code indicates a failure
*
* @param integer $errorCode The error code returned by StubIntl::getErrorCode()
* @return Boolean
*/
public static function isFailure($errorCode) {
return in_array($errorCode, static::$errorCodes, true)
&& $errorCode !== self::U_ZERO_ERROR;
}
/**
* Returns the error code of the last operation
*
* Returns StubIntl::U_ZERO_ERROR if no error occurred.
*
* @return integer
*/
public static function getErrorCode() {
return static::$errorCode;
}
/**
* Returns the error message of the last operation
*
* Returns "U_ZERO_ERROR" if no error occurred.
*
* @return string
*/
public static function getErrorMessage() {
return static::$errorMessages[static::$errorCode];
}
/**
* Sets the current error code
*
* @param integer $code One of the error constants in this class
* @throws \InvalidArgumentException If the code is not one of the error
* constants in this class
*/
public static function setErrorCode($code) {
if (!isset(static::$errorMessages[$code])) {
throw new \InvalidArgumentException(sprintf('No such error code: "%s"', $code));
}
static::$errorCode = $code;
}
}

View File

@ -160,10 +160,18 @@ class StubIntlDateFormatter
*/
public function format($timestamp)
{
if (!is_int($timestamp)) {
// intl allows timestamps to be passed as arrays - we don't
if (is_array($timestamp)) {
throw new MethodArgumentValueNotImplementedException(__METHOD__, 'timestamp', $timestamp, 'Only integer unix timestamps are supported');
}
if (!is_int($timestamp)) {
// behave like the intl extension
StubIntl::setErrorCode(StubIntl::U_ILLEGAL_ARGUMENT_ERROR);
return false;
}
$transformer = new FullTransformer($this->getPattern(), $this->getTimeZoneId());
$formatted = $transformer->format($this->createDateTime($timestamp));
@ -311,6 +319,8 @@ class StubIntlDateFormatter
throw new MethodArgumentNotImplementedException(__METHOD__, 'position');
}
StubIntl::setErrorCode(StubIntl::U_ZERO_ERROR);
$dateTime = $this->createDateTime(0);
$transformer = new FullTransformer($this->getPattern(), $this->getTimeZoneId());
return $transformer->parse($dateTime, $value);

View File

@ -32,7 +32,7 @@ class TypeValidator extends ConstraintValidator
}
$this->setMessage($constraint->message, array(
'{{ value }}' => $value,
'{{ value }}' => is_object($value) ? get_class($value) : (string)$value,
'{{ type }}' => $constraint->type,
));

View File

@ -939,6 +939,22 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
);
}
public function testSearch()
{
$form = $this->factory->createNamed('search', 'na&me', 'foo&bar', array(
'property_path' => 'name',
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
[@type="search"]
[@name="na&me"]
[@value="foo&bar"]
[not(@maxlength)]
'
);
}
public function testTime()
{
$form = $this->factory->createNamed('time', 'na&me', '04:05:06', array(

View File

@ -50,16 +50,16 @@ class ResizeFormListenerTest extends \PHPUnit_Framework_TestCase
$this->factory->expects($this->at(0))
->method('createNamed')
->with('text', 1, null, array('property_path' => '[1]'))
->with('text', 1, null, array('property_path' => '[1]', 'max_length' => 10))
->will($this->returnValue($this->getForm('1')));
$this->factory->expects($this->at(1))
->method('createNamed')
->with('text', 2, null, array('property_path' => '[2]'))
->with('text', 2, null, array('property_path' => '[2]', 'max_length' => 10))
->will($this->returnValue($this->getForm('2')));
$data = array(1 => 'string', 2 => 'string');
$event = new DataEvent($this->form, $data);
$listener = new ResizeFormListener($this->factory, 'text', false, false);
$listener = new ResizeFormListener($this->factory, 'text', array('max_length' => '10'), false, false);
$listener->preSetData($event);
$this->assertFalse($this->form->has('0'));
@ -73,7 +73,7 @@ class ResizeFormListenerTest extends \PHPUnit_Framework_TestCase
$data = array();
$event = new DataEvent($this->form, $data);
$listener = new ResizeFormListener($this->factory, 'text', false, false);
$listener = new ResizeFormListener($this->factory, 'text', array(), false, false);
$listener->preSetData($event);
$this->assertFalse($this->form->has('$$name$$'));
@ -85,7 +85,7 @@ class ResizeFormListenerTest extends \PHPUnit_Framework_TestCase
$data = array();
$event = new DataEvent($this->form, $data);
$listener = new ResizeFormListener($this->factory, 'text', true, false);
$listener = new ResizeFormListener($this->factory, 'text', array(), true, false);
$listener->preSetData($event);
$this->assertTrue($this->form->has('$$name$$'));
@ -98,7 +98,7 @@ class ResizeFormListenerTest extends \PHPUnit_Framework_TestCase
{
$data = 'no array or traversable';
$event = new DataEvent($this->form, $data);
$listener = new ResizeFormListener($this->factory, 'text', false, false);
$listener = new ResizeFormListener($this->factory, 'text', array(), false, false);
$listener->preSetData($event);
}
@ -108,7 +108,7 @@ class ResizeFormListenerTest extends \PHPUnit_Framework_TestCase
$data = null;
$event = new DataEvent($this->form, $data);
$listener = new ResizeFormListener($this->factory, 'text', false, false);
$listener = new ResizeFormListener($this->factory, 'text', array(), false, false);
$listener->preSetData($event);
}
@ -118,12 +118,12 @@ class ResizeFormListenerTest extends \PHPUnit_Framework_TestCase
$this->factory->expects($this->once())
->method('createNamed')
->with('text', 1, null, array('property_path' => '[1]'))
->with('text', 1, null, array('property_path' => '[1]', 'max_length' => 10))
->will($this->returnValue($this->getForm('1')));
$data = array(0 => 'string', 1 => 'string');
$event = new DataEvent($this->form, $data);
$listener = new ResizeFormListener($this->factory, 'text', true, false);
$listener = new ResizeFormListener($this->factory, 'text', array('max_length' => 10), true, false);
$listener->preBind($event);
$this->assertTrue($this->form->has('0'));
@ -137,7 +137,7 @@ class ResizeFormListenerTest extends \PHPUnit_Framework_TestCase
$data = array(0 => 'string');
$event = new DataEvent($this->form, $data);
$listener = new ResizeFormListener($this->factory, 'text', false, true);
$listener = new ResizeFormListener($this->factory, 'text', array(), false, true);
$listener->preBind($event);
$this->assertTrue($this->form->has('0'));
@ -151,7 +151,7 @@ class ResizeFormListenerTest extends \PHPUnit_Framework_TestCase
$data = array();
$event = new DataEvent($this->form, $data);
$listener = new ResizeFormListener($this->factory, 'text', false, true);
$listener = new ResizeFormListener($this->factory, 'text', array(), false, true);
$listener->preBind($event);
$this->assertFalse($this->form->has('0'));
@ -164,7 +164,7 @@ class ResizeFormListenerTest extends \PHPUnit_Framework_TestCase
$data = array(0 => 'string', 2 => 'string');
$event = new DataEvent($this->form, $data);
$listener = new ResizeFormListener($this->factory, 'text', false, false);
$listener = new ResizeFormListener($this->factory, 'text', array(), false, false);
$listener->preBind($event);
$this->assertTrue($this->form->has('0'));
@ -179,7 +179,7 @@ class ResizeFormListenerTest extends \PHPUnit_Framework_TestCase
{
$data = 'no array or traversable';
$event = new DataEvent($this->form, $data);
$listener = new ResizeFormListener($this->factory, 'text', false, false);
$listener = new ResizeFormListener($this->factory, 'text', array(), false, false);
$listener->preBind($event);
}
@ -189,7 +189,7 @@ class ResizeFormListenerTest extends \PHPUnit_Framework_TestCase
$data = null;
$event = new DataEvent($this->form, $data);
$listener = new ResizeFormListener($this->factory, 'text', false, true);
$listener = new ResizeFormListener($this->factory, 'text', array(), false, true);
$listener->preBind($event);
$this->assertFalse($this->form->has('1'));
@ -202,7 +202,7 @@ class ResizeFormListenerTest extends \PHPUnit_Framework_TestCase
$data = '';
$event = new DataEvent($this->form, $data);
$listener = new ResizeFormListener($this->factory, 'text', false, true);
$listener = new ResizeFormListener($this->factory, 'text', array(), false, true);
$listener->preBind($event);
$this->assertFalse($this->form->has('1'));
@ -214,7 +214,7 @@ class ResizeFormListenerTest extends \PHPUnit_Framework_TestCase
$data = array(0 => 'first', 1 => 'second', 2 => 'third');
$event = new FilterDataEvent($this->form, $data);
$listener = new ResizeFormListener($this->factory, 'text', false, true);
$listener = new ResizeFormListener($this->factory, 'text', array(), false, true);
$listener->onBindNormData($event);
$this->assertEquals(array(1 => 'second'), $event->getData());
@ -226,7 +226,7 @@ class ResizeFormListenerTest extends \PHPUnit_Framework_TestCase
$data = array(0 => 'first', 1 => 'second', 2 => 'third');
$event = new FilterDataEvent($this->form, $data);
$listener = new ResizeFormListener($this->factory, 'text', false, false);
$listener = new ResizeFormListener($this->factory, 'text', array(), false, false);
$listener->onBindNormData($event);
$this->assertEquals($data, $event->getData());
@ -239,7 +239,7 @@ class ResizeFormListenerTest extends \PHPUnit_Framework_TestCase
{
$data = 'no array or traversable';
$event = new FilterDataEvent($this->form, $data);
$listener = new ResizeFormListener($this->factory, 'text', false, false);
$listener = new ResizeFormListener($this->factory, 'text', array(), false, false);
$listener->onBindNormData($event);
}
@ -249,7 +249,7 @@ class ResizeFormListenerTest extends \PHPUnit_Framework_TestCase
$data = null;
$event = new FilterDataEvent($this->form, $data);
$listener = new ResizeFormListener($this->factory, 'text', false, true);
$listener = new ResizeFormListener($this->factory, 'text', array(), false, true);
$listener->onBindNormData($event);
$this->assertEquals(array(), $event->getData());

View File

@ -312,7 +312,7 @@ class ChoiceTypeTest extends TypeTestCase
$this->assertSame(array('b' => 'B', 'd' => 'D'), $view->get('preferred_choices'));
}
public function testAdjustNameForMultipleNonExpanded()
public function testAdjustFullNameForMultipleNonExpanded()
{
$form = $this->factory->createNamed('choice', 'name', null, array(
'multiple' => true,
@ -321,6 +321,6 @@ class ChoiceTypeTest extends TypeTestCase
));
$view = $form->createView();
$this->assertSame('name[]', $view->get('name'));
$this->assertSame('name[]', $view->get('full_name'));
}
}

View File

@ -29,6 +29,9 @@ class CollectionFormTest extends TypeTestCase
{
$form = $this->factory->create('collection', null, array(
'type' => 'field',
'options' => array(
'max_length' => 20,
),
));
$form->setData(array('foo@foo.com', 'foo@bar.com'));
@ -37,18 +40,24 @@ class CollectionFormTest extends TypeTestCase
$this->assertEquals(2, count($form));
$this->assertEquals('foo@foo.com', $form[0]->getData());
$this->assertEquals('foo@bar.com', $form[1]->getData());
$this->assertEquals(20, $form[0]->getAttribute('max_length'));
$this->assertEquals(20, $form[1]->getAttribute('max_length'));
$form->setData(array('foo@baz.com'));
$this->assertTrue($form[0] instanceof Form);
$this->assertFalse(isset($form[1]));
$this->assertEquals(1, count($form));
$this->assertEquals('foo@baz.com', $form[0]->getData());
$this->assertEquals(20, $form[0]->getAttribute('max_length'));
}
public function testSetDataAddsPrototypeIfAllowAdd()
{
$form = $this->factory->create('collection', null, array(
'type' => 'field',
'options' => array(
'max_length' => 20,
),
'allow_add' => true,
'prototype' => true,
));
@ -57,12 +66,14 @@ class CollectionFormTest extends TypeTestCase
$this->assertTrue($form[0] instanceof Form);
$this->assertTrue($form[1] instanceof Form);
$this->assertTrue($form['$$name$$'] instanceof Form);
$this->assertEquals(20, $form['$$name$$']->getAttribute('max_length'));
$this->assertEquals(3, count($form));
$form->setData(array('foo@baz.com'));
$this->assertTrue($form[0] instanceof Form);
$this->assertFalse(isset($form[1]));
$this->assertTrue($form['$$name$$'] instanceof Form);
$this->assertEquals(20, $form['$$name$$']->getAttribute('max_length'));
$this->assertEquals(2, count($form));
}

View File

@ -117,6 +117,7 @@ class FieldTypeTest extends TypeTestCase
$this->assertEquals('name', $view->get('id'));
$this->assertEquals('name', $view->get('name'));
$this->assertEquals('name', $view->get('full_name'));
}
public function testPassIdAndNameToViewWithParent()
@ -126,7 +127,8 @@ class FieldTypeTest extends TypeTestCase
$view = $parent->createView();
$this->assertEquals('parent_child', $view['child']->get('id'));
$this->assertEquals('parent[child]', $view['child']->get('name'));
$this->assertEquals('child', $view['child']->get('name'));
$this->assertEquals('parent[child]', $view['child']->get('full_name'));
}
public function testPassIdAndNameToViewWithGrandParent()
@ -137,7 +139,8 @@ class FieldTypeTest extends TypeTestCase
$view = $parent->createView();
$this->assertEquals('parent_child_grand_child', $view['child']['grand_child']->get('id'));
$this->assertEquals('parent[child][grand_child]', $view['child']['grand_child']->get('name'));
$this->assertEquals('grand_child', $view['child']['grand_child']->get('name'));
$this->assertEquals('parent[child][grand_child]', $view['child']['grand_child']->get('full_name'));
}
public function testPassMaxLengthToView()

View File

@ -0,0 +1,48 @@
<?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\Tests\Component\Form\Extension\Core\Type;
require_once __DIR__ . '/TypeTestCase.php';
use Symfony\Component\HttpFoundation\File\UploadedFile;
class FileTypeTest extends TypeTestCase
{
public function testDontPassValueToView()
{
$form = $this->factory->create('file');
$form->bind(array(
'file' => $this->createUploadedFileMock('abcdef', 'original.jpg', true),
));
$view = $form->createView();
$this->assertEquals('', $view['file']->get('value'));
}
private function createUploadedFileMock($name, $originalName, $valid)
{
$file = $this->getMockBuilder('Symfony\Component\HttpFoundation\File\UploadedFile')
->disableOriginalConstructor()
->getMock();
$file->expects($this->any())
->method('getName')
->will($this->returnValue($name));
$file->expects($this->any())
->method('getOriginalName')
->will($this->returnValue($originalName));
$file->expects($this->any())
->method('isValid')
->will($this->returnValue($valid));
return $file;
}
}

View File

@ -21,13 +21,13 @@ class RadioTypeTest extends TypeTestCase
$this->assertEquals('foobar', $view->get('value'));
}
public function testPassParentNameToView()
public function testPassParentFullNameToView()
{
$parent = $this->factory->createNamed('field', 'parent');
$parent->add($this->factory->createNamed('radio', 'child'));
$view = $parent->createView();
$this->assertEquals('parent', $view['child']->get('name'));
$this->assertEquals('parent', $view['child']->get('full_name'));
}
public function testCheckedIfDataTrue()

View File

@ -159,6 +159,7 @@ class FormTest extends \PHPUnit_Framework_TestCase
$form->bind('new');
$this->assertEquals('initial', $form->getData());
$this->assertTrue($form->isBound());
}
public function testNeverRequiredIfParentNotRequired()
@ -297,6 +298,14 @@ class FormTest extends \PHPUnit_Framework_TestCase
$this->assertTrue($this->form->isValid());
}
public function testValidIfBoundAndReadOnly()
{
$form = $this->getBuilder()->setReadOnly(true)->getForm();
$form->bind('foobar');
$this->assertTrue($form->isValid());
}
public function testNotValidIfNotBound()
{
$this->assertFalse($this->form->isValid());

View File

@ -14,6 +14,7 @@ namespace Symfony\Tests\Component\Locale\Stub;
require_once __DIR__.'/../TestCase.php';
use Symfony\Component\Locale\Locale;
use Symfony\Component\Locale\Stub\StubIntl;
use Symfony\Component\Locale\Stub\StubIntlDateFormatter;
use Symfony\Tests\Component\Locale\TestCase as LocaleTestCase;
@ -55,21 +56,27 @@ class StubIntlDateFormatterTest extends LocaleTestCase
/**
* @dataProvider formatProvider
*/
public function testFormatStub($pattern, $timestamp, $expected)
public function testFormatStub($pattern, $timestamp, $expected, $errorCode = 0, $errorMessage = 'U_ZERO_ERROR')
{
$formatter = $this->createStubFormatter($pattern);
$this->assertSame($expected, $formatter->format($timestamp));
$this->assertSame($errorMessage, StubIntl::getErrorMessage());
$this->assertSame($errorCode, StubIntl::getErrorCode());
$this->assertSame($errorCode != 0, StubIntl::isFailure(StubIntl::getErrorCode()));
}
/**
* @dataProvider formatProvider
*/
public function testFormatIntl($pattern, $timestamp, $expected)
public function testFormatIntl($pattern, $timestamp, $expected, $errorCode = 0, $errorMessage = 'U_ZERO_ERROR')
{
$this->skipIfIntlExtensionIsNotLoaded();
$this->skipIfICUVersionIsTooOld();
$formatter = $this->createIntlFormatter($pattern);
$this->assertSame($expected, $formatter->format($timestamp));
$this->assertSame($errorMessage, intl_get_error_message());
$this->assertSame($errorCode, intl_get_error_code());
$this->assertSame($errorCode != 0, intl_is_failure(intl_get_error_code()));
}
public function formatProvider()
@ -248,6 +255,10 @@ class StubIntlDateFormatterTest extends LocaleTestCase
array('zzz', 0, 'GMT+00:00'),
array('zzzz', 0, 'GMT+00:00'),
array('zzzzz', 0, 'GMT+00:00'),
/* errors */
array('y-M-d', '0', false, 1, 'datefmt_format: takes either an array or an integer timestamp value : U_ILLEGAL_ARGUMENT_ERROR'),
array('y-M-d', 'foobar', false, 1, 'datefmt_format: takes either an array or an integer timestamp value : U_ILLEGAL_ARGUMENT_ERROR'),
);
return $formatData;
@ -481,20 +492,26 @@ class StubIntlDateFormatterTest extends LocaleTestCase
/**
* @dataProvider parseProvider
*/
public function testParseIntl($pattern, $value, $expected)
public function testParseIntl($pattern, $value, $expected, $errorCode = 0, $errorMessage = 'U_ZERO_ERROR')
{
$this->skipIfIntlExtensionIsNotLoaded();
$formatter = $this->createIntlFormatter($pattern);
$this->assertSame($expected, $formatter->parse($value));
$this->assertSame($errorMessage, intl_get_error_message());
$this->assertSame($errorCode, intl_get_error_code());
$this->assertSame($errorCode != 0, intl_is_failure(intl_get_error_code()));
}
/**
* @dataProvider parseProvider
*/
public function testParseStub($pattern, $value, $expected)
public function testParseStub($pattern, $value, $expected, $errorCode = 0, $errorMessage = 'U_ZERO_ERROR')
{
$formatter = $this->createStubFormatter($pattern);
$this->assertSame($expected, $formatter->parse($value));
$this->assertSame($errorMessage, StubIntl::getErrorMessage());
$this->assertSame($errorCode, StubIntl::getErrorCode());
$this->assertSame($errorCode != 0, StubIntl::isFailure(StubIntl::getErrorCode()));
}
public function parseProvider()
@ -511,8 +528,8 @@ class StubIntlDateFormatterTest extends LocaleTestCase
array('y-MMMM-d', '1970-January-1', 0),
// 1 char month
array('y-MMMMM-d', '1970-J-1', false),
array('y-MMMMM-d', '1970-S-1', false),
array('y-MMMMM-d', '1970-J-1', false, 9, 'Date parsing failed: U_PARSE_ERROR'),
array('y-MMMMM-d', '1970-S-1', false, 9, 'Date parsing failed: U_PARSE_ERROR'),
// standalone months
array('y-L-d', '1970-1-1', 0),
@ -520,8 +537,8 @@ class StubIntlDateFormatterTest extends LocaleTestCase
array('y-LLLL-d', '1970-January-1', 0),
// standalone 1 char month
array('y-LLLLL-d', '1970-J-1', false),
array('y-LLLLL-d', '1970-S-1', false),
array('y-LLLLL-d', '1970-J-1', false, 9, 'Date parsing failed: U_PARSE_ERROR'),
array('y-LLLLL-d', '1970-S-1', false, 9, 'Date parsing failed: U_PARSE_ERROR'),
// days
array('y-M-d', '1970-1-1', 0),

View File

@ -13,6 +13,7 @@ namespace Symfony\Tests\Component\Validator\Constraints;
use Symfony\Component\Validator\Constraints\Type;
use Symfony\Component\Validator\Constraints\TypeValidator;
use Symfony\Component\Validator\ConstraintViolation;
class TypeValidatorTest extends \PHPUnit_Framework_TestCase
{
@ -83,6 +84,22 @@ class TypeValidatorTest extends \PHPUnit_Framework_TestCase
$this->assertFalse($this->validator->isValid($value, $constraint));
}
public function testConstraintViolationCanHandleArrayValue()
{
$constraint = new Type(array('type' => 'string'));
$this->validator->isValid(array(0 => "Test"), $constraint);
$violation = new ConstraintViolation(
'{{ value }}',
$this->validator->getMessageParameters(),
'',
'',
''
);
$this->assertEquals('Array', $violation->getMessage());
}
public function getInvalidValues()
{