From d26d931ac5cf756ad555d15227f1b3c8af2c6a33 Mon Sep 17 00:00:00 2001 From: GeLo Date: Mon, 25 Jan 2016 20:26:58 +0100 Subject: [PATCH 1/3] [Form] Add position support --- src/Symfony/Component/Form/ButtonBuilder.php | 40 ++- .../Form/Extension/Core/Type/BaseType.php | 7 +- .../Component/Form/FormConfigBuilder.php | 38 +- src/Symfony/Component/Form/FormOrderer.php | 327 ++++++++++++++++++ .../OrderedFormConfigBuilderInterface.php | 52 +++ .../Form/OrderedFormConfigInterface.php | 27 ++ .../Component/Form/ResolvedFormType.php | 17 + .../Component/Form/Tests/FormOrdererTest.php | 326 +++++++++++++++++ 8 files changed, 829 insertions(+), 5 deletions(-) create mode 100644 src/Symfony/Component/Form/FormOrderer.php create mode 100644 src/Symfony/Component/Form/OrderedFormConfigBuilderInterface.php create mode 100644 src/Symfony/Component/Form/OrderedFormConfigInterface.php create mode 100644 src/Symfony/Component/Form/Tests/FormOrdererTest.php diff --git a/src/Symfony/Component/Form/ButtonBuilder.php b/src/Symfony/Component/Form/ButtonBuilder.php index c65fb303ddcd3..bd920970646c5 100644 --- a/src/Symfony/Component/Form/ButtonBuilder.php +++ b/src/Symfony/Component/Form/ButtonBuilder.php @@ -12,15 +12,16 @@ namespace Symfony\Component\Form; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\Form\Exception\InvalidArgumentException; use Symfony\Component\Form\Exception\BadMethodCallException; +use Symfony\Component\Form\Exception\InvalidArgumentException; +use Symfony\Component\Form\Exception\InvalidConfigurationException; /** * A builder for {@link Button} instances. * * @author Bernhard Schussek */ -class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface +class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface, OrderedFormConfigBuilderInterface { /** * @var bool @@ -47,6 +48,11 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface */ private $attributes = array(); + /** + * @var null|string|array + */ + private $position; + /** * @var array */ @@ -503,6 +509,28 @@ public function setAutoInitialize($initialize) return $this; } + /** + * {@inheritdoc} + */ + public function setPosition($position) + { + if ($this->locked) { + throw new BadMethodCallException('The config builder cannot be modified anymore.'); + } + + if (is_string($position) && ($position !== 'first') && ($position !== 'last')) { + throw new InvalidConfigurationException('If you use position as string, you can only use "first" & "last".'); + } + + if (is_array($position) && !isset($position['before']) && !isset($position['after'])) { + throw new InvalidConfigurationException('If you use position as array, you must at least define the "before" or "after" option.'); + } + + $this->position = $position; + + return $this; + } + /** * Unsupported method. * @@ -752,6 +780,14 @@ public function getAutoInitialize() return false; } + /** + * {@inheritdoc} + */ + public function getPosition() + { + return $this->position; + } + /** * Unsupported method. * diff --git a/src/Symfony/Component/Form/Extension/Core/Type/BaseType.php b/src/Symfony/Component/Form/Extension/Core/Type/BaseType.php index d68337e3bc8c1..6c79a0236547a 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/BaseType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/BaseType.php @@ -32,8 +32,10 @@ abstract class BaseType extends AbstractType */ public function buildForm(FormBuilderInterface $builder, array $options) { - $builder->setDisabled($options['disabled']); - $builder->setAutoInitialize($options['auto_initialize']); + $builder + ->setDisabled($options['disabled']) + ->setAutoInitialize($options['auto_initialize']) + ->setPosition($options['position']); } /** @@ -117,6 +119,7 @@ public function configureOptions(OptionsResolver $resolver) 'attr' => array(), 'translation_domain' => null, 'auto_initialize' => true, + 'position' => null, )); $resolver->setAllowedTypes('attr', 'array'); diff --git a/src/Symfony/Component/Form/FormConfigBuilder.php b/src/Symfony/Component/Form/FormConfigBuilder.php index eee201d93a83e..82e7bf805579c 100644 --- a/src/Symfony/Component/Form/FormConfigBuilder.php +++ b/src/Symfony/Component/Form/FormConfigBuilder.php @@ -13,6 +13,7 @@ use Symfony\Component\Form\Exception\BadMethodCallException; use Symfony\Component\Form\Exception\InvalidArgumentException; +use Symfony\Component\Form\Exception\InvalidConfigurationException; use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Component\PropertyAccess\PropertyPath; use Symfony\Component\PropertyAccess\PropertyPathInterface; @@ -25,7 +26,7 @@ * * @author Bernhard Schussek */ -class FormConfigBuilder implements FormConfigBuilderInterface +class FormConfigBuilder implements FormConfigBuilderInterface, OrderedFormConfigBuilderInterface { /** * Caches a globally unique {@link NativeRequestHandler} instance. @@ -172,6 +173,11 @@ class FormConfigBuilder implements FormConfigBuilderInterface */ private $autoInitialize = false; + /** + * @var null|string|array + */ + private $position; + /** * @var array */ @@ -513,6 +519,14 @@ public function getAutoInitialize() return $this->autoInitialize; } + /** + * {@inheritdoc} + */ + public function getPosition() + { + return $this->position; + } + /** * {@inheritdoc} */ @@ -831,6 +845,28 @@ public function setAutoInitialize($initialize) return $this; } + /** + * {@inheritdoc} + */ + public function setPosition($position) + { + if ($this->locked) { + throw new BadMethodCallException('The config builder cannot be modified anymore.'); + } + + if (is_string($position) && ($position !== 'first') && ($position !== 'last')) { + throw new InvalidConfigurationException('If you use position as string, you can only use "first" & "last".'); + } + + if (is_array($position) && !isset($position['before']) && !isset($position['after'])) { + throw new InvalidConfigurationException('If you use position as array, you must at least define the "before" or "after" option.'); + } + + $this->position = $position; + + return $this; + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Form/FormOrderer.php b/src/Symfony/Component/Form/FormOrderer.php new file mode 100644 index 0000000000000..33c8d510b902f --- /dev/null +++ b/src/Symfony/Component/Form/FormOrderer.php @@ -0,0 +1,327 @@ + + * + * 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\InvalidConfigurationException; + +/** + * Form orderer. + * + * @author GeLo + */ +class FormOrderer +{ + /** + * @var array + */ + private $weights; + + /** + * @var array + */ + private $deferred; + + /** + * @var int + */ + private $firstWeight; + + /** + * @var int + */ + private $currentWeight; + + /** + * @var int + */ + private $lastWeight; + + /** + * Orders the form. + * + * @param FormInterface $form The form. + * + * @return array The ordered form child names. + * + * @throws \Symfony\Component\Form\Exception\InvalidConfigurationException If a position is not valid. + */ + public function order(FormInterface $form) + { + $this->reset(); + + foreach ($form as $child) { + $position = $child->getConfig()->getPosition(); + + if (empty($position)) { + $this->processEmptyPosition($child); + } elseif (is_string($position)) { + $this->processStringPosition($child, $position); + } else { + $this->processArrayPosition($child, $position); + } + } + + asort($this->weights, SORT_NUMERIC); + + return array_keys($this->weights); + } + + /** + * Process the form using the current weight in order to maintain the default order. + * + * @param FormInterface $form The form. + */ + private function processEmptyPosition(FormInterface $form) + { + $this->processWeight($form, $this->currentWeight); + } + + /** + * Process the form using the current first/last weight in order to put your form at the + * first/last position according to the default order. + * + * @param FormInterface $form The form. + * @param string $position The position. + */ + private function processStringPosition(FormInterface $form, $position) + { + if ($position === 'first') { + $this->processFirst($form); + } else { + $this->processLast($form); + } + } + + /** + * Processes an array position (before/after). + * + * FIXME + * + * @param FormInterface $form The form. + * @param array $position The position. + */ + private function processArrayPosition(FormInterface $form, array $position) + { + if (isset($position['before'])) { + $this->processBefore($form, $position['before']); + } + + if (isset($position['after'])) { + $this->processAfter($form, $position['after']); + } + } + + /** + * Process the form using the current first weight in order to put + * your form at the first position according to the default order. + * + * @param FormInterface $form The form. + */ + private function processFirst(FormInterface $form) + { + $this->processWeight($form, $this->firstWeight++); + } + + /** + * Processes the form using the current last weight in order to put + * your form at the last position according to the default order. + * + * @param FormInterface $form The form. + */ + private function processLast(FormInterface $form) + { + $this->processWeight($form, $this->lastWeight + 1); + } + + /** + * Process the form using the weight of the "before" form. + * If the "before" form has not been processed yet, we defer it for the next forms. + * + * @param FormInterface $form The form. + * @param string $before The before form name. + */ + private function processBefore(FormInterface $form, $before) + { + if (!isset($this->weights[$before])) { + $this->processDeferred($form, $before, 'before'); + } else { + $this->processWeight($form, $this->weights[$before]); + } + } + + /** + * Process the form using the weight of the "after" form. + * If the "after" form has not been processed yet, we defer it for the next forms. + * + * @param FormInterface $form The form. + * @param string $after The after form name. + */ + private function processAfter(FormInterface $form, $after) + { + if (!isset($this->weights[$after])) { + $this->processDeferred($form, $after, 'after'); + } else { + $this->processWeight($form, $this->weights[$after] + 1); + } + } + + /** + * Process the form using the given weight. + * + * This method also updates the orderer state accordingly. + * + * @param FormInterface $form The form. + * @param int $weight The weight. + */ + private function processWeight(FormInterface $form, $weight) + { + foreach ($this->weights as &$weightRef) { + if ($weightRef >= $weight) { + ++$weightRef; + } + } + + if ($this->currentWeight >= $weight) { + ++$this->currentWeight; + } + + ++$this->lastWeight; + + $this->weights[$form->getName()] = $weight; + $this->finishWeight($form, $weight); + } + + /** + * Finishes the form weight processing by trying to process deferred forms + * which refers to the current processed form. + * + * @param FormInterface $form The form. + * @param int $weight The weight. + * @param string $position The position (null|before|after). + * + * @return int The new weight. + */ + private function finishWeight(FormInterface $form, $weight, $position = null) + { + if ($position === null) { + foreach (array_keys($this->deferred) as $position) { + $weight = $this->finishWeight($form, $weight, $position); + } + } else { + $name = $form->getName(); + + if (isset($this->deferred[$position][$name])) { + $postIncrement = $position === 'before'; + + foreach ($this->deferred[$position][$name] as $deferred) { + $this->processWeight($deferred, $postIncrement ? $weight++ : ++$weight); + } + + unset($this->deferred[$position][$name]); + } + } + + return $weight; + } + + /** + * Processes a deferred form by checking if it is valid and + * if it does not become a circular or symmetric ordering. + * + * @param FormInterface $form The form. + * @param string $deferred The deferred form name. + * @param string $position The position (before|after). + * + * @throws InvalidConfigurationException If the deferred form does not exist. + */ + private function processDeferred(FormInterface $form, $deferred, $position) + { + if (!$form->getParent()->has($deferred)) { + throw new InvalidConfigurationException(sprintf('The "%s" form is configured to be placed just %s the form "%s" but the form "%s" does not exist.', $form->getName(), $position, $deferred, $deferred)); + } + + $this->deferred[$position][$deferred][] = $form; + + $name = $form->getName(); + + $this->detectCircularDeferred($name, $position); + $this->detectedSymmetricDeferred($name, $deferred, $position); + } + + /** + * Detects circular deferred forms for after/before position such as A => B => C => A. + * + * @param string $name The form name. + * @param string $position The position (before|after) + * @param array $stack The circular stack. + * + * @throws InvalidConfigurationException If there is a circular before/after deferred. + */ + private function detectCircularDeferred($name, $position, array $stack = array()) + { + if (!isset($this->deferred[$position][$name])) { + return; + } + + $stack[] = $name; + + foreach ($this->deferred[$position][$name] as $deferred) { + $deferredName = $deferred->getName(); + + if ($deferredName === $stack[0]) { + $stack[] = $stack[0]; + + throw new InvalidConfigurationException(sprintf('The form ordering cannot be resolved due to conflict in %s positions (%s).', $position, implode(' => ', $stack))); + } + + $this->detectCircularDeferred($deferredName, $position, $stack); + } + } + + /** + * Detects symmetric before/after deferred such as A after B and B after A. + * + * @param string $name The form name. + * @param string $deferred The deferred form name. + * @param string $position The position (before|after). + * + * @throws InvalidConfigurationException If there is a symetric before/after deferred. + */ + private function detectedSymmetricDeferred($name, $deferred, $position) + { + $reversePosition = ($position === 'before') ? 'after' : 'before'; + + if (isset($this->deferred[$reversePosition][$name])) { + foreach ($this->deferred[$reversePosition][$name] as $diff) { + if ($diff->getName() === $deferred) { + throw new InvalidConfigurationException(sprintf('The form ordering does not support symmetrical before/after option (%s <=> %s).', $name, $deferred)); + } + } + } + } + + /** + * Resets the orderer. + */ + private function reset() + { + $this->weights = array(); + $this->deferred = array( + 'before' => array(), + 'after' => array(), + ); + + $this->firstWeight = 0; + $this->currentWeight = 0; + $this->lastWeight = 0; + } +} diff --git a/src/Symfony/Component/Form/OrderedFormConfigBuilderInterface.php b/src/Symfony/Component/Form/OrderedFormConfigBuilderInterface.php new file mode 100644 index 0000000000000..29e697d0ec967 --- /dev/null +++ b/src/Symfony/Component/Form/OrderedFormConfigBuilderInterface.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * @author GeLo + */ +interface OrderedFormConfigBuilderInterface extends OrderedFormConfigInterface +{ + /** + * Sets the form position. + * + * * The position can be `null` to reflect the original forms order. + * + * * The position can be `first` to place this form at the first position. + * If many forms are defined as `first`, the original order between these forms is maintained. + * Warning, `first` does not mean "very first" if there are many forms which are defined as `first` + * or if you set up an other form `before` this form. + * + * * The position can be `last` to place this form at the last position. + * If many forms are defined as `last`, the original order between these forms is maintained. + * Warning, `last` does not mean "very last" if there are many forms which are defined as `last` + * or if you set up an other form `after` this form. + * + * * The position can be `array('before' => 'form_name')` to place this form before the `form_name` form. + * If many forms defines the same `before` form, the original order between these forms is maintained. + * Warning, `before` does not mean "just before" if there are many forms which defined the same `before` form. + * + * * The position can be `array('after' => 'form_name')` to place this form after the `form_name` form. + * If many forms defines the same after form, the original order between these forms is maintained. + * Warning, `after` does not mean "just after" if there are many forms which defined the same `after` form. + * + * You can combine the `after` & `before` options together or with `first` and/or `last` to achieve + * more complex use cases. + * + * @param null|string|array $position The form position. + * + * @throws \Symfony\Component\Form\Exception\InvalidConfigurationException If the position is not valid. + * + * @return self The configuration object. + */ + public function setPosition($position); +} diff --git a/src/Symfony/Component/Form/OrderedFormConfigInterface.php b/src/Symfony/Component/Form/OrderedFormConfigInterface.php new file mode 100644 index 0000000000000..f61ef110d4f3d --- /dev/null +++ b/src/Symfony/Component/Form/OrderedFormConfigInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * @author GeLo + */ +interface OrderedFormConfigInterface +{ + /** + * Gets the form position. + * + * @see FormConfigBuilderInterface::setPosition + * + * @return null|string|array The position. + */ + public function getPosition(); +} diff --git a/src/Symfony/Component/Form/ResolvedFormType.php b/src/Symfony/Component/Form/ResolvedFormType.php index b71be3ceb0a52..fd3b7dda857d2 100644 --- a/src/Symfony/Component/Form/ResolvedFormType.php +++ b/src/Symfony/Component/Form/ResolvedFormType.php @@ -53,6 +53,7 @@ public function __construct(FormTypeInterface $innerType, array $typeExtensions $this->innerType = $innerType; $this->typeExtensions = $typeExtensions; $this->parent = $parent; + $this->orderer = new FormOrderer(); } /** @@ -173,6 +174,22 @@ public function finishView(FormView $view, FormInterface $form, array $options) /* @var FormTypeExtensionInterface $extension */ $extension->finishView($view, $form, $options); } + + $children = $view->children; + $view->children = array(); + + foreach ($this->orderer->order($form) as $name) { + if (!isset($children[$name])) { + continue; + } + + $view->children[$name] = $children[$name]; + unset($children[$name]); + } + + foreach ($children as $name => $child) { + $view->children[$name] = $child; + } } /** diff --git a/src/Symfony/Component/Form/Tests/FormOrdererTest.php b/src/Symfony/Component/Form/Tests/FormOrdererTest.php new file mode 100644 index 0000000000000..eec00130f6c6c --- /dev/null +++ b/src/Symfony/Component/Form/Tests/FormOrdererTest.php @@ -0,0 +1,326 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests; + +use Symfony\Component\Form\Test\FormIntegrationTestCase; + +/** + * Form orderer test. + * + * @author GeLo + */ +class FormOrdererTest extends FormIntegrationTestCase +{ + public function getValidPositions() + { + return array( + // No position + array( + array('foo', 'bar', 'baz', 'bat'), + array('foo', 'bar', 'baz', 'bat'), + ), + + // First position + array( + array('foo' => 'first', 'bar', 'baz', 'bat'), + array('foo', 'bar', 'baz', 'bat'), + ), + array( + array('bar', 'baz', 'foo' => 'first', 'bat'), + array('foo', 'bar', 'baz', 'bat'), + ), + array( + array('bar', 'baz', 'bat', 'foo' => 'first'), + array('foo', 'bar', 'baz', 'bat'), + ), + array( + array('baz', 'foo' => 'first', 'bat', 'bar' => 'first'), + array('foo', 'bar', 'baz', 'bat'), + ), + + // Last position + array( + array('foo', 'bar', 'baz', 'bat' => 'last'), + array('foo', 'bar', 'baz', 'bat'), + ), + array( + array('foo', 'bar', 'bat' => 'last', 'baz'), + array('foo', 'bar', 'baz', 'bat'), + ), + array( + array('bat' => 'last', 'foo', 'bar', 'baz'), + array('foo', 'bar', 'baz', 'bat'), + ), + array( + array('baz' => 'last', 'foo', 'bat' => 'last', 'bar'), + array('foo', 'bar', 'baz', 'bat'), + ), + + // Before position + array( + array('foo' => array('before' => 'bar'), 'bar', 'baz', 'bat'), + array('foo', 'bar', 'baz', 'bat'), + ), + array( + array('bar', 'foo' => array('before' => 'bar'), 'baz', 'bat'), + array('foo', 'bar', 'baz', 'bat'), + ), + array( + array('bar', 'baz', 'bat', 'foo' => array('before' => 'bar')), + array('foo', 'bar', 'baz', 'bat'), + ), + array( + array( + 'bar' => array('before' => 'baz'), + 'foo' => array('before' => 'bar'), + 'bat', + 'baz' => array('before' => 'bat'), + ), + array('foo', 'bar', 'baz', 'bat'), + ), + array( + array( + 'bar' => array('before' => 'bat'), + 'foo' => array('before' => 'bar'), + 'bat', + 'baz' => array('before' => 'bat'), + ), + array('foo', 'bar', 'baz', 'bat'), + ), + + // After position + array( + array('foo', 'bar' => array('after' => 'foo'), 'baz', 'bat'), + array('foo', 'bar', 'baz', 'bat'), + ), + array( + array('bar' => array('after' => 'foo'), 'foo', 'baz', 'bat'), + array('foo', 'bar', 'baz', 'bat'), + ), + array( + array('foo', 'baz', 'bat', 'bar' => array('after' => 'foo')), + array('foo', 'bar', 'baz', 'bat'), + ), + array( + array( + 'foo', + 'baz' => array('after' => 'bar'), + 'bat' => array('after' => 'baz'), + 'bar' => array('after' => 'foo'), + ), + array('foo', 'bar', 'baz', 'bat'), + ), + array( + array( + 'foo', + 'baz' => array('after' => 'bar'), + 'bat' => array('after' => 'bar'), + 'bar' => array('after' => 'foo'), + ), + array('foo', 'bar', 'baz', 'bat'), + ), + + // First & last position + array( + array('foo' => 'first', 'bar', 'baz', 'bat' => 'last'), + array('foo', 'bar', 'baz', 'bat'), + ), + array( + array('bar', 'bat' => 'last', 'foo' => 'first', 'baz'), + array('foo', 'bar', 'baz', 'bat'), + ), + array( + array('baz' => 'last', 'foo' => 'first', 'bar' => 'first', 'bat' => 'last'), + array('foo', 'bar', 'baz', 'bat'), + ), + + // Before & after position + array( + array('foo', 'bar' => array('after' => 'foo', 'before' => 'baz'), 'baz', 'bat'), + array('foo', 'bar', 'baz', 'bat'), + ), + array( + array('foo', 'bar' => array('before' => 'baz', 'after' => 'foo'), 'baz', 'bat'), + array('foo', 'bar', 'baz', 'bat'), + ), + array( + array('bar' => array('after' => 'foo', 'before' => 'baz'), 'foo', 'baz', 'bat'), + array('foo', 'bar', 'baz', 'bat'), + ), + array( + array('bar' => array('before' => 'baz', 'after' => 'foo'), 'foo', 'baz', 'bat'), + array('foo', 'bar', 'baz', 'bat'), + ), + array( + array('foo', 'baz', 'bat', 'bar' => array('after' => 'foo', 'before' => 'baz')), + array('foo', 'bar', 'baz', 'bat'), + ), + array( + array('foo', 'baz', 'bat', 'bar' => array('before' => 'baz', 'after' => 'foo')), + array('foo', 'bar', 'baz', 'bat'), + ), + array( + array('foo' => array('before' => 'bar'), 'bar', 'baz' => array('after' => 'bar'), 'bat'), + array('foo', 'bar', 'baz', 'bat'), + ), + array( + array('bar', 'foo' => array('before' => 'bar'), 'bat', 'baz' => array('after' => 'bar')), + array('foo', 'bar', 'baz', 'bat'), + ), + array( + array('bar' => array('after' => 'foo'), 'foo', 'bat', 'baz' => array('before' => 'bat')), + array('foo', 'bar', 'baz', 'bat'), + ), + array( + array( + 'bar' => array('after' => 'foo', 'before' => 'baz'), + 'foo', + 'bat', + 'baz' => array('before' => 'bat', 'after' => 'bar'), + ), + array('foo', 'bar', 'baz', 'bat'), + ), + + // First, last, before & after position + array( + array( + 'bar' => array('after' => 'foo', 'before' => 'baz'), + 'foo' => 'first', + 'bat' => 'last', + 'baz' => array('before' => 'bat', 'after' => 'bar'), + ), + array('foo', 'bar', 'baz', 'bat'), + ), + array( + array( + 'bar' => array('after' => 'foo', 'before' => 'baz'), + 'foo' => 'first', + 'bat', + 'baz' => array('before' => 'bat'), + 'nan' => 'last', + 'pop' => array('after' => 'ban'), + 'ban', + 'biz' => array('before' => 'nan'), + 'boz' => array('before' => 'biz', array('after' => 'pop')), + + ), + array('foo', 'bar', 'baz', 'bat', 'ban', 'pop', 'boz', 'biz', 'nan'), + ), + ); + } + + public function getInvalidPositions() + { + return array( + // Invalid before/after + array( + array('foo' => array('before' => 'bar')), + 'The "foo" form is configured to be placed just before the form "bar" but the form "bar" does not exist.', + ), + array( + array('foo' => array('after' => 'bar')), + 'The "foo" form is configured to be placed just after the form "bar" but the form "bar" does not exist.', + ), + + // Circular before + array( + array('foo' => array('before' => 'foo')), + 'The form ordering cannot be resolved due to conflict in before positions (foo => foo)', + ), + array( + array('foo' => array('before' => 'bar'), 'bar' => array('before' => 'foo')), + 'The form ordering cannot be resolved due to conflict in before positions (bar => foo => bar).', + ), + array( + array( + 'foo' => array('before' => 'bar'), + 'bar' => array('before' => 'baz'), + 'baz' => array('before' => 'foo'), + ), + 'The form ordering cannot be resolved due to conflict in before positions (baz => bar => foo => baz).', + ), + + // Circular after + array( + array('foo' => array('after' => 'foo')), + 'The form ordering cannot be resolved due to conflict in after positions (foo => foo).', + ), + array( + array('foo' => array('after' => 'bar'), 'bar' => array('after' => 'foo')), + 'The form ordering cannot be resolved due to conflict in after positions (bar => foo => bar).', + ), + array( + array( + 'foo' => array('after' => 'bar'), + 'bar' => array('after' => 'baz'), + 'baz' => array('after' => 'foo'), + ), + 'The form ordering cannot be resolved due to conflict in after positions (baz => bar => foo => baz).', + ), + + // Symetric before/after + array( + array('foo' => array('before' => 'bar'), 'bar' => array('after' => 'foo')), + 'The form ordering does not support symmetrical before/after option (bar <=> foo).', + ), + array( + array( + 'bat' => array('before' => 'baz'), + 'baz' => array('after' => 'bar'), + 'foo' => array('before' => 'bar'), + 'bar' => array('after' => 'foo'), + ), + 'The form ordering does not support symmetrical before/after option (bar <=> foo).', + ), + ); + } + + /** + * @dataProvider getValidPositions + */ + public function testValidPosition(array $config, array $expected) + { + $view = $this->createForm($config)->createView(); + $children = array_values($view->children); + + foreach ($expected as $index => $value) { + $this->assertArrayHasKey($index, $children); + $this->assertArrayHasKey($value, $view->children); + + $this->assertSame($children[$index], $view->children[$value]); + } + } + + /** + * @dataProvider getInvalidPositions + */ + public function testInvalidPosition(array $config, $exceptionMessage = null) + { + $this->setExpectedException('Symfony\Component\Form\Exception\InvalidConfigurationException', $exceptionMessage); + $this->createForm($config)->createView(); + } + + private function createForm(array $config) + { + $builder = $this->factory->createBuilder(); + + foreach ($config as $key => $value) { + if (is_string($key) && (is_string($value) || is_array($value))) { + $builder->add($key, null, array('position' => $value)); + } else { + $builder->add($value); + } + } + + return $builder->getForm(); + } +} From 31241875a0d240288bf532dd9bea794e3b05eeed Mon Sep 17 00:00:00 2001 From: GeLo Date: Tue, 17 Jan 2017 20:53:01 +0100 Subject: [PATCH 2/3] Fix error messages + phpdoc + CS --- src/Symfony/Component/Form/ButtonBuilder.php | 8 +- .../Component/Form/FormConfigBuilder.php | 6 +- src/Symfony/Component/Form/FormOrderer.php | 118 ++++++++---------- 3 files changed, 56 insertions(+), 76 deletions(-) diff --git a/src/Symfony/Component/Form/ButtonBuilder.php b/src/Symfony/Component/Form/ButtonBuilder.php index bd920970646c5..728fe43adc8b9 100644 --- a/src/Symfony/Component/Form/ButtonBuilder.php +++ b/src/Symfony/Component/Form/ButtonBuilder.php @@ -515,15 +515,15 @@ public function setAutoInitialize($initialize) public function setPosition($position) { if ($this->locked) { - throw new BadMethodCallException('The config builder cannot be modified anymore.'); + throw new BadMethodCallException('ButtonBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); } - if (is_string($position) && ($position !== 'first') && ($position !== 'last')) { - throw new InvalidConfigurationException('If you use position as string, you can only use "first" & "last".'); + if (is_string($position) && $position !== 'first' && $position !== 'last') { + throw new InvalidConfigurationException('When using position as a string, the only supported values are "first" and "last".'); } if (is_array($position) && !isset($position['before']) && !isset($position['after'])) { - throw new InvalidConfigurationException('If you use position as array, you must at least define the "before" or "after" option.'); + throw new InvalidConfigurationException('When using position as an array, the "before" or "after" option must be defined.'); } $this->position = $position; diff --git a/src/Symfony/Component/Form/FormConfigBuilder.php b/src/Symfony/Component/Form/FormConfigBuilder.php index 82e7bf805579c..50ab7ba282cf1 100644 --- a/src/Symfony/Component/Form/FormConfigBuilder.php +++ b/src/Symfony/Component/Form/FormConfigBuilder.php @@ -851,15 +851,15 @@ public function setAutoInitialize($initialize) public function setPosition($position) { if ($this->locked) { - throw new BadMethodCallException('The config builder cannot be modified anymore.'); + throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); } if (is_string($position) && ($position !== 'first') && ($position !== 'last')) { - throw new InvalidConfigurationException('If you use position as string, you can only use "first" & "last".'); + throw new InvalidConfigurationException('When using position as a string, the only supported values are "first" and "last".'); } if (is_array($position) && !isset($position['before']) && !isset($position['after'])) { - throw new InvalidConfigurationException('If you use position as array, you must at least define the "before" or "after" option.'); + throw new InvalidConfigurationException('When using position as an array, the "before" or "after" option must be defined.'); } $this->position = $position; diff --git a/src/Symfony/Component/Form/FormOrderer.php b/src/Symfony/Component/Form/FormOrderer.php index 33c8d510b902f..b5168eb8360ee 100644 --- a/src/Symfony/Component/Form/FormOrderer.php +++ b/src/Symfony/Component/Form/FormOrderer.php @@ -20,39 +20,20 @@ */ class FormOrderer { - /** - * @var array - */ private $weights; - - /** - * @var array - */ private $deferred; - - /** - * @var int - */ private $firstWeight; - - /** - * @var int - */ private $currentWeight; - - /** - * @var int - */ private $lastWeight; /** * Orders the form. * - * @param FormInterface $form The form. + * @param FormInterface $form * - * @return array The ordered form child names. + * @return array The ordered form child names * - * @throws \Symfony\Component\Form\Exception\InvalidConfigurationException If a position is not valid. + * @throws InvalidConfigurationException If a position is not valid */ public function order(FormInterface $form) { @@ -76,9 +57,9 @@ public function order(FormInterface $form) } /** - * Process the form using the current weight in order to maintain the default order. + * Process the form using the current weight in order to maintain the default order * - * @param FormInterface $form The form. + * @param FormInterface $form */ private function processEmptyPosition(FormInterface $form) { @@ -87,10 +68,10 @@ private function processEmptyPosition(FormInterface $form) /** * Process the form using the current first/last weight in order to put your form at the - * first/last position according to the default order. + * first/last position according to the default order * - * @param FormInterface $form The form. - * @param string $position The position. + * @param FormInterface $form + * @param string $position */ private function processStringPosition(FormInterface $form, $position) { @@ -102,12 +83,11 @@ private function processStringPosition(FormInterface $form, $position) } /** - * Processes an array position (before/after). - * - * FIXME + * Process the form using the weight of the "before" or "after" form + * If the "before" or "after" form has not been processed yet, we defer it for the next forms * - * @param FormInterface $form The form. - * @param array $position The position. + * @param FormInterface $form + * @param array $position */ private function processArrayPosition(FormInterface $form, array $position) { @@ -122,9 +102,9 @@ private function processArrayPosition(FormInterface $form, array $position) /** * Process the form using the current first weight in order to put - * your form at the first position according to the default order. + * your form at the first position according to the default order * - * @param FormInterface $form The form. + * @param FormInterface $form */ private function processFirst(FormInterface $form) { @@ -133,9 +113,9 @@ private function processFirst(FormInterface $form) /** * Processes the form using the current last weight in order to put - * your form at the last position according to the default order. + * your form at the last position according to the default order * - * @param FormInterface $form The form. + * @param FormInterface $form */ private function processLast(FormInterface $form) { @@ -143,11 +123,11 @@ private function processLast(FormInterface $form) } /** - * Process the form using the weight of the "before" form. - * If the "before" form has not been processed yet, we defer it for the next forms. + * Process the form using the weight of the "before" form + * If the "before" form has not been processed yet, we defer it for the next forms * - * @param FormInterface $form The form. - * @param string $before The before form name. + * @param FormInterface $form + * @param string $before */ private function processBefore(FormInterface $form, $before) { @@ -159,11 +139,11 @@ private function processBefore(FormInterface $form, $before) } /** - * Process the form using the weight of the "after" form. - * If the "after" form has not been processed yet, we defer it for the next forms. + * Process the form using the weight of the "after" form + * If the "after" form has not been processed yet, we defer it for the next forms * - * @param FormInterface $form The form. - * @param string $after The after form name. + * @param FormInterface $form + * @param string $after */ private function processAfter(FormInterface $form, $after) { @@ -175,12 +155,12 @@ private function processAfter(FormInterface $form, $after) } /** - * Process the form using the given weight. + * Process the form using the given weight * - * This method also updates the orderer state accordingly. + * This method also updates the orderer state accordingly * - * @param FormInterface $form The form. - * @param int $weight The weight. + * @param FormInterface $form + * @param int $weight */ private function processWeight(FormInterface $form, $weight) { @@ -202,13 +182,13 @@ private function processWeight(FormInterface $form, $weight) /** * Finishes the form weight processing by trying to process deferred forms - * which refers to the current processed form. + * which refers to the current processed form * - * @param FormInterface $form The form. - * @param int $weight The weight. - * @param string $position The position (null|before|after). + * @param FormInterface $form + * @param int $weight + * @param string $position * - * @return int The new weight. + * @return int The new weight */ private function finishWeight(FormInterface $form, $weight, $position = null) { @@ -235,13 +215,13 @@ private function finishWeight(FormInterface $form, $weight, $position = null) /** * Processes a deferred form by checking if it is valid and - * if it does not become a circular or symmetric ordering. + * if it does not become a circular or symmetric ordering * - * @param FormInterface $form The form. - * @param string $deferred The deferred form name. - * @param string $position The position (before|after). + * @param FormInterface $form + * @param string $deferred + * @param string $position * - * @throws InvalidConfigurationException If the deferred form does not exist. + * @throws InvalidConfigurationException If the deferred form does not exist */ private function processDeferred(FormInterface $form, $deferred, $position) { @@ -258,13 +238,13 @@ private function processDeferred(FormInterface $form, $deferred, $position) } /** - * Detects circular deferred forms for after/before position such as A => B => C => A. + * Detects circular deferred forms for after/before position such as A => B => C => A * - * @param string $name The form name. - * @param string $position The position (before|after) - * @param array $stack The circular stack. + * @param string $name + * @param string $position + * @param array $stack * - * @throws InvalidConfigurationException If there is a circular before/after deferred. + * @throws InvalidConfigurationException If there is a circular before/after deferred */ private function detectCircularDeferred($name, $position, array $stack = array()) { @@ -288,13 +268,13 @@ private function detectCircularDeferred($name, $position, array $stack = array() } /** - * Detects symmetric before/after deferred such as A after B and B after A. + * Detects symmetric before/after deferred such as A after B and B after A * - * @param string $name The form name. - * @param string $deferred The deferred form name. - * @param string $position The position (before|after). + * @param string $name + * @param string $deferred + * @param string $position * - * @throws InvalidConfigurationException If there is a symetric before/after deferred. + * @throws InvalidConfigurationException If there is a symetric before/after deferred */ private function detectedSymmetricDeferred($name, $deferred, $position) { @@ -310,7 +290,7 @@ private function detectedSymmetricDeferred($name, $deferred, $position) } /** - * Resets the orderer. + * Resets the orderer */ private function reset() { From a1c523868546e82facb1bd85efd663a44d0650e0 Mon Sep 17 00:00:00 2001 From: GeLo Date: Tue, 17 Jan 2017 20:58:39 +0100 Subject: [PATCH 3/3] Apply fabbot.io changes --- src/Symfony/Component/Form/ButtonBuilder.php | 2 +- .../Component/Form/FormConfigBuilder.php | 8 +++--- src/Symfony/Component/Form/FormOrderer.php | 26 +++++++++---------- .../OrderedFormConfigBuilderInterface.php | 6 ++--- .../Form/OrderedFormConfigInterface.php | 2 +- .../Component/Form/Tests/FormOrdererTest.php | 1 - 6 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/Symfony/Component/Form/ButtonBuilder.php b/src/Symfony/Component/Form/ButtonBuilder.php index 728fe43adc8b9..4b4f6cf774464 100644 --- a/src/Symfony/Component/Form/ButtonBuilder.php +++ b/src/Symfony/Component/Form/ButtonBuilder.php @@ -64,7 +64,7 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface, Ordered * @param string $name The name of the button * @param array $options The button's options * - * @throws InvalidArgumentException If the name is empty. + * @throws InvalidArgumentException if the name is empty */ public function __construct($name, array $options = array()) { diff --git a/src/Symfony/Component/Form/FormConfigBuilder.php b/src/Symfony/Component/Form/FormConfigBuilder.php index 50ab7ba282cf1..eb6666b267418 100644 --- a/src/Symfony/Component/Form/FormConfigBuilder.php +++ b/src/Symfony/Component/Form/FormConfigBuilder.php @@ -191,8 +191,8 @@ class FormConfigBuilder implements FormConfigBuilderInterface, OrderedFormConfig * @param EventDispatcherInterface $dispatcher The event dispatcher * @param array $options The form options * - * @throws InvalidArgumentException If the data class is not a valid class or if - * the name contains invalid characters. + * @throws InvalidArgumentException if the data class is not a valid class or if + * the name contains invalid characters */ public function __construct($name, $dataClass, EventDispatcherInterface $dispatcher, array $options = array()) { @@ -888,8 +888,8 @@ public function getFormConfig() * * @param string|int $name The tested form name * - * @throws UnexpectedTypeException If the name is not a string or an integer. - * @throws InvalidArgumentException If the name contains invalid characters. + * @throws UnexpectedTypeException if the name is not a string or an integer + * @throws InvalidArgumentException if the name contains invalid characters */ public static function validateName($name) { diff --git a/src/Symfony/Component/Form/FormOrderer.php b/src/Symfony/Component/Form/FormOrderer.php index b5168eb8360ee..1f7571be4079b 100644 --- a/src/Symfony/Component/Form/FormOrderer.php +++ b/src/Symfony/Component/Form/FormOrderer.php @@ -57,7 +57,7 @@ public function order(FormInterface $form) } /** - * Process the form using the current weight in order to maintain the default order + * Process the form using the current weight in order to maintain the default order. * * @param FormInterface $form */ @@ -68,7 +68,7 @@ private function processEmptyPosition(FormInterface $form) /** * Process the form using the current first/last weight in order to put your form at the - * first/last position according to the default order + * first/last position according to the default order. * * @param FormInterface $form * @param string $position @@ -84,7 +84,7 @@ private function processStringPosition(FormInterface $form, $position) /** * Process the form using the weight of the "before" or "after" form - * If the "before" or "after" form has not been processed yet, we defer it for the next forms + * If the "before" or "after" form has not been processed yet, we defer it for the next forms. * * @param FormInterface $form * @param array $position @@ -102,7 +102,7 @@ private function processArrayPosition(FormInterface $form, array $position) /** * Process the form using the current first weight in order to put - * your form at the first position according to the default order + * your form at the first position according to the default order. * * @param FormInterface $form */ @@ -113,7 +113,7 @@ private function processFirst(FormInterface $form) /** * Processes the form using the current last weight in order to put - * your form at the last position according to the default order + * your form at the last position according to the default order. * * @param FormInterface $form */ @@ -124,7 +124,7 @@ private function processLast(FormInterface $form) /** * Process the form using the weight of the "before" form - * If the "before" form has not been processed yet, we defer it for the next forms + * If the "before" form has not been processed yet, we defer it for the next forms. * * @param FormInterface $form * @param string $before @@ -140,7 +140,7 @@ private function processBefore(FormInterface $form, $before) /** * Process the form using the weight of the "after" form - * If the "after" form has not been processed yet, we defer it for the next forms + * If the "after" form has not been processed yet, we defer it for the next forms. * * @param FormInterface $form * @param string $after @@ -155,7 +155,7 @@ private function processAfter(FormInterface $form, $after) } /** - * Process the form using the given weight + * Process the form using the given weight. * * This method also updates the orderer state accordingly * @@ -182,7 +182,7 @@ private function processWeight(FormInterface $form, $weight) /** * Finishes the form weight processing by trying to process deferred forms - * which refers to the current processed form + * which refers to the current processed form. * * @param FormInterface $form * @param int $weight @@ -215,7 +215,7 @@ private function finishWeight(FormInterface $form, $weight, $position = null) /** * Processes a deferred form by checking if it is valid and - * if it does not become a circular or symmetric ordering + * if it does not become a circular or symmetric ordering. * * @param FormInterface $form * @param string $deferred @@ -238,7 +238,7 @@ private function processDeferred(FormInterface $form, $deferred, $position) } /** - * Detects circular deferred forms for after/before position such as A => B => C => A + * Detects circular deferred forms for after/before position such as A => B => C => A. * * @param string $name * @param string $position @@ -268,7 +268,7 @@ private function detectCircularDeferred($name, $position, array $stack = array() } /** - * Detects symmetric before/after deferred such as A after B and B after A + * Detects symmetric before/after deferred such as A after B and B after A. * * @param string $name * @param string $deferred @@ -290,7 +290,7 @@ private function detectedSymmetricDeferred($name, $deferred, $position) } /** - * Resets the orderer + * Resets the orderer. */ private function reset() { diff --git a/src/Symfony/Component/Form/OrderedFormConfigBuilderInterface.php b/src/Symfony/Component/Form/OrderedFormConfigBuilderInterface.php index 29e697d0ec967..c09c556d1725d 100644 --- a/src/Symfony/Component/Form/OrderedFormConfigBuilderInterface.php +++ b/src/Symfony/Component/Form/OrderedFormConfigBuilderInterface.php @@ -42,11 +42,11 @@ interface OrderedFormConfigBuilderInterface extends OrderedFormConfigInterface * You can combine the `after` & `before` options together or with `first` and/or `last` to achieve * more complex use cases. * - * @param null|string|array $position The form position. + * @param null|string|array $position the form position * - * @throws \Symfony\Component\Form\Exception\InvalidConfigurationException If the position is not valid. + * @throws \Symfony\Component\Form\Exception\InvalidConfigurationException if the position is not valid * - * @return self The configuration object. + * @return self the configuration object */ public function setPosition($position); } diff --git a/src/Symfony/Component/Form/OrderedFormConfigInterface.php b/src/Symfony/Component/Form/OrderedFormConfigInterface.php index f61ef110d4f3d..6385e19d1c8b3 100644 --- a/src/Symfony/Component/Form/OrderedFormConfigInterface.php +++ b/src/Symfony/Component/Form/OrderedFormConfigInterface.php @@ -21,7 +21,7 @@ interface OrderedFormConfigInterface * * @see FormConfigBuilderInterface::setPosition * - * @return null|string|array The position. + * @return null|string|array the position */ public function getPosition(); } diff --git a/src/Symfony/Component/Form/Tests/FormOrdererTest.php b/src/Symfony/Component/Form/Tests/FormOrdererTest.php index eec00130f6c6c..de75a4ff77e08 100644 --- a/src/Symfony/Component/Form/Tests/FormOrdererTest.php +++ b/src/Symfony/Component/Form/Tests/FormOrdererTest.php @@ -211,7 +211,6 @@ public function getValidPositions() 'ban', 'biz' => array('before' => 'nan'), 'boz' => array('before' => 'biz', array('after' => 'pop')), - ), array('foo', 'bar', 'baz', 'bat', 'ban', 'pop', 'boz', 'biz', 'nan'), ), pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy