[Form] Some code cleanup

This commit is contained in:
Victor Berchet 2012-05-25 09:17:31 +02:00 committed by Bernhard Schussek
parent 94f6f7776a
commit bc15e2d68b
7 changed files with 199 additions and 370 deletions

View File

@ -137,11 +137,7 @@ class FormType extends AbstractType
{ {
// Derive "data_class" option from passed "data" object // Derive "data_class" option from passed "data" object
$dataClass = function (Options $options) { $dataClass = function (Options $options) {
if (is_object($options['data'])) { return is_object($options['data']) ? get_class($options['data']) : null;
return get_class($options['data']);
}
return null;
}; };
// Derive "empty_data" closure from "data_class" option // Derive "empty_data" closure from "data_class" option
@ -150,20 +146,12 @@ class FormType extends AbstractType
if (null !== $class) { if (null !== $class) {
return function (FormInterface $form) use ($class) { return function (FormInterface $form) use ($class) {
if ($form->isEmpty() && !$form->isRequired()) { return $form->isEmpty() && !$form->isRequired() ? null : new $class();
return null;
}
return new $class();
}; };
} }
return function (FormInterface $form) { return function (FormInterface $form) {
if (count($form) > 0) { return count($form) > 0 ? array() : '';
return array();
}
return '';
}; };
}; };

View File

@ -34,11 +34,7 @@ class FormValidator extends ConstraintValidator
*/ */
public function __construct(ServerParams $params = null) public function __construct(ServerParams $params = null)
{ {
if (null === $params) { $this->serverParams = $params ?: new ServerParams();
$params = new ServerParams();
}
$this->serverParams = $params;
} }
/** /**
@ -107,34 +103,17 @@ class FormValidator extends ConstraintValidator
$length = $this->serverParams->getContentLength(); $length = $this->serverParams->getContentLength();
if ($form->isRoot() && null !== $length) { if ($form->isRoot() && null !== $length) {
$max = strtoupper(trim($this->serverParams->getPostMaxSize())); $max = $this->serverParams->getPostMaxSize();
if ('' !== $max) { if (null !== $max && $length > $max) {
$maxLength = (int) $max;
switch (substr($max, -1)) {
// The 'G' modifier is available since PHP 5.1.0
case 'G':
$maxLength *= pow(1024, 3);
break;
case 'M':
$maxLength *= pow(1024, 2);
break;
case 'K':
$maxLength *= 1024;
break;
}
if ($length > $maxLength) {
$this->context->addViolation( $this->context->addViolation(
$config->getOption('post_max_size_message'), $config->getOption('post_max_size_message'),
array('{{ max }}' => $max), array('{{ max }}' => $this->serverParams->getNormalizedIniPostMaxSize()),
$length $length
); );
} }
} }
} }
}
/** /**
* Returns whether the data of a form may be walked. * Returns whether the data of a form may be walked.
@ -159,14 +138,10 @@ class FormValidator extends ConstraintValidator
// Non-root forms are validated if validation cascading // Non-root forms are validated if validation cascading
// is enabled in all ancestor forms // is enabled in all ancestor forms
$parent = $form->getParent(); while (null !== ($form = $form->getParent())) {
if (!$form->getConfig()->getOption('cascade_validation')) {
while (null !== $parent) {
if (!$parent->getConfig()->getOption('cascade_validation')) {
return false; return false;
} }
$parent = $parent->getParent();
} }
return true; return true;
@ -181,22 +156,20 @@ class FormValidator extends ConstraintValidator
*/ */
private function getValidationGroups(FormInterface $form) private function getValidationGroups(FormInterface $form)
{ {
$groups = null; do {
while (null !== $form && null === $groups) {
$groups = $form->getConfig()->getOption('validation_groups'); $groups = $form->getConfig()->getOption('validation_groups');
if (null !== $groups) {
if (is_callable($groups)) { if (is_callable($groups)) {
$groups = (array) call_user_func($groups, $form); $groups = call_user_func($groups, $form);
}
$form = $form->getParent();
}
if (null === $groups) {
$groups = array(Constraint::DEFAULT_GROUP);
} }
return (array) $groups; return (array) $groups;
} }
$form = $form->getParent();
} while (null !== $form);
return array(Constraint::DEFAULT_GROUP);
}
} }

View File

@ -12,18 +12,11 @@
namespace Symfony\Component\Form\Extension\Validator\EventListener; namespace Symfony\Component\Form\Extension\Validator\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Form\Extension\Validator\Constraints\Form;
use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapperInterface; use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapperInterface;
use Symfony\Component\Form\FormInterface; use Symfony\Component\Validator\ValidatorInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\Event\DataEvent; use Symfony\Component\Form\Event\DataEvent;
use Symfony\Component\Form\Exception\FormException; use Symfony\Component\Form\Extension\Validator\Constraints\Form;
use Symfony\Component\Form\Util\PropertyPath;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ValidatorInterface;
use Symfony\Component\Validator\ExecutionContext;
/** /**
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>

View File

@ -17,13 +17,40 @@ namespace Symfony\Component\Form\Extension\Validator\Util;
class ServerParams class ServerParams
{ {
/** /**
* Returns the "post_max_size" ini setting. * Returns maximum post size in bytes.
* *
* @return string The value of the ini setting. * @return null|integer The maximum post size in bytes
*/ */
public function getPostMaxSize() public function getPostMaxSize()
{ {
return ini_get('post_max_size'); $iniMax = $this->getNormalizedIniPostMaxSize();
if ('' === $iniMax) {
return null;
}
$max = (int) $iniMax;
switch (substr($iniMax, -1)) {
case 'G':
$max *= 1024;
case 'M':
$max *= 1024;
case 'K':
$max *= 1024;
}
return $max;
}
/**
* Returns the normalized "post_max_size" ini setting.
*
* @return string
*/
public function getNormalizedIniPostMaxSize()
{
return strtoupper(trim(ini_get('post_max_size')));
} }
/** /**

View File

@ -55,7 +55,10 @@ class FormValidatorTest extends \PHPUnit_Framework_TestCase
$this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); $this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$this->factory = $this->getMock('Symfony\Component\Form\FormFactoryInterface'); $this->factory = $this->getMock('Symfony\Component\Form\FormFactoryInterface');
$this->serverParams = $this->getMock('Symfony\Component\Form\Extension\Validator\Util\ServerParams'); $this->serverParams = $this->getMock(
'Symfony\Component\Form\Extension\Validator\Util\ServerParams',
array('getNormalizedIniPostMaxSize', 'getContentLength')
);
$this->validator = new FormValidator($this->serverParams); $this->validator = new FormValidator($this->serverParams);
} }
@ -408,14 +411,18 @@ class FormValidatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('Extra!', $context->getViolations()->get(0)->getMessage()); $this->assertEquals('Extra!', $context->getViolations()->get(0)->getMessage());
} }
public function testViolationIfPostMaxSizeExceeded_GigaUpper()
/**
* @dataProvider getPostMaxSizeFixtures
*/
public function testPostMaxSizeViolation($contentLength, $iniMax, $nbViolation, $msg)
{ {
$this->serverParams->expects($this->any()) $this->serverParams->expects($this->once())
->method('getContentLength') ->method('getContentLength')
->will($this->returnValue(pow(1024, 3) + 1)); ->will($this->returnValue($contentLength));
$this->serverParams->expects($this->any()) $this->serverParams->expects($this->any())
->method('getPostMaxSize') ->method('getNormalizedIniPostMaxSize')
->will($this->returnValue('1G')); ->will($this->returnValue($iniMax));
$context = $this->getExecutionContext(); $context = $this->getExecutionContext();
$options = array('post_max_size_message' => 'Max {{ max }}!'); $options = array('post_max_size_message' => 'Max {{ max }}!');
@ -424,132 +431,33 @@ class FormValidatorTest extends \PHPUnit_Framework_TestCase
$this->validator->initialize($context); $this->validator->initialize($context);
$this->validator->validate($form, new Form()); $this->validator->validate($form, new Form());
$this->assertCount(1, $context->getViolations()); $this->assertCount($nbViolation, $context->getViolations());
$this->assertEquals('Max 1G!', $context->getViolations()->get(0)->getMessage()); if (null !== $msg) {
$this->assertEquals($msg, $context->getViolations()->get(0)->getMessage());
}
} }
public function testViolationIfPostMaxSizeExceeded_GigaLower() public function getPostMaxSizeFixtures()
{ {
$this->serverParams->expects($this->any()) return array(
->method('getContentLength') array(pow(1024, 3) + 1, '1G', 1, 'Max 1G!'),
->will($this->returnValue(pow(1024, 3) + 1)); array(pow(1024, 3), '1G', 0, null),
$this->serverParams->expects($this->any()) array(pow(1024, 2) + 1, '1M', 1, 'Max 1M!'),
->method('getPostMaxSize') array(pow(1024, 2), '1M', 0, null),
->will($this->returnValue('1g')); array(1024 + 1, '1K', 1, 'Max 1K!'),
array(1024, '1K', 0, null),
$context = $this->getExecutionContext(); array(null, '1K', 0, null),
$options = array('post_max_size_message' => 'Max {{ max }}!'); array(1024, '', 0, null),
$form = $this->getBuilder('name', null, $options)->getForm(); );
$this->validator->initialize($context);
$this->validator->validate($form, new Form());
$this->assertCount(1, $context->getViolations());
$this->assertEquals('Max 1G!', $context->getViolations()->get(0)->getMessage());
}
public function testNoViolationIfPostMaxSizeNotExceeded_Giga()
{
$this->serverParams->expects($this->any())
->method('getContentLength')
->will($this->returnValue(pow(1024, 3)));
$this->serverParams->expects($this->any())
->method('getPostMaxSize')
->will($this->returnValue('1G'));
$context = $this->getExecutionContext();
$form = $this->getForm();
$this->validator->initialize($context);
$this->validator->validate($form, new Form());
$this->assertCount(0, $context->getViolations());
}
public function testViolationIfPostMaxSizeExceeded_Mega()
{
$this->serverParams->expects($this->any())
->method('getContentLength')
->will($this->returnValue(pow(1024, 2) + 1));
$this->serverParams->expects($this->any())
->method('getPostMaxSize')
->will($this->returnValue('1M'));
$context = $this->getExecutionContext();
$options = array('post_max_size_message' => 'Max {{ max }}!');
$form = $this->getBuilder('name', null, $options)->getForm();
$this->validator->initialize($context);
$this->validator->validate($form, new Form());
$this->assertCount(1, $context->getViolations());
$this->assertEquals('Max 1M!', $context->getViolations()->get(0)->getMessage());
}
public function testNoViolationIfPostMaxSizeNotExceeded_Mega()
{
$this->serverParams->expects($this->any())
->method('getContentLength')
->will($this->returnValue(pow(1024, 2)));
$this->serverParams->expects($this->any())
->method('getPostMaxSize')
->will($this->returnValue('1M'));
$context = $this->getExecutionContext();
$form = $this->getForm();
$this->validator->initialize($context);
$this->validator->validate($form, new Form());
$this->assertCount(0, $context->getViolations());
}
public function testViolationIfPostMaxSizeExceeded_Kilo()
{
$this->serverParams->expects($this->any())
->method('getContentLength')
->will($this->returnValue(1025));
$this->serverParams->expects($this->any())
->method('getPostMaxSize')
->will($this->returnValue('1K'));
$context = $this->getExecutionContext();
$options = array('post_max_size_message' => 'Max {{ max }}!');
$form = $this->getBuilder('name', null, $options)->getForm();
$this->validator->initialize($context);
$this->validator->validate($form, new Form());
$this->assertCount(1, $context->getViolations());
$this->assertEquals('Max 1K!', $context->getViolations()->get(0)->getMessage());
}
public function testNoViolationIfPostMaxSizeNotExceeded_Kilo()
{
$this->serverParams->expects($this->any())
->method('getContentLength')
->will($this->returnValue(1024));
$this->serverParams->expects($this->any())
->method('getPostMaxSize')
->will($this->returnValue('1K'));
$context = $this->getExecutionContext();
$form = $this->getForm();
$this->validator->initialize($context);
$this->validator->validate($form, new Form());
$this->assertCount(0, $context->getViolations());
} }
public function testNoViolationIfNotRoot() public function testNoViolationIfNotRoot()
{ {
$this->serverParams->expects($this->any()) $this->serverParams->expects($this->once())
->method('getContentLength') ->method('getContentLength')
->will($this->returnValue(1025)); ->will($this->returnValue(1025));
$this->serverParams->expects($this->any()) $this->serverParams->expects($this->never())
->method('getPostMaxSize') ->method('getNormalizedIniPostMaxSize');
->will($this->returnValue('1K'));
$context = $this->getExecutionContext(); $context = $this->getExecutionContext();
$parent = $this->getForm(); $parent = $this->getForm();
@ -562,80 +470,6 @@ class FormValidatorTest extends \PHPUnit_Framework_TestCase
$this->assertCount(0, $context->getViolations()); $this->assertCount(0, $context->getViolations());
} }
public function testNoViolationIfContentLengthNull()
{
$this->serverParams->expects($this->any())
->method('getContentLength')
->will($this->returnValue(null));
$this->serverParams->expects($this->any())
->method('getPostMaxSize')
->will($this->returnValue('1K'));
$context = $this->getExecutionContext();
$form = $this->getForm();
$this->validator->initialize($context);
$this->validator->validate($form, new Form());
$this->assertCount(0, $context->getViolations());
}
public function testTrimPostMaxSize()
{
$this->serverParams->expects($this->any())
->method('getContentLength')
->will($this->returnValue(1025));
$this->serverParams->expects($this->any())
->method('getPostMaxSize')
->will($this->returnValue(' 1K '));
$context = $this->getExecutionContext();
$options = array('post_max_size_message' => 'Max {{ max }}!');
$form = $this->getBuilder('name', null, $options)->getForm();
$this->validator->initialize($context);
$this->validator->validate($form, new Form());
$this->assertCount(1, $context->getViolations());
$this->assertEquals('Max 1K!', $context->getViolations()->get(0)->getMessage());
}
public function testNoViolationIfPostMaxSizeEmpty()
{
$this->serverParams->expects($this->any())
->method('getContentLength')
->will($this->returnValue(1025));
$this->serverParams->expects($this->any())
->method('getPostMaxSize')
->will($this->returnValue(' '));
$context = $this->getExecutionContext();
$form = $this->getForm();
$this->validator->initialize($context);
$this->validator->validate($form, new Form());
$this->assertCount(0, $context->getViolations());
}
public function testNoViolationIfPostMaxSizeNull()
{
$this->serverParams->expects($this->any())
->method('getContentLength')
->will($this->returnValue(1025));
$this->serverParams->expects($this->any())
->method('getPostMaxSize')
->will($this->returnValue(null));
$context = $this->getExecutionContext();
$form = $this->getForm();
$this->validator->initialize($context);
$this->validator->validate($form, new Form());
$this->assertCount(0, $context->getViolations());
}
/** /**
* Access has to be public, as this method is called via callback array * Access has to be public, as this method is called via callback array
* in {@link testValidateFormDataCanHandleCallbackValidationGroups()} * in {@link testValidateFormDataCanHandleCallbackValidationGroups()}

View File

@ -22,7 +22,7 @@ class FormTypeValidatorExtensionTest extends TypeTestCase
$this->assertNull($form->getConfig()->getOption('validation_groups')); $this->assertNull($form->getConfig()->getOption('validation_groups'));
} }
public function testValidationGroupsCanBeSetToString() public function testValidationGroupsTransformedToArray()
{ {
$form = $this->factory->create('form', null, array( $form = $this->factory->create('form', null, array(
'validation_groups' => 'group', 'validation_groups' => 'group',

View File

@ -355,7 +355,7 @@ class PropertyPath implements \IteratorAggregate, PropertyPathInterface
* *
* @param object|array $objectOrArray The object or array to read from. * @param object|array $objectOrArray The object or array to read from.
* @param string $property The property to read. * @param string $property The property to read.
* @param integer $isIndex Whether to interpret the property as index. * @param Boolean $isIndex Whether to interpret the property as index.
* *
* @return mixed The value of the read property * @return mixed The value of the read property
* *
@ -428,7 +428,7 @@ class PropertyPath implements \IteratorAggregate, PropertyPathInterface
* @param object|array $objectOrArray The object or array to write to. * @param object|array $objectOrArray The object or array to write to.
* @param string $property The property to write. * @param string $property The property to write.
* @param string $singular The singular form of the property name or null. * @param string $singular The singular form of the property name or null.
* @param integer $isIndex Whether to interpret the property as index. * @param Boolean $isIndex Whether to interpret the property as index.
* @param mixed $value The value to write. * @param mixed $value The value to write.
* *
* @throws InvalidPropertyException If the property does not exist. * @throws InvalidPropertyException If the property does not exist.
@ -445,78 +445,11 @@ class PropertyPath implements \IteratorAggregate, PropertyPathInterface
$objectOrArray[$property] = $value; $objectOrArray[$property] = $value;
} elseif (is_object($objectOrArray)) { } elseif (is_object($objectOrArray)) {
$reflClass = new ReflectionClass($objectOrArray); $reflClass = new ReflectionClass($objectOrArray);
$setter = 'set'.$this->camelize($property);
$addMethod = null;
$removeMethod = null;
$plural = null;
// Check if the parent has matching methods to add/remove items
if (is_array($value) || $value instanceof Traversable) { if (is_array($value) || $value instanceof Traversable) {
if (null !== $singular) { $methods = $this->findAdderAndRemover($reflClass, $singular);
$addMethod = 'add' . ucfirst($singular); if (null !== $methods) {
$removeMethod = 'remove' . ucfirst($singular); // At this point the add and remove methods have been found
if (!$this->isAccessible($reflClass, $addMethod, 1)) {
throw new InvalidPropertyException(sprintf(
'The public method "%s" with exactly one required parameter was not found on class %s',
$addMethod,
$reflClass->getName()
));
}
if (!$this->isAccessible($reflClass, $removeMethod, 1)) {
throw new InvalidPropertyException(sprintf(
'The public method "%s" with exactly one required parameter was not found on class %s',
$removeMethod,
$reflClass->getName()
));
}
} else {
// The plural form is the last element of the property path
$plural = ucfirst($this->elements[$this->length - 1]);
// Any of the two methods is required, but not yet known
$singulars = (array) FormUtil::singularify($plural);
foreach ($singulars as $singular) {
$addMethodName = 'add' . $singular;
$removeMethodName = 'remove' . $singular;
if ($this->isAccessible($reflClass, $addMethodName, 1)) {
$addMethod = $addMethodName;
}
if ($this->isAccessible($reflClass, $removeMethodName, 1)) {
$removeMethod = $removeMethodName;
}
if ($addMethod && !$removeMethod) {
throw new InvalidPropertyException(sprintf(
'Found the public method "%s", but did not find a public "%s" on class %s',
$addMethodName,
$removeMethodName,
$reflClass->getName()
));
}
if ($removeMethod && !$addMethod) {
throw new InvalidPropertyException(sprintf(
'Found the public method "%s", but did not find a public "%s" on class %s',
$removeMethodName,
$addMethodName,
$reflClass->getName()
));
}
if ($addMethod && $removeMethod) {
break;
}
}
}
}
// Collection with matching adder/remover in $objectOrArray
if ($addMethod && $removeMethod) {
$itemsToAdd = is_object($value) ? clone $value : $value; $itemsToAdd = is_object($value) ? clone $value : $value;
$itemToRemove = array(); $itemToRemove = array();
$previousValue = $this->readProperty($objectOrArray, $property, $isIndex); $previousValue = $this->readProperty($objectOrArray, $property, $isIndex);
@ -539,13 +472,19 @@ class PropertyPath implements \IteratorAggregate, PropertyPathInterface
} }
foreach ($itemToRemove as $item) { foreach ($itemToRemove as $item) {
$objectOrArray->$removeMethod($item); call_user_func(array($objectOrArray, $methods[1]), $item);
} }
foreach ($itemsToAdd as $item) { foreach ($itemsToAdd as $item) {
$objectOrArray->$addMethod($item); call_user_func(array($objectOrArray, $methods[0]), $item);
} }
} elseif ($reflClass->hasMethod($setter)) {
return;
}
}
$setter = 'set'.$this->camelize($property);
if ($reflClass->hasMethod($setter)) {
if (!$reflClass->getMethod($setter)->isPublic()) { if (!$reflClass->getMethod($setter)->isPublic()) {
throw new PropertyAccessDeniedException(sprintf('Method "%s()" is not public in class "%s"', $setter, $reflClass->getName())); throw new PropertyAccessDeniedException(sprintf('Method "%s()" is not public in class "%s"', $setter, $reflClass->getName()));
} }
@ -583,6 +522,81 @@ class PropertyPath implements \IteratorAggregate, PropertyPathInterface
return preg_replace_callback('/(^|_|\.)+(.)/', function ($match) { return ('.' === $match[1] ? '_' : '').strtoupper($match[2]); }, $string); return preg_replace_callback('/(^|_|\.)+(.)/', function ($match) { return ('.' === $match[1] ? '_' : '').strtoupper($match[2]); }, $string);
} }
/**
* Searches for add and remove methods.
*
* @param \ReflectionClass $reflClass The reflection class for the given object
* @param string $singular The singular form of the property name or null.
*
* @return array|null An array containin the adder and remover when found, null otherwise.
*
* @throws InvalidPropertyException If the property does not exist.
*/
private function findAdderAndRemover(\ReflectionClass $reflClass, $singular)
{
if (null !== $singular) {
$addMethod = 'add' . ucfirst($singular);
$removeMethod = 'remove' . ucfirst($singular);
if (!$this->isAccessible($reflClass, $addMethod, 1)) {
throw new InvalidPropertyException(sprintf(
'The public method "%s" with exactly one required parameter was not found on class %s',
$addMethod,
$reflClass->getName()
));
}
if (!$this->isAccessible($reflClass, $removeMethod, 1)) {
throw new InvalidPropertyException(sprintf(
'The public method "%s" with exactly one required parameter was not found on class %s',
$removeMethod,
$reflClass->getName()
));
}
return array($addMethod, $removeMethod);
} else {
// The plural form is the last element of the property path
$plural = ucfirst($this->elements[$this->length - 1]);
// Any of the two methods is required, but not yet known
$singulars = (array) FormUtil::singularify($plural);
foreach ($singulars as $singular) {
$methodsFound = 0;
$addMethodFound = false;
$addMethodName = 'add' . $singular;
$removeMethodName = 'remove' . $singular;
if ($this->isAccessible($reflClass, $addMethodName, 1)) {
$addMethod = $addMethodName;
$addMethodFound = true;
$methodsFound++;
}
if ($this->isAccessible($reflClass, $removeMethodName, 1)) {
$removeMethod = $removeMethodName;
$methodsFound++;
}
if (2 == $methodsFound) {
return array($addMethod, $removeMethod);
}
if (1 == $methodsFound) {
throw new InvalidPropertyException(sprintf(
'Found the public method "%s", but did not find a public "%s" on class %s',
$addMethodFound ? $addMethodName : $removeMethodName,
$addMethodFound ? $removeMethodName : $addMethodName,
$reflClass->getName()
));
}
}
}
return null;
}
/** /**
* Returns whether a method is public and has a specific number of required parameters. * Returns whether a method is public and has a specific number of required parameters.
* *