2010-06-24 09:40:05 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/*
|
2011-01-15 13:29:43 +00:00
|
|
|
* This file is part of the Symfony package.
|
2010-10-02 11:38:11 +01:00
|
|
|
*
|
2010-06-24 09:40:05 +01:00
|
|
|
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
|
|
|
|
*
|
2011-01-15 13:29:43 +00:00
|
|
|
* For the full copyright and license information, please view the LICENSE
|
|
|
|
* file that was distributed with this source code.
|
2010-06-24 09:40:05 +01:00
|
|
|
*/
|
|
|
|
|
2011-01-15 13:29:43 +00:00
|
|
|
namespace Symfony\Component\Form;
|
|
|
|
|
2011-01-28 22:09:22 +00:00
|
|
|
use Symfony\Component\HttpFoundation\Request;
|
|
|
|
use Symfony\Component\HttpFoundation\FileBag;
|
2010-10-02 11:38:11 +01:00
|
|
|
use Symfony\Component\Validator\ValidatorInterface;
|
2010-12-15 14:36:47 +00:00
|
|
|
use Symfony\Component\Form\Exception\FormException;
|
2011-02-02 08:41:57 +00:00
|
|
|
use Symfony\Component\Form\Exception\MissingOptionsException;
|
2011-01-29 21:39:36 +00:00
|
|
|
use Symfony\Component\Form\Exception\AlreadyBoundException;
|
|
|
|
use Symfony\Component\Form\Exception\UnexpectedTypeException;
|
|
|
|
use Symfony\Component\Form\Exception\DanglingFieldException;
|
|
|
|
use Symfony\Component\Form\Exception\FieldDefinitionException;
|
2011-01-25 08:56:37 +00:00
|
|
|
use Symfony\Component\Form\CsrfProvider\CsrfProviderInterface;
|
2010-10-02 11:38:11 +01:00
|
|
|
|
2010-06-24 09:40:05 +01:00
|
|
|
/**
|
|
|
|
* Form represents a form.
|
|
|
|
*
|
|
|
|
* A form is composed of a validator schema and a widget form schema.
|
|
|
|
*
|
2011-01-28 22:09:22 +00:00
|
|
|
* Form also takes care of CSRF protection by default.
|
2010-06-24 09:40:05 +01:00
|
|
|
*
|
2011-01-28 22:09:22 +00:00
|
|
|
* A CSRF secret can be any random string. If set to false, it disables the
|
|
|
|
* CSRF protection, and if set to null, it forces the form to use the global
|
|
|
|
* CSRF secret. If the global CSRF secret is also null, then a random one
|
2010-06-24 09:40:05 +01:00
|
|
|
* is generated on the fly.
|
|
|
|
*
|
2010-10-17 12:45:15 +01:00
|
|
|
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
2011-01-19 12:43:24 +00:00
|
|
|
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
|
2010-06-24 09:40:05 +01:00
|
|
|
*/
|
2011-01-29 21:39:36 +00:00
|
|
|
class Form extends Field implements \IteratorAggregate, FormInterface
|
2010-06-24 09:40:05 +01:00
|
|
|
{
|
2011-01-25 08:56:37 +00:00
|
|
|
/**
|
2011-01-29 21:39:36 +00:00
|
|
|
* Contains all the fields of this group
|
|
|
|
* @var array
|
2011-01-25 08:56:37 +00:00
|
|
|
*/
|
2011-01-29 21:39:36 +00:00
|
|
|
protected $fields = array();
|
|
|
|
|
|
|
|
/**
|
2011-02-01 09:59:18 +00:00
|
|
|
* Contains the names of submitted values who don't belong to any fields
|
2011-01-29 21:39:36 +00:00
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $extraFields = array();
|
2010-06-24 10:24:08 +01:00
|
|
|
|
2011-02-01 09:59:18 +00:00
|
|
|
/**
|
|
|
|
* Whether a request was bound to the form
|
|
|
|
* @var Boolean
|
|
|
|
*/
|
|
|
|
protected $bound = false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stores the class that the data of this form must be instances of
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $dataClass;
|
|
|
|
|
2011-02-01 12:45:27 +00:00
|
|
|
/**
|
|
|
|
* The context used when creating the form
|
|
|
|
* @var FormContext
|
|
|
|
*/
|
|
|
|
protected $context = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a new form with the options stored in the given context
|
|
|
|
*
|
|
|
|
* @param FormContextInterface $context
|
|
|
|
* @param string $name
|
|
|
|
* @param array $options
|
|
|
|
* @return Form
|
|
|
|
*/
|
|
|
|
public static function create(FormContextInterface $context, $name = null, array $options = array())
|
|
|
|
{
|
|
|
|
return new static($name, array_merge($context->getOptions(), $options));
|
|
|
|
}
|
|
|
|
|
2010-06-24 10:24:08 +01:00
|
|
|
/**
|
|
|
|
* Constructor.
|
|
|
|
*
|
2010-10-03 22:00:47 +01:00
|
|
|
* @param string $name
|
|
|
|
* @param array $options
|
2010-06-24 10:24:08 +01:00
|
|
|
*/
|
2011-01-29 21:39:36 +00:00
|
|
|
public function __construct($name = null, array $options = array())
|
2010-06-24 10:24:08 +01:00
|
|
|
{
|
2011-02-01 09:59:18 +00:00
|
|
|
$this->addOption('data_class');
|
2011-01-19 12:43:24 +00:00
|
|
|
$this->addOption('csrf_field_name', '_token');
|
2011-01-25 08:56:37 +00:00
|
|
|
$this->addOption('csrf_provider');
|
2011-01-05 11:36:04 +00:00
|
|
|
$this->addOption('field_factory');
|
2011-01-19 12:43:24 +00:00
|
|
|
$this->addOption('validation_groups');
|
2011-01-29 21:39:36 +00:00
|
|
|
$this->addOption('virtual', false);
|
|
|
|
$this->addOption('validator');
|
2011-02-01 12:45:27 +00:00
|
|
|
$this->addOption('context');
|
2011-01-19 12:43:24 +00:00
|
|
|
|
|
|
|
if (isset($options['validation_groups'])) {
|
|
|
|
$options['validation_groups'] = (array)$options['validation_groups'];
|
|
|
|
}
|
2011-01-05 11:36:04 +00:00
|
|
|
|
2011-02-01 09:59:18 +00:00
|
|
|
if (isset($options['data_class'])) {
|
|
|
|
$this->dataClass = $options['data_class'];
|
|
|
|
}
|
|
|
|
|
2010-07-04 15:20:10 +01:00
|
|
|
parent::__construct($name, $options);
|
2010-12-15 14:36:47 +00:00
|
|
|
|
2011-02-01 09:59:18 +00:00
|
|
|
// Enable CSRF protection
|
2011-01-25 08:56:37 +00:00
|
|
|
if ($this->getOption('csrf_provider')) {
|
|
|
|
if (!$this->getOption('csrf_provider') instanceof CsrfProviderInterface) {
|
|
|
|
throw new FormException('The object passed to the "csrf_provider" option must implement CsrfProviderInterface');
|
|
|
|
}
|
|
|
|
|
2011-01-31 14:40:31 +00:00
|
|
|
$fieldName = $this->getOption('csrf_field_name');
|
2011-01-25 08:56:37 +00:00
|
|
|
$token = $this->getOption('csrf_provider')->generateCsrfToken(get_class($this));
|
|
|
|
|
2011-02-01 09:59:18 +00:00
|
|
|
$this->add(new HiddenField($fieldName, array('data' => $token)));
|
2011-01-19 12:43:24 +00:00
|
|
|
}
|
2010-06-24 10:24:08 +01:00
|
|
|
}
|
|
|
|
|
2011-01-29 21:39:36 +00:00
|
|
|
/**
|
|
|
|
* Clones this group
|
|
|
|
*/
|
|
|
|
public function __clone()
|
|
|
|
{
|
|
|
|
foreach ($this->fields as $name => $field) {
|
|
|
|
$field = clone $field;
|
|
|
|
// this condition is only to "bypass" a PHPUnit bug with mocks
|
|
|
|
if (null !== $field->getParent()) {
|
|
|
|
$field->setParent($this);
|
|
|
|
}
|
|
|
|
$this->fields[$name] = $field;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a new field to this group. A field must have a unique name within
|
|
|
|
* the group. Otherwise the existing field is overwritten.
|
|
|
|
*
|
|
|
|
* If you add a nested group, this group should also be represented in the
|
|
|
|
* object hierarchy. If you want to add a group that operates on the same
|
|
|
|
* hierarchy level, use merge().
|
|
|
|
*
|
|
|
|
* <code>
|
|
|
|
* class Entity
|
|
|
|
* {
|
|
|
|
* public $location;
|
|
|
|
* }
|
|
|
|
*
|
|
|
|
* class Location
|
|
|
|
* {
|
|
|
|
* public $longitude;
|
|
|
|
* public $latitude;
|
|
|
|
* }
|
|
|
|
*
|
|
|
|
* $entity = new Entity();
|
|
|
|
* $entity->location = new Location();
|
|
|
|
*
|
|
|
|
* $form = new Form('entity', $entity, $validator);
|
|
|
|
*
|
|
|
|
* $locationGroup = new Form('location');
|
|
|
|
* $locationGroup->add(new TextField('longitude'));
|
|
|
|
* $locationGroup->add(new TextField('latitude'));
|
|
|
|
*
|
|
|
|
* $form->add($locationGroup);
|
|
|
|
* </code>
|
|
|
|
*
|
|
|
|
* @param FieldInterface|string $field
|
|
|
|
* @return FieldInterface
|
|
|
|
*/
|
|
|
|
public function add($field)
|
|
|
|
{
|
|
|
|
if ($this->isBound()) {
|
|
|
|
throw new AlreadyBoundException('You cannot add fields after binding a form');
|
|
|
|
}
|
|
|
|
|
|
|
|
// if the field is given as string, ask the field factory of the form
|
|
|
|
// to create a field
|
|
|
|
if (!$field instanceof FieldInterface) {
|
|
|
|
if (!is_string($field)) {
|
|
|
|
throw new UnexpectedTypeException($field, 'FieldInterface or string');
|
|
|
|
}
|
|
|
|
|
2011-02-01 09:59:18 +00:00
|
|
|
$factory = $this->getFieldFactory();
|
2011-01-29 21:39:36 +00:00
|
|
|
|
|
|
|
if (!$factory) {
|
2011-02-01 09:59:18 +00:00
|
|
|
throw new FormException('A field factory must be set to automatically create fields');
|
|
|
|
}
|
|
|
|
|
|
|
|
$class = $this->getDataClass();
|
|
|
|
|
|
|
|
if (!$class) {
|
|
|
|
throw new FormException('The data class must be set to automatically create fields');
|
2011-01-29 21:39:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$options = func_num_args() > 1 ? func_get_arg(1) : array();
|
2011-02-01 09:59:18 +00:00
|
|
|
$field = $factory->getInstance($class, $field, $options);
|
2011-01-29 21:39:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if ('' === $field->getKey() || null === $field->getKey()) {
|
|
|
|
throw new FieldDefinitionException('You cannot add anonymous fields');
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->fields[$field->getKey()] = $field;
|
|
|
|
|
|
|
|
$field->setParent($this);
|
|
|
|
|
|
|
|
$data = $this->getTransformedData();
|
|
|
|
|
|
|
|
// if the property "data" is NULL, getTransformedData() returns an empty
|
|
|
|
// string
|
|
|
|
if (!empty($data)) {
|
2011-02-01 09:59:18 +00:00
|
|
|
$field->readProperty($data);
|
2011-01-29 21:39:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return $field;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes the field with the given key.
|
|
|
|
*
|
|
|
|
* @param string $key
|
|
|
|
*/
|
|
|
|
public function remove($key)
|
|
|
|
{
|
|
|
|
$this->fields[$key]->setParent(null);
|
|
|
|
|
|
|
|
unset($this->fields[$key]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns whether a field with the given key exists.
|
|
|
|
*
|
|
|
|
* @param string $key
|
|
|
|
* @return Boolean
|
|
|
|
*/
|
|
|
|
public function has($key)
|
|
|
|
{
|
|
|
|
return isset($this->fields[$key]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the field with the given key.
|
|
|
|
*
|
|
|
|
* @param string $key
|
|
|
|
* @return FieldInterface
|
|
|
|
*/
|
|
|
|
public function get($key)
|
|
|
|
{
|
|
|
|
if (isset($this->fields[$key])) {
|
|
|
|
return $this->fields[$key];
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new \InvalidArgumentException(sprintf('Field "%s" does not exist.', $key));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns all fields in this group
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getFields()
|
|
|
|
{
|
|
|
|
return $this->fields;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an array of visible fields from the current schema.
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getVisibleFields()
|
|
|
|
{
|
|
|
|
return $this->getFieldsByVisibility(false, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an array of visible fields from the current schema.
|
|
|
|
*
|
|
|
|
* This variant of the method will recursively get all the
|
|
|
|
* fields from the nested forms or field groups
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getAllVisibleFields()
|
|
|
|
{
|
|
|
|
return $this->getFieldsByVisibility(false, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an array of hidden fields from the current schema.
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getHiddenFields()
|
|
|
|
{
|
|
|
|
return $this->getFieldsByVisibility(true, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an array of hidden fields from the current schema.
|
|
|
|
*
|
|
|
|
* This variant of the method will recursively get all the
|
|
|
|
* fields from the nested forms or field groups
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getAllHiddenFields()
|
|
|
|
{
|
|
|
|
return $this->getFieldsByVisibility(true, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a filtered array of fields from the current schema.
|
|
|
|
*
|
|
|
|
* @param Boolean $hidden Whether to return hidden fields only or visible fields only
|
|
|
|
* @param Boolean $recursive Whether to recur through embedded schemas
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
protected function getFieldsByVisibility($hidden, $recursive)
|
|
|
|
{
|
|
|
|
$fields = array();
|
|
|
|
$hidden = (Boolean)$hidden;
|
|
|
|
|
|
|
|
foreach ($this->fields as $field) {
|
|
|
|
if ($field instanceof Form && $recursive) {
|
|
|
|
$fields = array_merge($fields, $field->getFieldsByVisibility($hidden, $recursive));
|
|
|
|
} else if ($hidden === $field->isHidden()) {
|
|
|
|
$fields[] = $field;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $fields;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initializes the field group with an object to operate on
|
|
|
|
*
|
|
|
|
* @see FieldInterface
|
|
|
|
*/
|
|
|
|
public function setData($data)
|
|
|
|
{
|
|
|
|
parent::setData($data);
|
|
|
|
|
|
|
|
// get transformed data and pass its values to child fields
|
|
|
|
$data = $this->getTransformedData();
|
|
|
|
|
|
|
|
if (!empty($data) && !is_array($data) && !is_object($data)) {
|
|
|
|
throw new \InvalidArgumentException(sprintf('Expected argument of type object or array, %s given', gettype($data)));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!empty($data)) {
|
2011-02-01 09:59:18 +00:00
|
|
|
if ($this->dataClass && !$data instanceof $this->dataClass) {
|
|
|
|
throw new FormException(sprintf('Form data should be instance of %s', $this->dataClass));
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->readObject($data);
|
2011-01-29 21:39:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the data of the field as it is displayed to the user.
|
|
|
|
*
|
|
|
|
* @see FieldInterface
|
|
|
|
* @return array of field name => value
|
|
|
|
*/
|
|
|
|
public function getDisplayedData()
|
|
|
|
{
|
|
|
|
$values = array();
|
|
|
|
|
|
|
|
foreach ($this->fields as $key => $field) {
|
|
|
|
$values[$key] = $field->getDisplayedData();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $values;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Binds POST data to the field, transforms and validates it.
|
|
|
|
*
|
2011-02-01 09:59:18 +00:00
|
|
|
* @param string|array $data The POST data
|
2011-01-29 21:39:36 +00:00
|
|
|
*/
|
2011-02-01 09:59:18 +00:00
|
|
|
public function submit($data)
|
2011-01-29 21:39:36 +00:00
|
|
|
{
|
2011-02-01 09:59:18 +00:00
|
|
|
if (null === $data) {
|
|
|
|
$data = array();
|
2011-01-29 21:39:36 +00:00
|
|
|
}
|
|
|
|
|
2011-02-01 09:59:18 +00:00
|
|
|
if (!is_array($data)) {
|
|
|
|
throw new UnexpectedTypeException($data, 'array');
|
2011-01-29 21:39:36 +00:00
|
|
|
}
|
|
|
|
|
2011-02-01 09:59:18 +00:00
|
|
|
// remember for later
|
|
|
|
$submittedData = $data;
|
|
|
|
|
2011-01-29 21:39:36 +00:00
|
|
|
foreach ($this->fields as $key => $field) {
|
2011-02-01 09:59:18 +00:00
|
|
|
if (!isset($data[$key])) {
|
|
|
|
$data[$key] = null;
|
2011-01-29 21:39:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-02-01 09:59:18 +00:00
|
|
|
$data = $this->preprocessData($data);
|
2011-01-29 21:39:36 +00:00
|
|
|
|
2011-02-01 09:59:18 +00:00
|
|
|
foreach ($data as $key => $value) {
|
2011-01-29 21:39:36 +00:00
|
|
|
if ($this->has($key)) {
|
2011-02-01 09:59:18 +00:00
|
|
|
$this->fields[$key]->submit($value);
|
2011-01-29 21:39:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$data = $this->getTransformedData();
|
|
|
|
|
2011-02-01 09:59:18 +00:00
|
|
|
$this->writeObject($data);
|
2011-01-29 21:39:36 +00:00
|
|
|
|
2011-02-01 09:59:18 +00:00
|
|
|
// set and reverse transform the data
|
|
|
|
parent::submit($data);
|
2011-01-29 21:39:36 +00:00
|
|
|
|
|
|
|
$this->extraFields = array();
|
|
|
|
|
2011-02-01 09:59:18 +00:00
|
|
|
foreach ($submittedData as $key => $value) {
|
2011-01-29 21:39:36 +00:00
|
|
|
if (!$this->has($key)) {
|
|
|
|
$this->extraFields[] = $key;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates the child fields from the properties of the given data
|
|
|
|
*
|
2011-02-01 09:59:18 +00:00
|
|
|
* This method calls readProperty() on all child fields that have a
|
2011-01-29 21:39:36 +00:00
|
|
|
* property path set. If a child field has no property path set but
|
2011-02-01 09:59:18 +00:00
|
|
|
* implements FormInterface, writeProperty() is called on its
|
2011-01-29 21:39:36 +00:00
|
|
|
* children instead.
|
|
|
|
*
|
|
|
|
* @param array|object $objectOrArray
|
|
|
|
*/
|
2011-02-01 09:59:18 +00:00
|
|
|
protected function readObject(&$objectOrArray)
|
2011-01-29 21:39:36 +00:00
|
|
|
{
|
|
|
|
$iterator = new RecursiveFieldIterator($this);
|
|
|
|
$iterator = new \RecursiveIteratorIterator($iterator);
|
|
|
|
|
|
|
|
foreach ($iterator as $field) {
|
2011-02-01 09:59:18 +00:00
|
|
|
$field->readProperty($objectOrArray);
|
2011-01-29 21:39:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates all properties of the given data from the child fields
|
|
|
|
*
|
2011-02-01 09:59:18 +00:00
|
|
|
* This method calls writeProperty() on all child fields that have a property
|
2011-01-29 21:39:36 +00:00
|
|
|
* path set. If a child field has no property path set but implements
|
2011-02-01 09:59:18 +00:00
|
|
|
* FormInterface, writeProperty() is called on its children instead.
|
2011-01-29 21:39:36 +00:00
|
|
|
*
|
|
|
|
* @param array|object $objectOrArray
|
|
|
|
*/
|
2011-02-01 09:59:18 +00:00
|
|
|
protected function writeObject(&$objectOrArray)
|
2011-01-29 21:39:36 +00:00
|
|
|
{
|
|
|
|
$iterator = new RecursiveFieldIterator($this);
|
|
|
|
$iterator = new \RecursiveIteratorIterator($iterator);
|
|
|
|
|
|
|
|
foreach ($iterator as $field) {
|
2011-02-01 09:59:18 +00:00
|
|
|
$field->writeProperty($objectOrArray);
|
2011-01-29 21:39:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2011-02-01 09:59:18 +00:00
|
|
|
* Processes the submitted data before it is passed to the individual fields
|
2011-01-29 21:39:36 +00:00
|
|
|
*
|
|
|
|
* The data is in the user format.
|
|
|
|
*
|
|
|
|
* @param array $data
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
protected function preprocessData(array $data)
|
|
|
|
{
|
|
|
|
return $data;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
|
|
|
public function isVirtual()
|
|
|
|
{
|
|
|
|
return $this->getOption('virtual');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2011-02-01 09:59:18 +00:00
|
|
|
* Returns whether this form was submitted with extra fields
|
2011-01-29 21:39:36 +00:00
|
|
|
*
|
|
|
|
* @return Boolean
|
|
|
|
*/
|
2011-02-01 09:59:18 +00:00
|
|
|
public function isSubmittedWithExtraFields()
|
2011-01-29 21:39:36 +00:00
|
|
|
{
|
|
|
|
// TODO: integrate the field names in the error message
|
|
|
|
return count($this->extraFields) > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns whether the field is valid.
|
|
|
|
*
|
|
|
|
* @return Boolean
|
|
|
|
*/
|
|
|
|
public function isValid()
|
|
|
|
{
|
|
|
|
if (!parent::isValid()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($this->fields as $field) {
|
|
|
|
if (!$field->isValid()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* {@inheritDoc}
|
|
|
|
*/
|
|
|
|
public function addError(FieldError $error, PropertyPathIterator $pathIterator = null, $type = null)
|
|
|
|
{
|
|
|
|
if (null !== $pathIterator) {
|
|
|
|
if ($type === self::FIELD_ERROR && $pathIterator->hasNext()) {
|
|
|
|
$pathIterator->next();
|
|
|
|
|
|
|
|
if ($pathIterator->isProperty() && $pathIterator->current() === 'fields') {
|
|
|
|
$pathIterator->next();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->has($pathIterator->current()) && !$this->get($pathIterator->current())->isHidden()) {
|
|
|
|
$this->get($pathIterator->current())->addError($error, $pathIterator, $type);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else if ($type === self::DATA_ERROR) {
|
|
|
|
$iterator = new RecursiveFieldIterator($this);
|
|
|
|
$iterator = new \RecursiveIteratorIterator($iterator);
|
|
|
|
|
|
|
|
foreach ($iterator as $field) {
|
|
|
|
if (null !== ($fieldPath = $field->getPropertyPath())) {
|
|
|
|
if ($fieldPath->getElement(0) === $pathIterator->current() && !$field->isHidden()) {
|
|
|
|
if ($pathIterator->hasNext()) {
|
|
|
|
$pathIterator->next();
|
|
|
|
}
|
|
|
|
|
|
|
|
$field->addError($error, $pathIterator, $type);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
parent::addError($error);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns whether the field requires a multipart form.
|
|
|
|
*
|
|
|
|
* @return Boolean
|
|
|
|
*/
|
|
|
|
public function isMultipart()
|
|
|
|
{
|
|
|
|
foreach ($this->fields as $field) {
|
|
|
|
if ($field->isMultipart()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2011-02-01 09:59:18 +00:00
|
|
|
* Returns true if the field exists (implements the \ArrayAccess interface).
|
2011-01-29 21:39:36 +00:00
|
|
|
*
|
2011-02-01 09:59:18 +00:00
|
|
|
* @param string $key The key of the field
|
2011-01-29 21:39:36 +00:00
|
|
|
*
|
|
|
|
* @return Boolean true if the widget exists, false otherwise
|
|
|
|
*/
|
|
|
|
public function offsetExists($key)
|
|
|
|
{
|
|
|
|
return $this->has($key);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the form field associated with the name (implements the \ArrayAccess interface).
|
|
|
|
*
|
|
|
|
* @param string $key The offset of the value to get
|
|
|
|
*
|
|
|
|
* @return Field A form field instance
|
|
|
|
*/
|
|
|
|
public function offsetGet($key)
|
|
|
|
{
|
|
|
|
return $this->get($key);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Throws an exception saying that values cannot be set (implements the \ArrayAccess interface).
|
|
|
|
*
|
|
|
|
* @param string $offset (ignored)
|
|
|
|
* @param string $value (ignored)
|
|
|
|
*
|
|
|
|
* @throws \LogicException
|
|
|
|
*/
|
|
|
|
public function offsetSet($key, $field)
|
|
|
|
{
|
|
|
|
throw new \LogicException('Use the method add() to add fields');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Throws an exception saying that values cannot be unset (implements the \ArrayAccess interface).
|
|
|
|
*
|
|
|
|
* @param string $key
|
|
|
|
*
|
|
|
|
* @throws \LogicException
|
|
|
|
*/
|
|
|
|
public function offsetUnset($key)
|
|
|
|
{
|
|
|
|
return $this->remove($key);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the iterator for this group.
|
|
|
|
*
|
|
|
|
* @return \ArrayIterator
|
|
|
|
*/
|
|
|
|
public function getIterator()
|
|
|
|
{
|
|
|
|
return new \ArrayIterator($this->fields);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the number of form fields (implements the \Countable interface).
|
|
|
|
*
|
|
|
|
* @return integer The number of embedded form fields
|
|
|
|
*/
|
|
|
|
public function count()
|
|
|
|
{
|
|
|
|
return count($this->fields);
|
|
|
|
}
|
|
|
|
|
2010-06-24 10:24:08 +01:00
|
|
|
/**
|
2011-01-19 12:43:24 +00:00
|
|
|
* Returns a factory for automatically creating fields based on metadata
|
|
|
|
* available for a form's object
|
2010-06-24 10:24:08 +01:00
|
|
|
*
|
2011-01-19 12:43:24 +00:00
|
|
|
* @return FieldFactoryInterface The factory
|
2010-06-24 10:24:08 +01:00
|
|
|
*/
|
2011-01-19 12:43:24 +00:00
|
|
|
public function getFieldFactory()
|
2010-06-24 10:24:08 +01:00
|
|
|
{
|
2011-01-19 12:43:24 +00:00
|
|
|
return $this->getOption('field_factory');
|
2010-06-24 10:24:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2011-01-19 12:43:24 +00:00
|
|
|
* Returns the validator used by the form
|
2010-06-24 10:24:08 +01:00
|
|
|
*
|
2011-01-19 12:43:24 +00:00
|
|
|
* @return ValidatorInterface The validator instance
|
|
|
|
*/
|
|
|
|
public function getValidator()
|
|
|
|
{
|
2011-02-01 17:07:59 +00:00
|
|
|
return $this->getOption('validator');
|
2011-01-19 12:43:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the validation groups validated by the form
|
|
|
|
*
|
|
|
|
* @return array A list of validation groups or null
|
2010-06-24 10:24:08 +01:00
|
|
|
*/
|
|
|
|
public function getValidationGroups()
|
|
|
|
{
|
2011-01-19 12:43:24 +00:00
|
|
|
return $this->getOption('validation_groups');
|
2010-06-24 10:24:08 +01:00
|
|
|
}
|
|
|
|
|
2011-01-05 11:36:04 +00:00
|
|
|
/**
|
2011-01-19 12:43:24 +00:00
|
|
|
* Returns the name used for the CSRF protection field
|
2011-01-05 11:36:04 +00:00
|
|
|
*
|
2011-01-19 12:43:24 +00:00
|
|
|
* @return string The field name
|
2011-01-05 11:36:04 +00:00
|
|
|
*/
|
2011-01-19 12:43:24 +00:00
|
|
|
public function getCsrfFieldName()
|
2011-01-05 11:36:04 +00:00
|
|
|
{
|
2011-01-19 12:43:24 +00:00
|
|
|
return $this->getOption('csrf_field_name');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2011-01-25 08:56:37 +00:00
|
|
|
* Returns the provider used for generating and validating CSRF tokens
|
2011-01-19 12:43:24 +00:00
|
|
|
*
|
2011-01-25 08:56:37 +00:00
|
|
|
* @return CsrfProviderInterface The provider instance
|
2011-01-19 12:43:24 +00:00
|
|
|
*/
|
2011-01-25 08:56:37 +00:00
|
|
|
public function getCsrfProvider()
|
2011-01-19 12:43:24 +00:00
|
|
|
{
|
2011-01-25 08:56:37 +00:00
|
|
|
return $this->getOption('csrf_provider');
|
2011-01-05 11:36:04 +00:00
|
|
|
}
|
|
|
|
|
2010-06-24 10:24:08 +01:00
|
|
|
/**
|
2011-02-01 09:59:18 +00:00
|
|
|
* Binds a request to the form
|
2010-06-24 10:24:08 +01:00
|
|
|
*
|
2011-02-01 09:59:18 +00:00
|
|
|
* If the request was a POST request, the data is submitted to the form,
|
|
|
|
* transformed and written into the form data (an object or an array).
|
|
|
|
* You can set the form data by passing it in the second parameter
|
|
|
|
* of this method or by passing it in the "data" option of the form's
|
|
|
|
* constructor.
|
|
|
|
*
|
|
|
|
* @param Request $request The request to bind to the form
|
|
|
|
* @param array|object $data The data from which to read default values
|
|
|
|
* and where to write submitted values
|
2011-01-28 22:09:22 +00:00
|
|
|
*/
|
2011-02-01 09:59:18 +00:00
|
|
|
public function bind(Request $request, $data = null)
|
2011-01-28 22:09:22 +00:00
|
|
|
{
|
2011-02-01 09:59:18 +00:00
|
|
|
$this->bound = true;
|
|
|
|
|
|
|
|
// Store object from which to read the default values and where to
|
|
|
|
// write the submitted values
|
|
|
|
if (null !== $data) {
|
|
|
|
$this->setData($data);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store the submitted data in case of a post request
|
|
|
|
if ('POST' == $request->getMethod()) {
|
|
|
|
$values = $request->request->get($this->getName(), array());
|
|
|
|
$files = $request->files->get($this->getName(), array());
|
2011-01-28 22:09:22 +00:00
|
|
|
|
2011-02-01 09:59:18 +00:00
|
|
|
$this->submit(self::deepArrayUnion($values, $files));
|
|
|
|
|
2011-02-02 08:41:57 +00:00
|
|
|
$this->validate();
|
2011-02-01 09:59:18 +00:00
|
|
|
}
|
2011-01-28 22:09:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2011-02-02 08:41:57 +00:00
|
|
|
* @var Boolean whether a request and an object were bound to the form
|
2010-06-24 10:24:08 +01:00
|
|
|
*/
|
2011-02-02 08:41:57 +00:00
|
|
|
public function isBound()
|
2010-06-24 10:24:08 +01:00
|
|
|
{
|
2011-02-02 08:41:57 +00:00
|
|
|
return $this->bound;
|
2011-02-01 09:59:18 +00:00
|
|
|
}
|
2010-06-24 10:24:08 +01:00
|
|
|
|
2011-02-01 09:59:18 +00:00
|
|
|
/**
|
2011-02-02 08:41:57 +00:00
|
|
|
* Validates the form and its domain object
|
|
|
|
*
|
|
|
|
* @throws FormException If the option "validator" was not set
|
2011-02-01 09:59:18 +00:00
|
|
|
*/
|
2011-02-02 08:41:57 +00:00
|
|
|
public function validate()
|
2011-02-01 09:59:18 +00:00
|
|
|
{
|
2011-02-02 08:41:57 +00:00
|
|
|
if (null === $this->getOption('validator')) {
|
|
|
|
throw new MissingOptionsException('The option "validator" is required for validating', array('validator'));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate the submitted data
|
|
|
|
if ($violations = $this->getOption('validator')->validate($this, $this->getOption('validation_groups'))) {
|
|
|
|
// TODO: test me
|
|
|
|
foreach ($violations as $violation) {
|
|
|
|
$propertyPath = new PropertyPath($violation->getPropertyPath());
|
|
|
|
$iterator = $propertyPath->getIterator();
|
|
|
|
|
|
|
|
if ($iterator->current() == 'data') {
|
|
|
|
$type = self::DATA_ERROR;
|
|
|
|
$iterator->next(); // point at the first data element
|
|
|
|
} else {
|
|
|
|
$type = self::FIELD_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->addError(new FieldError($violation->getMessageTemplate(), $violation->getMessageParameters()), $iterator, $type);
|
|
|
|
}
|
|
|
|
}
|
2011-01-28 22:09:22 +00:00
|
|
|
}
|
2010-09-09 14:23:28 +01:00
|
|
|
|
2010-06-24 10:24:08 +01:00
|
|
|
/**
|
|
|
|
* @return true if this form is CSRF protected
|
|
|
|
*/
|
|
|
|
public function isCsrfProtected()
|
|
|
|
{
|
2011-01-19 12:43:24 +00:00
|
|
|
return $this->has($this->getOption('csrf_field_name'));
|
2010-06-24 10:24:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns whether the CSRF token is valid
|
|
|
|
*
|
2011-01-21 01:39:28 +00:00
|
|
|
* @return Boolean
|
2010-06-24 10:24:08 +01:00
|
|
|
*/
|
|
|
|
public function isCsrfTokenValid()
|
|
|
|
{
|
|
|
|
if (!$this->isCsrfProtected()) {
|
|
|
|
return true;
|
|
|
|
} else {
|
2011-01-25 08:56:37 +00:00
|
|
|
$token = $this->get($this->getOption('csrf_field_name'))->getDisplayedData();
|
2011-01-19 12:43:24 +00:00
|
|
|
|
2011-01-25 08:56:37 +00:00
|
|
|
return $this->getOption('csrf_provider')->isCsrfTokenValid(get_class($this), $token);
|
2010-06-24 10:24:08 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns whether the maximum POST size was reached in this request.
|
|
|
|
*
|
2011-01-21 01:39:28 +00:00
|
|
|
* @return Boolean
|
2010-06-24 10:24:08 +01:00
|
|
|
*/
|
|
|
|
public function isPostMaxSizeReached()
|
|
|
|
{
|
2011-01-28 22:13:10 +00:00
|
|
|
if ($this->isRoot() && isset($_SERVER['CONTENT_LENGTH'])) {
|
2010-06-24 10:24:08 +01:00
|
|
|
$length = (int) $_SERVER['CONTENT_LENGTH'];
|
|
|
|
$max = trim(ini_get('post_max_size'));
|
|
|
|
|
|
|
|
switch (strtolower(substr($max, -1))) {
|
|
|
|
// The 'G' modifier is available since PHP 5.1.0
|
|
|
|
case 'g':
|
|
|
|
$max *= 1024;
|
|
|
|
case 'm':
|
|
|
|
$max *= 1024;
|
|
|
|
case 'k':
|
|
|
|
$max *= 1024;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $length > $max;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-02-01 09:59:18 +00:00
|
|
|
/**
|
|
|
|
* Sets the class that object bound to this form must be instances of
|
|
|
|
*
|
|
|
|
* @param string A fully qualified class name
|
|
|
|
*/
|
|
|
|
protected function setDataClass($class)
|
|
|
|
{
|
|
|
|
$this->dataClass = $class;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the class that object must have that are bound to this form
|
|
|
|
*
|
|
|
|
* @return string A fully qualified class name
|
|
|
|
*/
|
|
|
|
public function getDataClass()
|
|
|
|
{
|
|
|
|
return $this->dataClass;
|
|
|
|
}
|
|
|
|
|
2011-02-01 12:45:27 +00:00
|
|
|
/**
|
|
|
|
* Returns the context used when creating this form
|
|
|
|
*
|
|
|
|
* @return FormContext The context instance
|
|
|
|
*/
|
|
|
|
public function getContext()
|
|
|
|
{
|
|
|
|
return $this->getOption('context');
|
|
|
|
}
|
|
|
|
|
2010-06-24 10:24:08 +01:00
|
|
|
/**
|
|
|
|
* Merges two arrays without reindexing numeric keys.
|
|
|
|
*
|
|
|
|
* @param array $array1 An array to merge
|
|
|
|
* @param array $array2 An array to merge
|
|
|
|
*
|
|
|
|
* @return array The merged array
|
|
|
|
*/
|
|
|
|
static protected function deepArrayUnion($array1, $array2)
|
|
|
|
{
|
|
|
|
foreach ($array2 as $key => $value) {
|
|
|
|
if (is_array($value) && isset($array1[$key]) && is_array($array1[$key])) {
|
|
|
|
$array1[$key] = self::deepArrayUnion($array1[$key], $value);
|
|
|
|
} else {
|
|
|
|
$array1[$key] = $value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $array1;
|
|
|
|
}
|
2010-06-24 09:40:05 +01:00
|
|
|
}
|