From 4f5b44bd3bd490309eb2ba7b44df4769816ba729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20L=C5=93uillet?= Date: Sat, 3 Aug 2013 19:26:54 +0200 Subject: twig implementation --- .../symfony/form/Symfony/Component/Form/Form.php | 1046 ++++++++++++++++++++ 1 file changed, 1046 insertions(+) create mode 100644 vendor/symfony/form/Symfony/Component/Form/Form.php (limited to 'vendor/symfony/form/Symfony/Component/Form/Form.php') diff --git a/vendor/symfony/form/Symfony/Component/Form/Form.php b/vendor/symfony/form/Symfony/Component/Form/Form.php new file mode 100644 index 00000000..35135274 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Form.php @@ -0,0 +1,1046 @@ + + * + * 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\RuntimeException; +use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Symfony\Component\Form\Exception\AlreadySubmittedException; +use Symfony\Component\Form\Exception\TransformationFailedException; +use Symfony\Component\Form\Exception\LogicException; +use Symfony\Component\Form\Exception\OutOfBoundsException; +use Symfony\Component\Form\Util\FormUtil; +use Symfony\Component\Form\Util\InheritDataAwareIterator; +use Symfony\Component\PropertyAccess\PropertyPath; + +/** + * Form represents a form. + * + * To implement your own form fields, you need to have a thorough understanding + * of the data flow within a form. A form stores its data in three different + * representations: + * + * (1) the "model" format required by the form's object + * (2) the "normalized" format for internal processing + * (3) the "view" format used for display + * + * A date field, for example, may store a date as "Y-m-d" string (1) in the + * object. To facilitate processing in the field, this value is normalized + * to a DateTime object (2). In the HTML representation of your form, a + * localized string (3) is presented to and modified by the user. + * + * In most cases, format (1) and format (2) will be the same. For example, + * a checkbox field uses a Boolean value for both internal processing and + * storage in the object. In these cases you simply need to set a value + * transformer to convert between formats (2) and (3). You can do this by + * calling addViewTransformer(). + * + * In some cases though it makes sense to make format (1) configurable. To + * demonstrate this, let's extend our above date field to store the value + * either as "Y-m-d" string or as timestamp. Internally we still want to + * use a DateTime object for processing. To convert the data from string/integer + * to DateTime you can set a normalization transformer by calling + * addNormTransformer(). The normalized data is then converted to the displayed + * data as described before. + * + * The conversions (1) -> (2) -> (3) use the transform methods of the transformers. + * The conversions (3) -> (2) -> (1) use the reverseTransform methods of the transformers. + * + * @author Fabien Potencier + * @author Bernhard Schussek + */ +class Form implements \IteratorAggregate, FormInterface +{ + /** + * The form's configuration + * @var FormConfigInterface + */ + private $config; + + /** + * The parent of this form + * @var FormInterface + */ + private $parent; + + /** + * The children of this form + * @var FormInterface[] An array of FormInterface instances + */ + private $children = array(); + + /** + * The errors of this form + * @var FormError[] An array of FormError instances + */ + private $errors = array(); + + /** + * Whether this form was submitted + * @var Boolean + */ + private $submitted = false; + + /** + * The form data in model format + * @var mixed + */ + private $modelData; + + /** + * The form data in normalized format + * @var mixed + */ + private $normData; + + /** + * The form data in view format + * @var mixed + */ + private $viewData; + + /** + * The submitted values that don't belong to any children + * @var array + */ + private $extraData = array(); + + /** + * Whether the data in model, normalized and view format is + * synchronized. Data may not be synchronized if transformation errors + * occur. + * @var Boolean + */ + private $synchronized = true; + + /** + * Whether the form's data has been initialized. + * + * When the data is initialized with its default value, that default value + * is passed through the transformer chain in order to synchronize the + * model, normalized and view format for the first time. This is done + * lazily in order to save performance when {@link setData()} is called + * manually, making the initialization with the configured default value + * superfluous. + * + * @var Boolean + */ + private $defaultDataSet = false; + + /** + * Whether setData() is currently being called. + * @var Boolean + */ + private $lockSetData = false; + + /** + * Creates a new form based on the given configuration. + * + * @param FormConfigInterface $config The form configuration. + * + * @throws LogicException if a data mapper is not provided for a compound form + */ + public function __construct(FormConfigInterface $config) + { + // Compound forms always need a data mapper, otherwise calls to + // `setData` and `add` will not lead to the correct population of + // the child forms. + if ($config->getCompound() && !$config->getDataMapper()) { + throw new LogicException('Compound forms need a data mapper'); + } + + // If the form inherits the data from its parent, it is not necessary + // to call setData() with the default data. + if ($config->getInheritData()) { + $this->defaultDataSet = true; + } + + $this->config = $config; + } + + public function __clone() + { + foreach ($this->children as $key => $child) { + $this->children[$key] = clone $child; + } + } + + /** + * {@inheritdoc} + */ + public function getConfig() + { + return $this->config; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->config->getName(); + } + + /** + * {@inheritdoc} + */ + public function getPropertyPath() + { + if (null !== $this->config->getPropertyPath()) { + return $this->config->getPropertyPath(); + } + + if (null === $this->getName() || '' === $this->getName()) { + return null; + } + + $parent = $this->parent; + + while ($parent && $parent->getConfig()->getInheritData()) { + $parent = $parent->getParent(); + } + + if ($parent && null === $parent->getConfig()->getDataClass()) { + return new PropertyPath('['.$this->getName().']'); + } + + return new PropertyPath($this->getName()); + } + + /** + * {@inheritdoc} + */ + public function isRequired() + { + if (null === $this->parent || $this->parent->isRequired()) { + return $this->config->getRequired(); + } + + return false; + } + + /** + * {@inheritDoc} + */ + public function isDisabled() + { + if (null === $this->parent || !$this->parent->isDisabled()) { + return $this->config->getDisabled(); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function setParent(FormInterface $parent = null) + { + if ($this->submitted) { + throw new AlreadySubmittedException('You cannot set the parent of a submitted form'); + } + + if (null !== $parent && '' === $this->config->getName()) { + throw new LogicException('A form with an empty name cannot have a parent form.'); + } + + $this->parent = $parent; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getParent() + { + return $this->parent; + } + + /** + * {@inheritdoc} + */ + public function getRoot() + { + return $this->parent ? $this->parent->getRoot() : $this; + } + + /** + * {@inheritdoc} + */ + public function isRoot() + { + return null === $this->parent; + } + + /** + * {@inheritdoc} + */ + public function setData($modelData) + { + // If the form is submitted while disabled, it is set to submitted, but the data is not + // changed. In such cases (i.e. when the form is not initialized yet) don't + // abort this method. + if ($this->submitted && $this->defaultDataSet) { + throw new AlreadySubmittedException('You cannot change the data of a submitted form.'); + } + + // If the form inherits its parent's data, disallow data setting to + // prevent merge conflicts + if ($this->config->getInheritData()) { + throw new RuntimeException('You cannot change the data of a form inheriting its parent data.'); + } + + // Don't allow modifications of the configured data if the data is locked + if ($this->config->getDataLocked() && $modelData !== $this->config->getData()) { + return $this; + } + + if (is_object($modelData) && !$this->config->getByReference()) { + $modelData = clone $modelData; + } + + if ($this->lockSetData) { + throw new RuntimeException('A cycle was detected. Listeners to the PRE_SET_DATA event must not call setData(). You should call setData() on the FormEvent object instead.'); + } + + $this->lockSetData = true; + $dispatcher = $this->config->getEventDispatcher(); + + // Hook to change content of the data + if ($dispatcher->hasListeners(FormEvents::PRE_SET_DATA)) { + $event = new FormEvent($this, $modelData); + $dispatcher->dispatch(FormEvents::PRE_SET_DATA, $event); + $modelData = $event->getData(); + } + + // Treat data as strings unless a value transformer exists + if (!$this->config->getViewTransformers() && !$this->config->getModelTransformers() && is_scalar($modelData)) { + $modelData = (string) $modelData; + } + + // Synchronize representations - must not change the content! + $normData = $this->modelToNorm($modelData); + $viewData = $this->normToView($normData); + + // Validate if view data matches data class (unless empty) + if (!FormUtil::isEmpty($viewData)) { + $dataClass = $this->config->getDataClass(); + + $actualType = is_object($viewData) ? 'an instance of class '.get_class($viewData) : ' a(n) '.gettype($viewData); + + if (null === $dataClass && is_object($viewData) && !$viewData instanceof \ArrayAccess) { + $expectedType = 'scalar, array or an instance of \ArrayAccess'; + + throw new LogicException( + 'The form\'s view data is expected to be of type '.$expectedType.', ' . + 'but is '.$actualType.'. You ' . + 'can avoid this error by setting the "data_class" option to ' . + '"'.get_class($viewData).'" or by adding a view transformer ' . + 'that transforms '.$actualType.' to '.$expectedType.'.' + ); + } + + if (null !== $dataClass && !$viewData instanceof $dataClass) { + throw new LogicException( + 'The form\'s view data is expected to be an instance of class ' . + $dataClass.', but is '. $actualType.'. You can avoid this error ' . + 'by setting the "data_class" option to null or by adding a view ' . + 'transformer that transforms '.$actualType.' to an instance of ' . + $dataClass.'.' + ); + } + } + + $this->modelData = $modelData; + $this->normData = $normData; + $this->viewData = $viewData; + $this->defaultDataSet = true; + $this->lockSetData = false; + + // It is not necessary to invoke this method if the form doesn't have children, + // even if the form is compound. + if (count($this->children) > 0) { + // Update child forms from the data + $childrenIterator = new InheritDataAwareIterator($this->children); + $childrenIterator = new \RecursiveIteratorIterator($childrenIterator); + $this->config->getDataMapper()->mapDataToForms($viewData, $childrenIterator); + } + + if ($dispatcher->hasListeners(FormEvents::POST_SET_DATA)) { + $event = new FormEvent($this, $modelData); + $dispatcher->dispatch(FormEvents::POST_SET_DATA, $event); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getData() + { + if ($this->config->getInheritData()) { + if (!$this->parent) { + throw new RuntimeException('The form is configured to inherit its parent\'s data, but does not have a parent.'); + } + + return $this->parent->getData(); + } + + if (!$this->defaultDataSet) { + $this->setData($this->config->getData()); + } + + return $this->modelData; + } + + /** + * {@inheritdoc} + */ + public function getNormData() + { + if ($this->config->getInheritData()) { + if (!$this->parent) { + throw new RuntimeException('The form is configured to inherit its parent\'s data, but does not have a parent.'); + } + + return $this->parent->getNormData(); + } + + if (!$this->defaultDataSet) { + $this->setData($this->config->getData()); + } + + return $this->normData; + } + + /** + * {@inheritdoc} + */ + public function getViewData() + { + if ($this->config->getInheritData()) { + if (!$this->parent) { + throw new RuntimeException('The form is configured to inherit its parent\'s data, but does not have a parent.'); + } + + return $this->parent->getViewData(); + } + + if (!$this->defaultDataSet) { + $this->setData($this->config->getData()); + } + + return $this->viewData; + } + + /** + * {@inheritdoc} + */ + public function getExtraData() + { + return $this->extraData; + } + + /** + * {@inheritdoc} + */ + public function initialize() + { + if (null !== $this->parent) { + throw new RuntimeException('Only root forms should be initialized.'); + } + + // Guarantee that the *_SET_DATA events have been triggered once the + // form is initialized. This makes sure that dynamically added or + // removed fields are already visible after initialization. + if (!$this->defaultDataSet) { + $this->setData($this->config->getData()); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function handleRequest($request = null) + { + $this->config->getRequestHandler()->handleRequest($this, $request); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function submit($submittedData, $clearMissing = true) + { + if ($this->submitted) { + throw new AlreadySubmittedException('A form can only be submitted once'); + } + + // Initialize errors in the very beginning so that we don't lose any + // errors added during listeners + $this->errors = array(); + + // Obviously, a disabled form should not change its data upon submission. + if ($this->isDisabled()) { + $this->submitted = true; + + return $this; + } + + // The data must be initialized if it was not initialized yet. + // This is necessary to guarantee that the *_SET_DATA listeners + // are always invoked before submit() takes place. + if (!$this->defaultDataSet) { + $this->setData($this->config->getData()); + } + + // Treat false as NULL to support binding false to checkboxes. + // Don't convert NULL to a string here in order to determine later + // whether an empty value has been submitted or whether no value has + // been submitted at all. This is important for processing checkboxes + // and radio buttons with empty values. + if (false === $submittedData) { + $submittedData = null; + } elseif (is_scalar($submittedData)) { + $submittedData = (string) $submittedData; + } + + $dispatcher = $this->config->getEventDispatcher(); + + // Hook to change content of the data submitted by the browser + if ($dispatcher->hasListeners(FormEvents::PRE_SUBMIT)) { + $event = new FormEvent($this, $submittedData); + $dispatcher->dispatch(FormEvents::PRE_SUBMIT, $event); + $submittedData = $event->getData(); + } + + // Check whether the form is compound. + // This check is preferable over checking the number of children, + // since forms without children may also be compound. + // (think of empty collection forms) + if ($this->config->getCompound()) { + if (!is_array($submittedData)) { + $submittedData = array(); + } + + foreach ($this->children as $name => $child) { + if (isset($submittedData[$name]) || $clearMissing) { + $child->submit(isset($submittedData[$name]) ? $submittedData[$name] : null, $clearMissing); + unset($submittedData[$name]); + } + } + + $this->extraData = $submittedData; + } + + // Forms that inherit their parents' data also are not processed, + // because then it would be too difficult to merge the changes in + // the child and the parent form. Instead, the parent form also takes + // changes in the grandchildren (i.e. children of the form that inherits + // its parent's data) into account. + // (see InheritDataAwareIterator below) + if ($this->config->getInheritData()) { + $this->submitted = true; + + // When POST_SUBMIT is reached, the data is not yet updated, so pass + // NULL to prevent hard-to-debug bugs. + $dataForPostSubmit = null; + } else { + // If the form is compound, the default data in view format + // is reused. The data of the children is merged into this + // default data using the data mapper. + // If the form is not compound, the submitted data is also the data in view format. + $viewData = $this->config->getCompound() ? $this->viewData : $submittedData; + + if (FormUtil::isEmpty($viewData)) { + $emptyData = $this->config->getEmptyData(); + + if ($emptyData instanceof \Closure) { + /* @var \Closure $emptyData */ + $emptyData = $emptyData($this, $viewData); + } + + $viewData = $emptyData; + } + + // Merge form data from children into existing view data + // It is not necessary to invoke this method if the form has no children, + // even if it is compound. + if (count($this->children) > 0) { + // Use InheritDataAwareIterator to process children of + // descendants that inherit this form's data. + // These descendants will not be submitted normally (see the check + // for $this->config->getInheritData() above) + $childrenIterator = new InheritDataAwareIterator($this->children); + $childrenIterator = new \RecursiveIteratorIterator($childrenIterator); + $this->config->getDataMapper()->mapFormsToData($childrenIterator, $viewData); + } + + $modelData = null; + $normData = null; + + try { + // Normalize data to unified representation + $normData = $this->viewToNorm($viewData); + + // Hook to change content of the data into the normalized + // representation + if ($dispatcher->hasListeners(FormEvents::SUBMIT)) { + $event = new FormEvent($this, $normData); + $dispatcher->dispatch(FormEvents::SUBMIT, $event); + $normData = $event->getData(); + } + + // Synchronize representations - must not change the content! + $modelData = $this->normToModel($normData); + $viewData = $this->normToView($normData); + } catch (TransformationFailedException $e) { + $this->synchronized = false; + } + + $this->submitted = true; + $this->modelData = $modelData; + $this->normData = $normData; + $this->viewData = $viewData; + + $dataForPostSubmit = $viewData; + } + + if ($dispatcher->hasListeners(FormEvents::POST_SUBMIT)) { + $event = new FormEvent($this, $dataForPostSubmit); + $dispatcher->dispatch(FormEvents::POST_SUBMIT, $event); + } + + return $this; + } + + /** + * Alias of {@link submit()}. + * + * @deprecated Deprecated since version 2.3, to be removed in 3.0. Use + * {@link submit()} instead. + */ + public function bind($submittedData) + { + return $this->submit($submittedData); + } + + /** + * {@inheritdoc} + */ + public function addError(FormError $error) + { + if ($this->parent && $this->config->getErrorBubbling()) { + $this->parent->addError($error); + } else { + $this->errors[] = $error; + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function isSubmitted() + { + return $this->submitted; + } + + /** + * Alias of {@link isSubmitted()}. + * + * @deprecated Deprecated since version 2.3, to be removed in 3.0. Use + * {@link isSubmitted()} instead. + */ + public function isBound() + { + return $this->submitted; + } + + /** + * {@inheritdoc} + */ + public function isSynchronized() + { + return $this->synchronized; + } + + /** + * {@inheritdoc} + */ + public function isEmpty() + { + foreach ($this->children as $child) { + if (!$child->isEmpty()) { + return false; + } + } + + return FormUtil::isEmpty($this->modelData) || + // arrays, countables + 0 === count($this->modelData) || + // traversables that are not countable + ($this->modelData instanceof \Traversable && 0 === iterator_count($this->modelData)); + } + + /** + * {@inheritdoc} + */ + public function isValid() + { + if (!$this->submitted) { + return false; + } + + if (count($this->errors) > 0) { + return false; + } + + if (!$this->isDisabled()) { + foreach ($this->children as $child) { + if (!$child->isValid()) { + return false; + } + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function getErrors() + { + return $this->errors; + } + + /** + * Returns a string representation of all form errors (including children errors). + * + * This method should only be used to help debug a form. + * + * @param integer $level The indentation level (used internally) + * + * @return string A string representation of all errors + */ + public function getErrorsAsString($level = 0) + { + $errors = ''; + foreach ($this->errors as $error) { + $errors .= str_repeat(' ', $level).'ERROR: '.$error->getMessage()."\n"; + } + + foreach ($this->children as $key => $child) { + $errors .= str_repeat(' ', $level).$key.":\n"; + if ($err = $child->getErrorsAsString($level + 4)) { + $errors .= $err; + } else { + $errors .= str_repeat(' ', $level + 4)."No errors\n"; + } + } + + return $errors; + } + + /** + * {@inheritdoc} + */ + public function all() + { + return $this->children; + } + + /** + * {@inheritdoc} + */ + public function add($child, $type = null, array $options = array()) + { + if ($this->submitted) { + throw new AlreadySubmittedException('You cannot add children to a submitted form'); + } + + if (!$this->config->getCompound()) { + throw new LogicException('You cannot add children to a simple form. Maybe you should set the option "compound" to true?'); + } + + // Obtain the view data + $viewData = null; + + // If setData() is currently being called, there is no need to call + // mapDataToForms() here, as mapDataToForms() is called at the end + // of setData() anyway. Not doing this check leads to an endless + // recursion when initializing the form lazily and an event listener + // (such as ResizeFormListener) adds fields depending on the data: + // + // * setData() is called, the form is not initialized yet + // * add() is called by the listener (setData() is not complete, so + // the form is still not initialized) + // * getViewData() is called + // * setData() is called since the form is not initialized yet + // * ... endless recursion ... + // + // Also skip data mapping if setData() has not been called yet. + // setData() will be called upon form initialization and data mapping + // will take place by then. + if (!$this->lockSetData && $this->defaultDataSet && !$this->config->getInheritData()) { + $viewData = $this->getViewData(); + } + + if (!$child instanceof FormInterface) { + if (!is_string($child) && !is_int($child)) { + throw new UnexpectedTypeException($child, 'string, integer or Symfony\Component\Form\FormInterface'); + } + + if (null !== $type && !is_string($type) && !$type instanceof FormTypeInterface) { + throw new UnexpectedTypeException($type, 'string or Symfony\Component\Form\FormTypeInterface'); + } + + // Never initialize child forms automatically + $options['auto_initialize'] = false; + + if (null === $type) { + $child = $this->config->getFormFactory()->createForProperty($this->config->getDataClass(), $child, null, $options); + } else { + $child = $this->config->getFormFactory()->createNamed($child, $type, null, $options); + } + } elseif ($child->getConfig()->getAutoInitialize()) { + throw new RuntimeException(sprintf( + 'Automatic initialization is only supported on root forms. You '. + 'should set the "auto_initialize" option to false on the field "%s".', + $child->getName() + )); + } + + $this->children[$child->getName()] = $child; + + $child->setParent($this); + + if (!$this->lockSetData && $this->defaultDataSet && !$this->config->getInheritData()) { + $childrenIterator = new InheritDataAwareIterator(array($child)); + $childrenIterator = new \RecursiveIteratorIterator($childrenIterator); + $this->config->getDataMapper()->mapDataToForms($viewData, $childrenIterator); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function remove($name) + { + if ($this->submitted) { + throw new AlreadySubmittedException('You cannot remove children from a submitted form'); + } + + if (isset($this->children[$name])) { + $this->children[$name]->setParent(null); + + unset($this->children[$name]); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function has($name) + { + return isset($this->children[$name]); + } + + /** + * {@inheritdoc} + */ + public function get($name) + { + if (isset($this->children[$name])) { + return $this->children[$name]; + } + + throw new OutOfBoundsException(sprintf('Child "%s" does not exist.', $name)); + } + + /** + * Returns whether a child with the given name exists (implements the \ArrayAccess interface). + * + * @param string $name The name of the child + * + * @return Boolean + */ + public function offsetExists($name) + { + return $this->has($name); + } + + /** + * Returns the child with the given name (implements the \ArrayAccess interface). + * + * @param string $name The name of the child + * + * @return FormInterface The child form + * + * @throws \OutOfBoundsException If the named child does not exist. + */ + public function offsetGet($name) + { + return $this->get($name); + } + + /** + * Adds a child to the form (implements the \ArrayAccess interface). + * + * @param string $name Ignored. The name of the child is used. + * @param FormInterface $child The child to be added. + * + * @throws AlreadySubmittedException If the form has already been submitted. + * @throws LogicException When trying to add a child to a non-compound form. + * + * @see self::add() + */ + public function offsetSet($name, $child) + { + $this->add($child); + } + + /** + * Removes the child with the given name from the form (implements the \ArrayAccess interface). + * + * @param string $name The name of the child to remove + * + * @throws AlreadySubmittedException If the form has already been submitted. + */ + public function offsetUnset($name) + { + $this->remove($name); + } + + /** + * Returns the iterator for this group. + * + * @return \ArrayIterator + */ + public function getIterator() + { + return new \ArrayIterator($this->children); + } + + /** + * Returns the number of form children (implements the \Countable interface). + * + * @return integer The number of embedded form children + */ + public function count() + { + return count($this->children); + } + + /** + * {@inheritdoc} + */ + public function createView(FormView $parent = null) + { + if (null === $parent && $this->parent) { + $parent = $this->parent->createView(); + } + + return $this->config->getType()->createView($this, $parent); + } + + /** + * Normalizes the value if a normalization transformer is set. + * + * @param mixed $value The value to transform + * + * @return mixed + */ + private function modelToNorm($value) + { + foreach ($this->config->getModelTransformers() as $transformer) { + $value = $transformer->transform($value); + } + + return $value; + } + + /** + * Reverse transforms a value if a normalization transformer is set. + * + * @param string $value The value to reverse transform + * + * @return mixed + */ + private function normToModel($value) + { + $transformers = $this->config->getModelTransformers(); + + for ($i = count($transformers) - 1; $i >= 0; --$i) { + $value = $transformers[$i]->reverseTransform($value); + } + + return $value; + } + + /** + * Transforms the value if a value transformer is set. + * + * @param mixed $value The value to transform + * + * @return mixed + */ + private function normToView($value) + { + // Scalar values should be converted to strings to + // facilitate differentiation between empty ("") and zero (0). + // Only do this for simple forms, as the resulting value in + // compound forms is passed to the data mapper and thus should + // not be converted to a string before. + if (!$this->config->getViewTransformers() && !$this->config->getCompound()) { + return null === $value || is_scalar($value) ? (string) $value : $value; + } + + foreach ($this->config->getViewTransformers() as $transformer) { + $value = $transformer->transform($value); + } + + return $value; + } + + /** + * Reverse transforms a value if a value transformer is set. + * + * @param string $value The value to reverse transform + * + * @return mixed + */ + private function viewToNorm($value) + { + $transformers = $this->config->getViewTransformers(); + + if (!$transformers) { + return '' === $value ? null : $value; + } + + for ($i = count($transformers) - 1; $i >= 0; --$i) { + $value = $transformers[$i]->reverseTransform($value); + } + + return $value; + } +} -- cgit v1.2.3