/** * {@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 && null === $this->config->getDataClass()) { $type = 'Symfony\\Component\\Form\\Extension\\Core\\Type\\TextType'; } 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()) { $iterator = new InheritDataAwareIterator(new \ArrayIterator(array($child->getName() => $child))); $iterator = new \RecursiveIteratorIterator($iterator); $this->config->getDataMapper()->mapDataToForms($viewData, $iterator); } return $this; }
/** * {@inheritdoc} */ public function add($child, $type = null, array $options = array()) { if ($this->bound) { throw new AlreadyBoundException('You cannot add children to a bound form'); } if (!$this->config->getCompound()) { throw new Exception('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 ... if (!$this->lockSetData) { $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'); } if (null === $type) { $child = $this->config->getFormFactory()->createForProperty($this->config->getDataClass(), $child, null, $options); } else { $child = $this->config->getFormFactory()->createNamed($child, $type, null, $options); } } $this->children[$child->getName()] = $child; $child->setParent($this); if (!$this->lockSetData) { $this->config->getDataMapper()->mapDataToForms($viewData, array($child)); } return $this; }
/** * {@inheritdoc} */ public function configure($name, array $options, array $metadata, FormConfigInterface $parentConfig) { // The implementation uses a FormTypeGuesserInterface instance in order // to guess the "required" value from different sources (PHPdoc, // validation or doctrine metadata, etc.) $guessed = $this->guesser->guessRequired($parentConfig->getDataClass(), $name); if (null !== $guessed) { $options['required'] = $guessed->getValue(); } return $options; }
/** * Updates the form with default data. * * @param array $modelData The data formatted as expected for the underlying object * * @return Form The current form */ public function setData($modelData) { if ($this->bound) { throw new AlreadyBoundException('You cannot change the data of a bound form'); } // 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; } // Hook to change content of the data $event = new FormEvent($this, $modelData); $this->config->getEventDispatcher()->dispatch(FormEvents::PRE_SET_DATA, $event); // BC until 2.3 $this->config->getEventDispatcher()->dispatch(FormEvents::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 FormException('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 FormException('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->synchronized = true; if ($this->config->getCompound()) { // Update child forms from the data $this->config->getDataMapper()->mapDataToForms($viewData, $this->children); } $event = new FormEvent($this, $modelData); $this->config->getEventDispatcher()->dispatch(FormEvents::POST_SET_DATA, $event); return $this; }
/** * {@inheritdoc} */ public function configure($name, array $options, array $metadata, FormConfigInterface $parentConfig) { if (!isset($options['class'])) { $guessedOptions = $this->guesser->guessType($parentConfig->getDataClass(), $name)->getOptions(); $options['class'] = $guessedOptions['class']; $options['multiple'] = $guessedOptions['multiple']; $options['em'] = $guessedOptions['em']; } if ($metadata['associationType'] & ClassMetadata::TO_MANY) { $options['attr']['multiple'] = true; } // Supported associations are displayed using advanced JavaScript widgets $options['attr']['data-widget'] = 'select2'; // Configure "placeholder" option for entity fields if ($metadata['associationType'] & ClassMetadata::TO_ONE && !isset($options[$placeHolderOptionName = $this->getPlaceholderOptionName()]) && false === $options['required']) { $options[$placeHolderOptionName] = 'form.label.empty_value'; } return $options; }
/** * Creates an unmodifiable copy of a given configuration. * * @param FormConfigInterface $config The configuration to copy. */ public function __construct(FormConfigInterface $config) { $dispatcher = $config->getEventDispatcher(); if (!$dispatcher instanceof UnmodifiableEventDispatcher) { $dispatcher = new UnmodifiableEventDispatcher($dispatcher); } $this->dispatcher = $dispatcher; $this->name = $config->getName(); $this->propertyPath = $config->getPropertyPath(); $this->mapped = $config->getMapped(); $this->byReference = $config->getByReference(); $this->virtual = $config->getVirtual(); $this->compound = $config->getCompound(); $this->types = $config->getTypes(); $this->viewTransformers = $config->getViewTransformers(); $this->modelTransformers = $config->getModelTransformers(); $this->dataMapper = $config->getDataMapper(); $this->validators = $config->getValidators(); $this->required = $config->getRequired(); $this->disabled = $config->getDisabled(); $this->errorBubbling = $config->getErrorBubbling(); $this->emptyData = $config->getEmptyData(); $this->attributes = $config->getAttributes(); $this->data = $config->getData(); $this->dataClass = $config->getDataClass(); $this->options = $config->getOptions(); }
/** * {@inheritdoc} */ public function setData($modelData) { // If the form is bound while disabled, it is set to bound, 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->bound && $this->initialized) { throw new AlreadyBoundException('You cannot change the data of a bound form'); } // 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 FormException('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) || $dispatcher->hasListeners(FormEvents::SET_DATA)) { $event = new FormEvent($this, $modelData); $dispatcher->dispatch(FormEvents::PRE_SET_DATA, $event); // BC until 2.3 $dispatcher->dispatch(FormEvents::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 FormException('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 FormException('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->initialized = 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 $this->config->getDataMapper()->mapDataToForms($viewData, $this->children); } if ($dispatcher->hasListeners(FormEvents::POST_SET_DATA)) { $event = new FormEvent($this, $modelData); $dispatcher->dispatch(FormEvents::POST_SET_DATA, $event); } return $this; }