Merge branch 'form'

* form: (291 commits)
  [FrameworkBundle] updated method call
  [Form] Removing excess option in the TimezoneType
  [FrameworkBundle] Adding check for invalid form type for better exception message
  [TwigBundle] Removing dbug text in form template
  [Form] Removed obsolete code in TextType
  [Form] fixed translations escaping
  [Form] Adding a check that the choice_list option on the ChoiceType implements the ChoiceListInterface.
  [Form] added support for groups in form validation (when using array data)
  [Form] fixed error bubbling for choices when expanded is true
  [Form] added a unit test
  [Form] Removed obsolete view variables
  [Form] Renamed ChoiceUtil to FormUtil and gave its methods more general names
  [Form] Changed separator for Twig blocks from double underscore to single underscore to match the PHP template separator
  [Form] Removed StripTagsListenerTest
  [Form] Removed StripTagsListener. Its implementation is insufficient and needs to be replaced by a better one.
  [Form] added a way to specify the form constraint when building the form (useful if you work with arrays instead of objects)
  [Form] Added test for 'email' type and fixed a few bugs
  [Form] Removed obsolete constraints from validation.xml
  Revert "[Form] removed validation.xml file (not used anymore)"
  Added html5 email input to the forms
  ...
This commit is contained in:
Fabien Potencier 2011-04-22 09:36:52 +02:00
commit 0069a70e42
292 changed files with 15460 additions and 10718 deletions

View File

@ -0,0 +1,275 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\Form\ChoiceList;
use Symfony\Component\Form\Util\PropertyPath;
use Symfony\Component\Form\Exception\FormException;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\NoResultException;
class EntityChoiceList extends ArrayChoiceList
{
/**
* @var Doctrine\ORM\EntityManager
*/
private $em;
/**
* @var Doctrine\ORM\Mapping\ClassMetadata
*/
private $class;
/**
* The entities from which the user can choose
*
* This array is either indexed by ID (if the ID is a single field)
* or by key in the choices array (if the ID consists of multiple fields)
*
* This property is initialized by initializeChoices(). It should only
* be accessed through getEntity() and getEntities().
*
* @var Collection
*/
private $entities = array();
/**
* Contains the query builder that builds the query for fetching the
* entities
*
* This property should only be accessed through queryBuilder.
*
* @var Doctrine\ORM\QueryBuilder
*/
private $queryBuilder;
/**
* The fields of which the identifier of the underlying class consists
*
* This property should only be accessed through identifier.
*
* @var array
*/
private $identifier = array();
/**
* A cache for \ReflectionProperty instances for the underlying class
*
* This property should only be accessed through getReflProperty().
*
* @var array
*/
private $reflProperties = array();
/**
* A cache for the UnitOfWork instance of Doctrine
*
* @var Doctrine\ORM\UnitOfWork
*/
private $unitOfWork;
private $propertyPath;
public function __construct(EntityManager $em, $class, $property = null, $queryBuilder = null, $choices = array())
{
// If a query builder was passed, it must be a closure or QueryBuilder
// instance
if (!(null === $queryBuilder || $queryBuilder instanceof QueryBuilder || $queryBuilder instanceof \Closure)) {
throw new UnexpectedTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder or \Closure');
}
if ($queryBuilder instanceof \Closure) {
$queryBuilder = $queryBuilder($em->getRepository($class));
if (!$queryBuilder instanceof QueryBuilder) {
throw new UnexpectedTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder');
}
}
$this->em = $em;
$this->class = $class;
$this->queryBuilder = $queryBuilder;
$this->unitOfWork = $em->getUnitOfWork();
$this->identifier = $em->getClassMetadata($class)->getIdentifierFieldNames();
// The propery option defines, which property (path) is used for
// displaying entities as strings
if ($property) {
$this->propertyPath = new PropertyPath($property);
}
parent::__construct($choices);
}
/**
* Initializes the choices and returns them
*
* The choices are generated from the entities. If the entities have a
* composite identifier, the choices are indexed using ascending integers.
* Otherwise the identifiers are used as indices.
*
* If the entities were passed in the "choices" option, this method
* does not have any significant overhead. Otherwise, if a query builder
* was passed in the "query_builder" option, this builder is now used
* to construct a query which is executed. In the last case, all entities
* for the underlying class are fetched from the repository.
*
* If the option "property" was passed, the property path in that option
* is used as option values. Otherwise this method tries to convert
* objects to strings using __toString().
*
* @return array An array of choices
*/
protected function load()
{
parent::load();
if ($this->choices) {
$entities = $this->choices;
} else if ($qb = $this->queryBuilder) {
$entities = $qb->getQuery()->execute();
} else {
$entities = $this->em->getRepository($this->class)->findAll();
}
$propertyPath = null;
$this->choices = array();
$this->entities = array();
foreach ($entities as $key => $entity) {
if ($this->propertyPath) {
// If the property option was given, use it
$value = $this->propertyPath->getValue($entity);
} else {
// Otherwise expect a __toString() method in the entity
$value = (string)$entity;
}
if (count($this->identifier) > 1) {
// When the identifier consists of multiple field, use
// naturally ordered keys to refer to the choices
$this->choices[$key] = $value;
$this->entities[$key] = $entity;
} else {
// When the identifier is a single field, index choices by
// entity ID for performance reasons
$id = current($this->getIdentifierValues($entity));
$this->choices[$id] = $value;
$this->entities[$id] = $entity;
}
}
}
public function getIdentifier()
{
return $this->identifier;
}
/**
* Returns the according entities for the choices
*
* If the choices were not initialized, they are initialized now. This
* is an expensive operation, except if the entities were passed in the
* "choices" option.
*
* @return array An array of entities
*/
public function getEntities()
{
if (!$this->loaded) {
$this->load();
}
return $this->entities;
}
/**
* Returns the entity for the given key
*
* If the underlying entities have composite identifiers, the choices
* are intialized. The key is expected to be the index in the choices
* array in this case.
*
* If they have single identifiers, they are either fetched from the
* internal entity cache (if filled) or loaded from the database.
*
* @param string $key The choice key (for entities with composite
* identifiers) or entity ID (for entities with single
* identifiers)
* @return object The matching entity
*/
public function getEntity($key)
{
if (!$this->loaded) {
$this->load();
}
try {
if (count($this->identifier) > 1) {
// $key is a collection index
$entities = $this->getEntities();
return isset($entities[$key]) ? $entities[$key] : null;
} else if ($this->entities) {
return isset($this->entities[$key]) ? $this->entities[$key] : null;
} else if ($qb = $this->queryBuilder) {
// should we clone the builder?
$alias = $qb->getRootAlias();
$where = $qb->expr()->eq($alias.'.'.current($this->identifier), $key);
return $qb->andWhere($where)->getQuery()->getSingleResult();
}
return $this->em->find($this->class, $key);
} catch (NoResultException $e) {
return null;
}
}
/**
* Returns the \ReflectionProperty instance for a property of the
* underlying class
*
* @param string $property The name of the property
* @return \ReflectionProperty The reflection instsance
*/
private function getReflProperty($property)
{
if (!isset($this->reflProperties[$property])) {
$this->reflProperties[$property] = new \ReflectionProperty($this->class, $property);
$this->reflProperties[$property]->setAccessible(true);
}
return $this->reflProperties[$property];
}
/**
* Returns the values of the identifier fields of an entity
*
* Doctrine must know about this entity, that is, the entity must already
* be persisted or added to the identity map before. Otherwise an
* exception is thrown.
*
* @param object $entity The entity for which to get the identifier
* @throws FormException If the entity does not exist in Doctrine's
* identity map
*/
public function getIdentifierValues($entity)
{
if (!$this->unitOfWork->isInIdentityMap($entity)) {
throw new FormException('Entities passed to the choice field must be managed');
}
return $this->unitOfWork->getEntityIdentifier($entity);
}
}

View File

@ -0,0 +1,104 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\Form\DataTransformer;
use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Form\DataTransformer\TransformationFailedException;
use Symfony\Component\Form\DataTransformer\DataTransformerInterface;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
class EntitiesToArrayTransformer implements DataTransformerInterface
{
private $choiceList;
public function __construct(EntityChoiceList $choiceList)
{
$this->choiceList = $choiceList;
}
/**
* Transforms entities into choice keys
*
* @param Collection|object A collection of entities, a single entity or
* NULL
* @return mixed An array of choice keys, a single key or
* NULL
*/
public function transform($collection)
{
if (null === $collection) {
return array();
}
if (!($collection instanceof Collection)) {
throw new UnexpectedTypeException($collection, 'Doctrine\Common\Collection\Collection');
}
$array = array();
if (count($this->choiceList->getIdentifier()) > 1) {
// load all choices
$availableEntities = $this->choiceList->getEntities();
foreach ($collection as $entity) {
// identify choices by their collection key
$key = array_search($entity, $availableEntities);
$array[] = $key;
}
} else {
foreach ($collection as $entity) {
$array[] = current($this->choiceList->getIdentifierValues($entity));
}
}
return $array;
}
/**
* Transforms choice keys into entities
*
* @param mixed $keys An array of keys, a single key or NULL
* @return Collection|object A collection of entities, a single entity
* or NULL
*/
public function reverseTransform($keys)
{
$collection = new ArrayCollection();
if ('' === $keys || null === $keys) {
return $collection;
}
if (!is_array($keys)) {
throw new UnexpectedTypeException($keys, 'array');
}
$notFound = array();
// optimize this into a SELECT WHERE IN query
foreach ($keys as $key) {
if ($entity = $this->choiceList->getEntity($key)) {
$collection->add($entity);
} else {
$notFound[] = $key;
}
}
if (count($notFound) > 0) {
throw new TransformationFailedException(sprintf('The entities with keys "%s" could not be found', implode('", "', $notFound)));
}
return $collection;
}
}

View File

@ -0,0 +1,79 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\Form\DataTransformer;
use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Form\DataTransformer\DataTransformerInterface;
use Symfony\Component\Form\DataTransformer\TransformationFailedException;
class EntityToIdTransformer implements DataTransformerInterface
{
private $choiceList;
public function __construct(EntityChoiceList $choiceList)
{
$this->choiceList = $choiceList;
}
/**
* Transforms entities into choice keys
*
* @param Collection|object A collection of entities, a single entity or
* NULL
* @return mixed An array of choice keys, a single key or
* NULL
*/
public function transform($entity)
{
if (null === $entity || '' === $entity) {
return '';
}
if (!is_object($entity)) {
throw new UnexpectedTypeException($entity, 'object');
}
if (count($this->choiceList->getIdentifier()) > 1) {
// load all choices
$availableEntities = $this->choiceList->getEntities();
return array_search($entity, $availableEntities);
}
return current($this->choiceList->getIdentifierValues($entity));
}
/**
* Transforms choice keys into entities
*
* @param mixed $key An array of keys, a single key or NULL
* @return Collection|object A collection of entities, a single entity
* or NULL
*/
public function reverseTransform($key)
{
if ('' === $key || null === $key) {
return null;
}
if (!is_numeric($key)) {
throw new UnexpectedTypeException($key, 'numeric');
}
if (!($entity = $this->choiceList->getEntity($key))) {
throw new TransformationFailedException('The entity with key "%s" could not be found', $key);
}
return $entity;
}
}

View File

@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\Form;
use Symfony\Component\Form\Type\Loader\TypeLoaderInterface;
use Doctrine\ORM\EntityManager;
class DoctrineTypeLoader implements TypeLoaderInterface
{
private $types;
public function __construct(EntityManager $em)
{
$this->types['entity'] = new EntityType($em);
}
public function getType($name)
{
return $this->types[$name];
}
public function hasType($name)
{
return isset($this->types[$name]);
}
}

View File

@ -0,0 +1,82 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\Form;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList;
use Symfony\Bridge\Doctrine\Form\EventListener\MergeCollectionListener;
use Symfony\Bridge\Doctrine\Form\DataTransformer\EntitiesToArrayTransformer;
use Symfony\Bridge\Doctrine\Form\DataTransformer\EntityToIdTransformer;
use Symfony\Component\Form\Type\AbstractType;
use Doctrine\ORM\EntityManager;
class EntityType extends AbstractType
{
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function buildForm(FormBuilder $builder, array $options)
{
if ($options['multiple']) {
$builder->addEventSubscriber(new MergeCollectionListener())
->prependClientTransformer(new EntitiesToArrayTransformer($options['choice_list']));
} else {
$builder->prependClientTransformer(new EntityToIdTransformer($options['choice_list']));
}
}
public function getDefaultOptions(array $options)
{
$defaultOptions = array(
'template' => 'choice',
'multiple' => false,
'expanded' => false,
'em' => $this->em,
'class' => null,
'property' => null,
'query_builder' => null,
'choices' => array(),
'preferred_choices' => array(),
'multiple' => false,
'expanded' => false,
);
$options = array_replace($defaultOptions, $options);
if (!isset($options['choice_list'])) {
$defaultOptions['choice_list'] = new EntityChoiceList(
$options['em'],
$options['class'],
$options['property'],
$options['query_builder'],
$options['choices']
);
}
return $defaultOptions;
}
public function getParent(array $options)
{
return 'choice';
}
public function getName()
{
return 'entity';
}
}

View File

@ -0,0 +1,59 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Doctrine\Form\EventListener;
use Symfony\Component\Form\Events;
use Symfony\Component\Form\Event\FilterDataEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Merge changes from the request to a Doctrine\Common\Collections\Collection instance.
*
* This works with ORM, MongoDB and CouchDB instances of the collection interface.
*
* @see Doctrine\Common\Collections\Collection
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
*/
class MergeCollectionListener implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return Events::onBindNormData;
}
public function onBindNormData(FilterDataEvent $event)
{
$collection = $event->getForm()->getData();
$data = $event->getData();
if (!$collection) {
$collection = $data;
} else if (count($data) === 0) {
$collection->clear();
} else {
// merge $data into $collection
foreach ($collection as $entity) {
if (!$data->contains($entity)) {
$collection->removeElement($entity);
} else {
$data->removeElement($entity);
}
}
foreach ($data as $entity) {
$collection->add($entity);
}
}
$event->setData($collection);
}
}

View File

@ -9,16 +9,20 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\FieldFactory;
namespace Symfony\Bridge\Doctrine\Form\Type\Guesser;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Form\Type\Guesser\Guess;
use Symfony\Component\Form\Type\Guesser\TypeGuesserInterface;
use Symfony\Component\Form\Type\Guesser\TypeGuess;
use Symfony\Component\Form\Type\Guesser\ValueGuess;
/**
* Guesses form fields from the metadata of Doctrine 2
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
class EntityFieldFactoryGuesser implements FieldFactoryGuesserInterface
class EntityTypeGuesser implements TypeGuesserInterface
{
/**
* The Doctrine 2 entity manager
@ -49,7 +53,7 @@ class EntityFieldFactoryGuesser implements FieldFactoryGuesserInterface
/**
* @inheritDoc
*/
public function guessClass($class, $property)
public function guessType($class, $property)
{
if ($this->isMappedClass($class)) {
$metadata = $this->em->getClassMetadata($class);
@ -58,86 +62,86 @@ class EntityFieldFactoryGuesser implements FieldFactoryGuesserInterface
$multiple = $metadata->isCollectionValuedAssociation($property);
$mapping = $metadata->getAssociationMapping($property);
return new FieldFactoryClassGuess(
'Symfony\Component\Form\EntityChoiceField',
return new TypeGuess(
'entity',
array(
'em' => $this->em,
'class' => $mapping['targetEntity'],
'multiple' => $multiple,
),
FieldFactoryGuess::HIGH_CONFIDENCE
Guess::HIGH_CONFIDENCE
);
} else {
switch ($metadata->getTypeOfField($property))
{
// case 'array':
// return new FieldFactoryClassGuess(
// 'Symfony\Component\Form\CollectionField',
// return new TypeGuess(
// 'Collection',
// array(),
// FieldFactoryGuess::HIGH_CONFIDENCE
// Guess::HIGH_CONFIDENCE
// );
case 'boolean':
return new FieldFactoryClassGuess(
'Symfony\Component\Form\CheckboxField',
return new TypeGuess(
'checkbox',
array(),
FieldFactoryGuess::HIGH_CONFIDENCE
Guess::HIGH_CONFIDENCE
);
case 'datetime':
case 'vardatetime':
case 'datetimetz':
return new FieldFactoryClassGuess(
'Symfony\Component\Form\DateTimeField',
return new TypeGuess(
'datetime',
array(),
FieldFactoryGuess::HIGH_CONFIDENCE
Guess::HIGH_CONFIDENCE
);
case 'date':
return new FieldFactoryClassGuess(
'Symfony\Component\Form\DateField',
return new TypeGuess(
'date',
array(),
FieldFactoryGuess::HIGH_CONFIDENCE
Guess::HIGH_CONFIDENCE
);
case 'decimal':
case 'float':
return new FieldFactoryClassGuess(
'Symfony\Component\Form\NumberField',
return new TypeGuess(
'number',
array(),
FieldFactoryGuess::MEDIUM_CONFIDENCE
Guess::MEDIUM_CONFIDENCE
);
case 'integer':
case 'bigint':
case 'smallint':
return new FieldFactoryClassGuess(
'Symfony\Component\Form\IntegerField',
return new TypeGuess(
'integer',
array(),
FieldFactoryGuess::MEDIUM_CONFIDENCE
Guess::MEDIUM_CONFIDENCE
);
case 'string':
return new FieldFactoryClassGuess(
'Symfony\Component\Form\TextField',
return new TypeGuess(
'text',
array(),
FieldFactoryGuess::MEDIUM_CONFIDENCE
Guess::MEDIUM_CONFIDENCE
);
case 'text':
return new FieldFactoryClassGuess(
'Symfony\Component\Form\TextareaField',
return new TypeGuess(
'textarea',
array(),
FieldFactoryGuess::MEDIUM_CONFIDENCE
Guess::MEDIUM_CONFIDENCE
);
case 'time':
return new FieldFactoryClassGuess(
'Symfony\Component\Form\TimeField',
return new TypeGuess(
'time',
array(),
FieldFactoryGuess::HIGH_CONFIDENCE
Guess::HIGH_CONFIDENCE
);
// case 'object': ???
}
}
}
return new FieldFactoryClassGuess(
'Symfony\Component\Form\TextField',
return new TypeGuess(
'text',
array(),
FieldFactoryGuess::LOW_CONFIDENCE
Guess::LOW_CONFIDENCE
);
}
@ -151,15 +155,15 @@ class EntityFieldFactoryGuesser implements FieldFactoryGuesserInterface
if ($metadata->hasField($property)) {
if (!$metadata->isNullable($property)) {
return new FieldFactoryGuess(
return new ValueGuess(
true,
FieldFactoryGuess::HIGH_CONFIDENCE
Guess::HIGH_CONFIDENCE
);
}
return new FieldFactoryGuess(
return new ValueGuess(
false,
FieldFactoryGuess::MEDIUM_CONFIDENCE
Guess::MEDIUM_CONFIDENCE
);
}
}
@ -178,9 +182,9 @@ class EntityFieldFactoryGuesser implements FieldFactoryGuesserInterface
if (isset($mapping['length'])) {
return new FieldFactoryGuess(
return new ValueGuess(
$mapping['length'],
FieldFactoryGuess::HIGH_CONFIDENCE
Guess::HIGH_CONFIDENCE
);
}
}

View File

@ -0,0 +1,244 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Twig\Extension;
use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Exception\FormException;
/**
* FormExtension extends Twig with form capabilities.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
class FormExtension extends \Twig_Extension
{
protected $resources;
protected $templates;
protected $environment;
protected $themes;
public function __construct(array $resources = array())
{
$this->themes = new \SplObjectStorage();
$this->resources = $resources;
}
/**
* {@inheritdoc}
*/
public function initRuntime(\Twig_Environment $environment)
{
$this->environment = $environment;
}
/**
* Sets a theme for a given view.
*
* @param FormView $view A FormView instance
* @param array $resources An array of resources
*/
public function setTheme(FormView $view, array $resources)
{
$this->themes->attach($view, $resources);
}
/**
* Returns the token parser instance to add to the existing list.
*
* @return array An array of Twig_TokenParser instances
*/
public function getTokenParsers()
{
return array(
// {% form_theme form "SomeBungle::widgets.twig" %}
new FormThemeTokenParser(),
);
}
public function getFunctions()
{
return array(
'form_enctype' => new \Twig_Function_Method($this, 'renderEnctype', array('is_safe' => array('html'))),
'form_widget' => new \Twig_Function_Method($this, 'renderWidget', array('is_safe' => array('html'))),
'form_errors' => new \Twig_Function_Method($this, 'renderErrors', array('is_safe' => array('html'))),
'form_label' => new \Twig_Function_Method($this, 'renderLabel', array('is_safe' => array('html'))),
'form_row' => new \Twig_Function_Method($this, 'renderRow', array('is_safe' => array('html'))),
'form_rest' => new \Twig_Function_Method($this, 'renderRest', array('is_safe' => array('html'))),
);
}
/**
* Renders the HTML enctype in the form tag, if necessary
*
* Example usage in Twig templates:
*
* <form action="..." method="post" {{ form_enctype(form) }}>
*
* @param FormView $view The view for which to render the encoding type
*/
public function renderEnctype(FormView $view)
{
return $this->render($view, 'enctype');
}
/**
* Renders a row for the view.
*
* @param FormView $view The view to render as a row
*/
public function renderRow(FormView $view, array $variables = array())
{
return $this->render($view, 'row', $variables);
}
public function renderRest(FormView $view, array $variables = array())
{
return $this->render($view, 'rest', $variables);
}
/**
* Renders the HTML for a given view
*
* Example usage in Twig:
*
* {{ form_widget(view) }}
*
* You can pass attributes element during the call:
*
* {{ form_widget(view, {'class': 'foo'}) }}
*
* Some fields also accept additional variables as parameters:
*
* {{ form_widget(view, {}, {'separator': '+++++'}) }}
*
* @param FormView $view The view to render
* @param array $attributes HTML attributes passed to the template
* @param array $parameters Additional variables passed to the template
* @param array|string $resources A resource or array of resources
*/
public function renderWidget(FormView $view, array $variables = array(), $resources = null)
{
if (null !== $resources && !is_array($resources)) {
$resources = array($resources);
}
return $this->render($view, 'widget', $variables, $resources);
}
/**
* Renders the errors of the given view
*
* @param FormView $view The view to render the errors for
* @param array $params Additional variables passed to the template
*/
public function renderErrors(FormView $view)
{
return $this->render($view, 'errors');
}
/**
* Renders the label of the given view
*
* @param FormView $view The view to render the label for
*/
public function renderLabel(FormView $view, $label = null)
{
return $this->render($view, 'label', null === $label ? array() : array('label' => $label));
}
protected function render(FormView $view, $section, array $variables = array(), array $resources = null)
{
$templates = $this->getTemplates($view, $resources);
$blocks = $view->get('types');
foreach ($blocks as &$block) {
$block = $block.'_'.$section;
if (isset($templates[$block])) {
if ('widget' === $section || 'row' === $section) {
$view->setRendered(true);
}
return $templates[$block]->renderBlock($block, array_merge($view->all(), $variables));
}
}
throw new FormException(sprintf('Unable to render form as none of the following blocks exist: "%s".', implode('", "', $blocks)));
}
protected function getTemplate(FormView $view, $name, array $resources = null)
{
$templates = $this->getTemplates($view, $resources);
return $templates[$name];
}
protected function getTemplates(FormView $view, array $resources = null)
{
// templates are looked for in the following resources:
// * resources provided directly into the function call
// * resources from the themes (and its parents)
// * default resources
// defaults
$all = $this->resources;
// themes
$parent = $view;
do {
if (isset($this->themes[$parent])) {
$all = array_merge($all, $this->themes[$parent]);
}
} while ($parent = $parent->getParent());
// local
$all = array_merge($all, null !== $resources ? (array) $resources : array());
$templates = array();
foreach ($all as $resource) {
if (!$resource instanceof \Twig_Template) {
$resource = $this->environment->loadTemplate($resource);
}
$blocks = array();
foreach ($this->getBlockNames($resource) as $name) {
$blocks[$name] = $resource;
}
$templates = array_replace($templates, $blocks);
}
return $templates;
}
protected function getBlockNames($resource)
{
$names = $resource->getBlockNames();
$parent = $resource;
while (false !== $parent = $parent->getParent(array())) {
$names = array_merge($names, $parent->getBlockNames());
}
return array_unique($names);
}
/**
* Returns the name of the extension.
*
* @return string The extension name
*/
public function getName()
{
return 'form';
}
}

View File

@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\TwigBundle\Node;
namespace Symfony\Bridge\Twig\Node;
/**
*

View File

@ -9,9 +9,9 @@
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\TwigBundle\TokenParser;
namespace Symfony\Bridge\Twig\TokenParser;
use Symfony\Bundle\TwigBundle\Node\FormThemeNode;
use Symfony\Bridge\Twig\Node\FormThemeNode;
/**
*

View File

@ -38,7 +38,7 @@
<parameter key="doctrine.orm.proxy_cache_warmer.class">Symfony\Bundle\DoctrineBundle\CacheWarmer\ProxyCacheWarmer</parameter>
<!-- form field factory guesser -->
<parameter key="form.field_factory.doctrine_guesser.class">Symfony\Component\Form\FieldFactory\EntityFieldFactoryGuesser</parameter>
<parameter key="form.guesser.doctrine.class">Symfony\Bridge\Doctrine\Form\Type\Guesser\EntityTypeGuesser</parameter>
</parameters>
<services>
@ -55,8 +55,13 @@
<argument type="service" id="service_container" />
</service>
<service id="form.field_factory.doctrine_guesser" class="%form.field_factory.doctrine_guesser.class%" public="false">
<tag name="form.field_factory.guesser" />
<service id="form.guesser.doctrine" class="%form.guesser.doctrine.class%" public="false">
<tag name="form.guesser" />
<argument type="service" id="doctrine.orm.entity_manager" />
</service>
<service id="form.type.entity" class="Symfony\Bridge\Doctrine\Form\EntityType">
<tag name="form.type" alias="entity" />
<argument type="service" id="doctrine.orm.entity_manager" />
</service>
</services>

View File

@ -110,6 +110,7 @@ class TraceableEventDispatcher extends ContainerAwareEventDispatcher implements
public function getNotCalledListeners()
{
$notCalled = array();
foreach (array_keys($this->getListeners()) as $name) {
foreach ($this->getListeners($name) as $listener) {
if (!isset($this->called[$name.'.'.get_class($listener)])) {

View File

@ -16,23 +16,25 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
/**
* Adds all services with the tag "form.field_factory_guesser" as argument
* to the "form.field_factory" service
* Adds all services with the tag "form.guesser" as constructor argument of the
* "form.factory" service
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
class AddFieldFactoryGuessersPass implements CompilerPassInterface
class AddFormGuessersPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('form.field_factory')) {
if (!$container->hasDefinition('form.factory')) {
return;
}
$guessers = array_map(function($id) {
return new Reference($id);
}, array_keys($container->findTaggedServiceIds('form.field_factory.guesser')));
$guessers = array();
$container->getDefinition('form.field_factory')->replaceArgument(0, $guessers);
foreach ($container->findTaggedServiceIds('form.guesser') as $serviceId => $tag) {
$guessers[] = new Reference($serviceId);
}
$container->getDefinition('form.factory')->replaceArgument(1, $guessers);
}
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Adds all services with the tag "form.type" as argument
* to the "form.type.loader" service
*
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
*/
class AddFormTypesPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('form.type.loader')) {
return;
}
// Builds an array with service IDs as keys and tag aliases as values
$types = array();
$tags = $container->findTaggedServiceIds('form.type');
foreach ($tags as $serviceId => $arguments) {
$alias = isset($arguments[0]['alias'])
? $arguments[0]['alias']
: $serviceId;
// Flip, because we want tag aliases (= type identifiers) as keys
$types[$alias] = $serviceId;
}
$container->getDefinition('form.type.loader')->replaceArgument(1, $types);
}
}

View File

@ -0,0 +1,42 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Form;
use Symfony\Component\Form\Type\Loader\TypeLoaderInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class ContainerAwareTypeLoader implements TypeLoaderInterface
{
private $container;
private $serviceIds;
public function __construct(ContainerInterface $container, array $serviceIds)
{
$this->container = $container;
$this->serviceIds = $serviceIds;
}
public function getType($identifier)
{
if (!isset($this->serviceIds[$identifier])) {
throw new \InvalidArgumentException(sprintf('The field type "%s" is not registered with the service container.', $identifier));
}
return $this->container->get($this->serviceIds[$identifier]);
}
public function hasType($identifier)
{
return isset($this->serviceIds[$identifier]);
}
}

View File

@ -12,7 +12,8 @@
namespace Symfony\Bundle\FrameworkBundle;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConstraintValidatorsPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddFieldFactoryGuessersPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddFormTypesPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddFormGuessersPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TemplatingPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RegisterKernelListenersPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RoutingResolverPass;
@ -77,7 +78,8 @@ class FrameworkBundle extends Bundle
$container->addCompilerPass(new RegisterKernelListenersPass());
$container->addCompilerPass(new TemplatingPass());
$container->addCompilerPass(new AddConstraintValidatorsPass());
$container->addCompilerPass(new AddFieldFactoryGuessersPass());
$container->addCompilerPass(new AddFormTypesPass());
$container->addCompilerPass(new AddFormGuessersPass());
$container->addCompilerPass(new AddClassesToCachePass());
$container->addCompilerPass(new AddClassesToAutoloadMapPass());
$container->addCompilerPass(new TranslatorPass());

View File

@ -5,47 +5,147 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="form.field_factory.class">Symfony\Component\Form\FieldFactory\FieldFactory</parameter>
<parameter key="form.field_factory.validator_guesser.class">Symfony\Component\Form\FieldFactory\ValidatorFieldFactoryGuesser</parameter>
<parameter key="form.factory.class">Symfony\Component\Form\FormFactory</parameter>
<parameter key="form.type.loader.class">Symfony\Bundle\FrameworkBundle\Form\ContainerAwareTypeLoader</parameter>
<parameter key="form.guesser.validator.class">Symfony\Component\Form\Type\Guesser\ValidatorTypeGuesser</parameter>
<parameter key="form.csrf_provider.class">Symfony\Component\Form\CsrfProvider\SessionCsrfProvider</parameter>
<parameter key="form.context.class">Symfony\Component\Form\FormContext</parameter>
<parameter key="form.csrf_protection.enabled">true</parameter>
<parameter key="form.csrf_protection.field_name">_token</parameter>
<parameter key="form.csrf_protection.secret">secret</parameter>
<parameter key="form.validation_groups">Default</parameter>
<parameter key="file.temporary_storage.class">Symfony\Component\HttpFoundation\File\SessionBasedTemporaryStorage</parameter>
<parameter key="file.temporary_storage.secret">abcdef</parameter>
<parameter key="file.temporary_storage.nesting_levels">3</parameter>
<parameter key="file.temporary_storage.directory"></parameter>
</parameters>
<services>
<!-- FieldFactory -->
<service id="form.field_factory" class="%form.field_factory.class%">
<!-- All services with tag "form.field_factory.guesser" are inserted here by AddFieldFactoryGuessersPass -->
<!-- FormFactory -->
<service id="form.factory" class="%form.factory.class%">
<argument type="service" id="form.type.loader" />
<!--
All services with tag "form.guesser" are inserted here by
AddFormGuessersPass
-->
<argument type="collection" />
</service>
<!-- ValidatorFieldFactoryGuesser -->
<service id="form.field_factory.validator_guesser" class="%form.field_factory.validator_guesser.class%" public="false">
<tag name="form.field_factory.guesser" />
<!-- ValidatorTypeGuesser -->
<service id="form.guesser.validator" class="%form.guesser.validator.class%" public="false">
<tag name="form.guesser" />
<argument type="service" id="validator.mapping.class_metadata_factory" />
</service>
<!-- CsrfProvider -->
<service id="form.csrf_provider" class="%form.csrf_provider.class%">
<argument type="service" id="session" />
<argument>%form.csrf_protection.secret%</argument>
</service>
<!-- FormContext -->
<service id="form.context" class="%form.context.class%">
<argument type="collection">
<argument key="validator" type="service" id="validator" />
<argument key="validation_groups">%form.validation_groups%</argument>
<argument key="field_factory" type="service" id="form.field_factory" />
<argument key="csrf_protection">%form.csrf_protection.enabled%</argument>
<argument key="csrf_field_name">%form.csrf_protection.field_name%</argument>
<argument key="csrf_provider" type="service" id="form.csrf_provider" />
</argument>
<!-- TemporaryStorage - where should we put this? -->
<service id="file.temporary_storage" class="%file.temporary_storage.class%">
<argument type="service" id="session" />
<argument>%file.temporary_storage.secret%</argument>
<argument>%file.temporary_storage.nesting_levels%</argument>
<argument>%file.temporary_storage.directory%</argument>
</service>
<!-- ContainerAwareTypeLoader -->
<service id="form.type.loader" class="%form.type.loader.class%">
<argument type="service" id="service_container" />
<!--
All services with tag "form.type" are inserted here by
AddFormTypesPass
-->
<argument type="collection" />
</service>
<!-- FieldTypes -->
<service id="form.type.field" class="Symfony\Component\Form\Type\FieldType">
<tag name="form.type" alias="field" />
<argument type="service" id="validator" />
</service>
<service id="form.type.form" class="Symfony\Component\Form\Type\FormType">
<tag name="form.type" alias="form" />
</service>
<service id="form.type.birthday" class="Symfony\Component\Form\Type\BirthdayType">
<tag name="form.type" alias="birthday" />
</service>
<service id="form.type.checkbox" class="Symfony\Component\Form\Type\CheckboxType">
<tag name="form.type" alias="checkbox" />
</service>
<service id="form.type.choice" class="Symfony\Component\Form\Type\ChoiceType">
<tag name="form.type" alias="choice" />
</service>
<service id="form.type.collection" class="Symfony\Component\Form\Type\CollectionType">
<tag name="form.type" alias="collection" />
</service>
<service id="form.type.country" class="Symfony\Component\Form\Type\CountryType">
<tag name="form.type" alias="country" />
</service>
<service id="form.type.csrf" class="Symfony\Component\Form\Type\CsrfType">
<tag name="form.type" alias="csrf" />
<argument type="service" id="form.csrf_provider" />
</service>
<service id="form.type.date" class="Symfony\Component\Form\Type\DateType">
<tag name="form.type" alias="date" />
</service>
<service id="form.type.datetime" class="Symfony\Component\Form\Type\DateTimeType">
<tag name="form.type" alias="datetime" />
</service>
<service id="form.type.email" class="Symfony\Component\Form\Type\EmailType">
<tag name="form.type" alias="email" />
</service>
<service id="form.type.file" class="Symfony\Component\Form\Type\FileType">
<tag name="form.type" alias="file" />
<argument type="service" id="file.temporary_storage" />
</service>
<service id="form.type.hidden" class="Symfony\Component\Form\Type\HiddenType">
<tag name="form.type" alias="hidden" />
</service>
<service id="form.type.integer" class="Symfony\Component\Form\Type\IntegerType">
<tag name="form.type" alias="integer" />
</service>
<service id="form.type.language" class="Symfony\Component\Form\Type\LanguageType">
<tag name="form.type" alias="language" />
</service>
<service id="form.type.locale" class="Symfony\Component\Form\Type\LocaleType">
<tag name="form.type" alias="locale" />
</service>
<service id="form.type.money" class="Symfony\Component\Form\Type\MoneyType">
<tag name="form.type" alias="money" />
</service>
<service id="form.type.number" class="Symfony\Component\Form\Type\NumberType">
<tag name="form.type" alias="number" />
</service>
<service id="form.type.password" class="Symfony\Component\Form\Type\PasswordType">
<tag name="form.type" alias="password" />
</service>
<service id="form.type.percent" class="Symfony\Component\Form\Type\PercentType">
<tag name="form.type" alias="percent" />
</service>
<service id="form.type.radio" class="Symfony\Component\Form\Type\RadioType">
<tag name="form.type" alias="radio" />
</service>
<service id="form.type.repeated" class="Symfony\Component\Form\Type\RepeatedType">
<tag name="form.type" alias="repeated" />
</service>
<service id="form.type.textarea" class="Symfony\Component\Form\Type\TextareaType">
<tag name="form.type" alias="textarea" />
</service>
<service id="form.type.text" class="Symfony\Component\Form\Type\TextType">
<tag name="form.type" alias="text" />
</service>
<service id="form.type.time" class="Symfony\Component\Form\Type\TimeType">
<tag name="form.type" alias="time" />
</service>
<service id="form.type.timezone" class="Symfony\Component\Form\Type\TimezoneType">
<tag name="form.type" alias="timezone" />
</service>
<service id="form.type.url" class="Symfony\Component\Form\Type\UrlType">
<tag name="form.type" alias="url" />
</service>
</services>
</container>

View File

@ -1,9 +0,0 @@
<input type="checkbox"
id="<?php echo $field->getId() ?>"
name="<?php echo $field->getName() ?>"
<?php if ($field->hasValue()): ?>value="<?php echo $field->getValue() ?>"<?php endif ?>
<?php if ($field->isDisabled()): ?>disabled="disabled"<?php endif ?>
<?php if ($field->isRequired()): ?>required="required"<?php endif ?>
<?php if ($field->isChecked()): ?>checked="checked"<?php endif ?>
<?php echo $view['form']->attributes($attr) ?>
/>

View File

@ -0,0 +1,8 @@
<input type="checkbox"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
<?php if ($value): ?>value="<?php echo $view->escape($value) ?>"<?php endif ?>
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>
<?php if ($checked): ?>checked="checked"<?php endif ?>
/>

View File

@ -1,48 +0,0 @@
<?php if ($field->isExpanded()): ?>
<?php foreach ($field as $choice => $child): ?>
<?php echo $view['form']->render($child) ?>
<label for="<?php echo $child->getId() ?>"><?php echo $field->getLabel($choice) ?></label>
<?php endforeach ?>
<?php else: ?>
<select
id="<?php echo $field->getId() ?>"
name="<?php echo $field->getName() ?>"
<?php if ($field->isDisabled()): ?> disabled="disabled"<?php endif ?>
<?php if ($field->isMultipleChoice()): ?> multiple="multiple"<?php endif ?>
<?php echo $view['form']->attributes($attr) ?>
>
<?php if (count($field->getPreferredChoices()) > 0): ?>
<?php foreach ($field->getPreferredChoices() as $choice => $label): ?>
<?php if ($field->isChoiceGroup($label)): ?>
<optgroup label="<?php echo $choice ?>">
<?php foreach ($label as $nestedChoice => $nestedLabel): ?>
<option value="<?php echo $nestedChoice ?>"<?php if ($field->isChoiceSelected($nestedChoice)): ?> selected="selected"<?php endif?>>
<?php echo $nestedLabel ?>
</option>
<?php endforeach ?>
</optgroup>
<?php else: ?>
<option value="<?php echo $choice ?>"<?php if ($field->isChoiceSelected($choice)): ?> selected="selected"<?php endif?>>
<?php echo $label ?>
</option>
<?php endif ?>
<?php endforeach ?>
<option disabled="disabled"><?php echo isset($separator) ? $separator : '-----------------' ?></option>
<?php endif ?>
<?php foreach ($field->getOtherChoices() as $choice => $label): ?>
<?php if ($field->isChoiceGroup($label)): ?>
<optgroup label="<?php echo $choice ?>">
<?php foreach ($label as $nestedChoice => $nestedLabel): ?>
<option value="<?php echo $nestedChoice ?>"<?php if ($field->isChoiceSelected($nestedChoice)): ?> selected="selected"<?php endif?>>
<?php echo $nestedLabel ?>
</option>
<?php endforeach ?>
</optgroup>
<?php else: ?>
<option value="<?php echo $choice ?>"<?php if ($field->isChoiceSelected($choice)): ?> selected="selected"<?php endif?>>
<?php echo $label ?>
</option>
<?php endif ?>
<?php endforeach ?>
</select>
<?php endif ?>

View File

@ -0,0 +1,42 @@
<?php if ($expanded): ?>
<div<?php echo $view['form']->attributes() ?>>
<?php foreach ($form as $choice => $child): ?>
<?php echo $view['form']->widget($child) ?>
<?php echo $view['form']->label($child) ?>
<?php endforeach ?>
</div>
<?php else: ?>
<select
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
<?php if ($read_only): ?> disabled="disabled"<?php endif ?>
<?php if ($multiple): ?> multiple="multiple"<?php endif ?>
>
<?php if (!$multiple && !$required): ?><option value=""><?php echo $empty_value; ?></option><?php endif; ?>
<?php if (count($preferred_choices) > 0): ?>
<?php foreach ($preferred_choices as $choice => $label): ?>
<?php if ($form->isChoiceGroup($label)): ?>
<optgroup label="<?php echo $view->escape($choice) ?>">
<?php foreach ($label as $nestedChoice => $nestedLabel): ?>
<option value="<?php echo $view->escape($nestedChoice) ?>"<?php if ($form->isChoiceSelected($nestedChoice)): ?> selected="selected"<?php endif?>><?php echo $view->escape($nestedLabel) ?></option>
<?php endforeach ?>
</optgroup>
<?php else: ?>
<option value="<?php echo $view->escape($choice) ?>"<?php if ($form->isChoiceSelected($choice)): ?> selected="selected"<?php endif?>><?php echo $view->escape($label) ?></option>
<?php endif ?>
<?php endforeach ?>
<option disabled="disabled"><?php echo $separator ?></option>
<?php endif ?>
<?php foreach ($choices as $choice => $label): ?>
<?php if ($form->isChoiceGroup($label)): ?>
<optgroup label="<?php echo $view->escape($choice) ?>">
<?php foreach ($label as $nestedChoice => $nestedLabel): ?>
<option value="<?php echo $view->escape($nestedChoice) ?>"<?php if ($form->isChoiceSelected($nestedChoice)): ?> selected="selected"<?php endif?>><?php echo $view->escape($nestedLabel) ?></option>
<?php endforeach ?>
</optgroup>
<?php else: ?>
<option value="<?php echo $view->escape($choice) ?>"<?php if ($form->isChoiceSelected($choice)): ?> selected="selected"<?php endif?>><?php echo $view->escape($label) ?></option>
<?php endif ?>
<?php endforeach ?>
</select>
<?php endif ?>

View File

@ -0,0 +1 @@
<?php echo $view['form']->render('form', 'widget', $renderer->all()); ?>

View File

@ -0,0 +1,8 @@
<?php if (!$form->hasParent() || !$form->getParent()->hasParent()): ?>
<input type="hidden"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
/>
<?php endif ?>

View File

@ -1,16 +0,0 @@
<?php if ($field->isField()): ?>
<input type="text"
id="<?php echo $field->getId() ?>"
name="<?php echo $field->getName() ?>"
value="<?php echo $field->getDisplayedData() ?>"
<?php if ($field->isDisabled()): ?>disabled="disabled"<?php endif ?>
<?php if ($field->isRequired()): ?>required="required"<?php endif ?>
<?php echo $view['form']->attributes($attr) ?>
/>
<?php else: ?>
<?php echo str_replace(array('{{ year }}', '{{ month }}', '{{ day }}'), array(
$view['form']->render($field['year']),
$view['form']->render($field['month']),
$view['form']->render($field['day']),
), $field->getPattern()) ?>
<?php endif ?>

View File

@ -1,3 +0,0 @@
<?php echo $view['form']->render($field['date']) ?>
<!-- keep space -->
<?php echo $view['form']->render($field['time']) ?>

View File

@ -0,0 +1,18 @@
<?php if ($widget == 'text'): ?>
<input type="text"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>
<?php if ($max_length): ?>maxlength="<?php echo $max_length ?>"<?php endif ?>
/>
<?php else: ?>
<div<?php echo $view['form']->attributes() ?>>
<?php echo str_replace(array('{{ year }}', '{{ month }}', '{{ day }}'), array(
$view['form']->widget($form['year']),
$view['form']->widget($form['month']),
$view['form']->widget($form['day']),
), $date_pattern) ?>
</div>
<?php endif ?>

View File

@ -0,0 +1,5 @@
<div<?php echo $view['form']->attributes() ?>>
<?php echo $view['form']->widget($form['date'])
. ' '
. $view['form']->widget($form['time']) ?>
</div>

View File

@ -0,0 +1,8 @@
<input type="email"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($max_length): ?>maxlength="<?php echo $view->escape($max_length) ?>"<?php endif ?>
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>
/>

View File

@ -0,0 +1 @@
<?php if ($form->get('multipart')): ?>enctype="multipart/form-data"<?php endif ?>

View File

@ -1,6 +1,6 @@
<?php if ($field->hasErrors()): ?>
<?php if ($errors): ?>
<ul>
<?php foreach ($field->getErrors() as $error): ?>
<?php foreach ($errors as $error): ?>
<li><?php echo $view['translator']->trans(
$error->getMessageTemplate(),
$error->getMessageParameters(),

View File

@ -0,0 +1 @@
<label for="<?php echo $view->escape($id) ?>"><?php echo $view->escape($view['translator']->trans($label)) ?></label>

View File

@ -0,0 +1,5 @@
<?php foreach ($form->getChildren() as $child): ?>
<?php if (!$child->isRendered()): ?>
<?php echo $view['form']->row($child) ?>
<?php endif; ?>
<?php endforeach; ?>

View File

@ -1,5 +1,5 @@
<div>
<?php echo $view['form']->label($field) ?>
<?php echo $view['form']->errors($field) ?>
<?php echo $view['form']->render($field) ?>
<?php echo $view['form']->label($form) ?>
<?php echo $view['form']->errors($form) ?>
<?php echo $view['form']->widget($form) ?>
</div>

View File

@ -0,0 +1,7 @@
<input
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>
/>

View File

@ -1,10 +0,0 @@
<input type="file"
id="<?php echo $field['file']->getId() ?>"
name="<?php echo $field['file']->getName() ?>"
<?php if ($field['file']->isDisabled()): ?>disabled="disabled"<?php endif ?>
<?php if ($field['file']->isRequired()): ?>required="required"<?php endif ?>
<?php echo $view['form']->attributes($attr) ?>
/>
<?php echo $view['form']->render($field['token']) ?>
<?php echo $view['form']->render($field['original_name']) ?>

View File

@ -0,0 +1,11 @@
<div<?php echo $view['form']->attributes() ?>>
<input type="file"
id="<?php echo $view->escape($form['file']->get('id')) ?>"
name="<?php echo $view->escape($form['file']->get('name')) ?>"
<?php if ($form['file']->get('disabled')): ?>disabled="disabled"<?php endif ?>
<?php if ($form['file']->get('required')): ?>required="required"<?php endif ?>
/>
<?php echo $view['form']->widget($form['token']) ?>
<?php echo $view['form']->widget($form['name']) ?>
</div>

View File

@ -1,9 +0,0 @@
<?php echo $view['form']->errors($field) ?>
<div>
<?php foreach ($field->getVisibleFields() as $child): ?>
<?php echo $view['form']->row($child) ?>
<?php endforeach; ?>
</div>
<?php echo $view['form']->hidden($field) ?>

View File

@ -0,0 +1,8 @@
<div<?php echo $view['form']->attributes() ?>>
<?php echo $view['form']->errors($form); ?>
<?php foreach ($form->getChildren() as $child): ?>
<?php echo $view['form']->row($child); ?>
<?php endforeach; ?>
<?php echo $view['form']->rest($form) ?>
</div>

View File

@ -1,3 +0,0 @@
<?php foreach ($field->getAllHiddenFields() as $child): ?>
<?php echo $view['form']->render($child) ?>
<?php endforeach; ?>

View File

@ -1,7 +0,0 @@
<input type="hidden"
id="<?php echo $field->getId() ?>"
name="<?php echo $field->getName() ?>"
value="<?php echo $field->getDisplayedData() ?>"
<?php if ($field->isDisabled()): ?>disabled="disabled"<?php endif ?>
<?php echo $view['form']->attributes($attr) ?>
/>

View File

@ -0,0 +1 @@
<?php echo $view['form']->widget($form) ?>

View File

@ -0,0 +1,6 @@
<input type="hidden"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
/>

View File

@ -0,0 +1,7 @@
<input type="number"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>
/>

View File

@ -1 +0,0 @@
<label for="<?php echo $field->getId() ?>"><?php echo $view['translator']->trans($label) ?></label>

View File

@ -1,4 +0,0 @@
<?php echo str_replace('{{ widget }}',
$view['form']->render($field, array(), array(), 'FrameworkBundle:Form:number_field.html.php'),
$field->getPattern()
) ?>

View File

@ -0,0 +1,4 @@
<?php echo str_replace('{{ widget }}',
$view['form']->render('FrameworkBundle:Form:number_widget.html.php'),
$money_pattern
) ?>

View File

@ -1,8 +0,0 @@
<input type="number"
id="<?php echo $field->getId() ?>"
name="<?php echo $field->getName() ?>"
value="<?php echo $field->getDisplayedData() ?>"
<?php if ($field->isDisabled()): ?>disabled="disabled"<?php endif ?>
<?php if ($field->isRequired()): ?>required="required"<?php endif ?>
<?php echo $view['form']->attributes($attr) ?>
/>

View File

@ -0,0 +1,8 @@
<input type="text"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>
<?php if ($max_length): ?>maxlength="<?php echo $max_length ?>"<?php endif ?>
/>

View File

@ -1,9 +0,0 @@
<input type="password"
id="<?php echo $field->getId() ?>"
name="<?php echo $field->getName() ?>"
value="<?php echo $field->getDisplayedData() ?>"
<?php if ($field->isDisabled()): ?>disabled="disabled"<?php endif ?>
<?php if ($field->isRequired()): ?>required="required"<?php endif ?>
<?php // FIXME if (!isset($attr['maxlength']) && $field->getMaxLength() > 0) $attr['maxlength'] = $field->getMaxLength() ?>
<?php echo $view['form']->attributes($attr) ?>
/>

View File

@ -0,0 +1,8 @@
<input type="password"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>
<?php if ($max_length): ?>maxlength="<?php echo $max_length ?>"<?php endif ?>
/>

View File

@ -1 +0,0 @@
<?php echo $view['form']->render($field, array(), array(), 'FrameworkBundle:Form:number_field.html.php') ?> %

View File

@ -0,0 +1 @@
<?php echo $view['form']->render('FrameworkBundle:Form:number_widget.html.php') ?> %

View File

@ -1,9 +0,0 @@
<input type="radio"
id="<?php echo $field->getId() ?>"
name="<?php echo $field->getName() ?>"
<?php if ($field->hasValue()): ?>value="<?php echo $field->getValue() ?>"<?php endif ?>
<?php if ($field->isDisabled()): ?>disabled="disabled"<?php endif ?>
<?php if ($field->isRequired()): ?>required="required"<?php endif ?>
<?php if ($field->isChecked()): ?>checked="checked"<?php endif ?>
<?php echo $view['form']->attributes($attr) ?>
/>

View File

@ -0,0 +1,8 @@
<input type="radio"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>
<?php if ($checked): ?>checked="checked"<?php endif ?>
/>

View File

@ -0,0 +1,5 @@
<?php echo $view['form']->errors($form) ?>
<?php foreach ($form->getChildren() as $child): ?>
<?php echo $view['form']->row($child); ?>
<?php endforeach; ?>

View File

@ -1,9 +0,0 @@
<input type="text"
id="<?php echo $field->getId() ?>"
name="<?php echo $field->getName() ?>"
value="<?php echo $field->getDisplayedData() ?>"
<?php if ($field->isDisabled()): ?>disabled="disabled"<?php endif ?>
<?php if ($field->isRequired()): ?>required="required"<?php endif ?>
<?php if (!isset($attr['maxlength']) && $field->getMaxLength() > 0) $attr['maxlength'] = $field->getMaxLength() ?>
<?php echo $view['form']->attributes($attr) ?>
/>

View File

@ -0,0 +1,8 @@
<input type="text"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>
<?php if ($max_length): ?>maxlength="<?php echo $max_length ?>"<?php endif ?>
/>

View File

@ -1,9 +0,0 @@
<textarea
id="<?php echo $field->getId() ?>"
name="<?php echo $field->getName() ?>"
<?php if ($field->isDisabled()): ?>disabled="disabled"<?php endif ?>
<?php if ($field->isRequired()): ?>required="required"<?php endif ?>
<?php echo $view['form']->attributes($attr) ?>
><?php
echo $view->escape($field->getDisplayedData())
?></textarea>

View File

@ -0,0 +1,6 @@
<textarea
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>
><?php echo $view->escape($value) ?></textarea>

View File

@ -1,12 +0,0 @@
<?php
// There should be no spaces between the colons and the widgets, that's why
// this block is written in a single PHP tag
echo $view['form']->render($field['hour'], array('size' => 1));
echo ':';
echo $view['form']->render($field['minute'], array('size' => 1));
if ($field->isWithSeconds()) {
echo ':';
echo $view['form']->render($field['second'], array('size' => 1));
}
?>

View File

@ -0,0 +1,14 @@
<div<?php echo $view['form']->attributes() ?>>
<?php
// There should be no spaces between the colons and the widgets, that's why
// this block is written in a single PHP tag
echo $view['form']->widget($form['hour'], array('attr' => array('size' => 1)));
echo ':';
echo $view['form']->widget($form['minute'], array('attr' => array('size' => 1)));
if ($with_seconds) {
echo ':';
echo $view['form']->widget($form['second'], array('attr' => array('size' => 1)));
}
?>
</div>

View File

@ -1,8 +0,0 @@
<input type="url"
id="<?php echo $field->getId() ?>"
name="<?php echo $field->getName() ?>"
value="<?php echo $field->getDisplayedData() ?>"
<?php if ($field->isDisabled()): ?>disabled="disabled"<?php endif ?>
<?php if ($field->isRequired()): ?>required="required"<?php endif ?>
<?php echo $view['form']->attributes($attr) ?>
/>

View File

@ -0,0 +1,7 @@
<input type="url"
<?php echo $view['form']->attributes() ?>
name="<?php echo $view->escape($name) ?>"
value="<?php echo $view->escape($value) ?>"
<?php if ($read_only): ?>disabled="disabled"<?php endif ?>
<?php if ($required): ?>required="required"<?php endif ?>
/>

View File

@ -12,12 +12,12 @@
namespace Symfony\Bundle\FrameworkBundle\Templating\Helper;
use Symfony\Component\Templating\Helper\Helper;
use Symfony\Component\Form\FieldInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Exception\FormException;
/**
* Form is a factory that wraps Form instances.
*
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
@ -28,82 +28,45 @@ class FormHelper extends Helper
protected $engine;
protected $varStack = array();
public function __construct(EngineInterface $engine)
{
$this->engine = $engine;
}
public function getName()
public function attributes()
{
return 'form';
}
$html = '';
$attr = array();
public function attributes($attributes)
{
if ($attributes instanceof \Traversable) {
$attributes = iterator_to_array($attributes);
}
if (count($this->varStack) > 0) {
$vars = end($this->varStack);
return implode('', array_map(array($this, 'attributesCallback'), array_keys($attributes), array_values($attributes)));
}
if (isset($vars['attr'])) {
$attr = $vars['attr'];
}
private function attribute($name, $value)
{
return sprintf('%s="%s"', $name, true === $value ? $name : $value);
}
/**
* Prepares an attribute key and value for HTML representation.
*
* It removes empty attributes, except for the value one.
*
* @param string $name The attribute name
* @param string $value The attribute value
*
* @return string The HTML representation of the HTML key attribute pair.
*/
private function attributesCallback($name, $value)
{
if (false === $value || null === $value || ('' === $value && 'value' != $name)) {
return '';
}
return ' '.$this->attribute($name, $value);
}
/**
* Renders the form tag.
*
* This method only renders the opening form tag.
* You need to close it after the form rendering.
*
* This method takes into account the multipart widgets.
*
* @param string $url The URL for the action
* @param array $attributes An array of HTML attributes
*
* @return string An HTML representation of the opening form tag
*/
public function enctype(/*Form */$form)
{
return $form->isMultipart() ? ' enctype="multipart/form-data"' : '';
}
public function render(/*FieldInterface */$field, array $attributes = array(), array $parameters = array(), $template = null)
{
if (null === $template) {
$template = $this->lookupTemplate($field);
if (null === $template) {
throw new \RuntimeException(sprintf('Unable to find a template to render the "%s" widget.', $field->getKey()));
if (isset($vars['id'])) {
$attr['id'] = $vars['id'];
}
}
return trim($this->engine->render($template, array(
'field' => $field,
'attr' => $attributes,
'params' => $parameters,
)));
foreach ($attr as $k => $v) {
$html .= ' '.$this->engine->escape($k).'="'.$this->engine->escape($v).'"';
}
return $html;
}
public function enctype(FormView $view)
{
return $this->renderSection($view, 'enctype');
}
public function widget(FormView $view, array $variables = array())
{
return trim($this->renderSection($view, 'widget', $variables));
}
/**
@ -112,85 +75,89 @@ class FormHelper extends Helper
* @param FieldInterface $field
* @return string
*/
public function row(/*FieldInterface*/ $field, $template = null)
public function row(FormView $view, array $variables = array())
{
if (null === $template) {
$template = 'FrameworkBundle:Form:field_row.html.php';
}
return $this->engine->render($template, array(
'field' => $field,
));
return $this->renderSection($view, 'row', $variables);
}
public function label(/*FieldInterface */$field, $label = false, array $parameters = array(), $template = null)
public function label(FormView $view, $label = null)
{
if (null === $template) {
$template = 'FrameworkBundle:Form:label.html.php';
}
return $this->engine->render($template, array(
'field' => $field,
'params' => $parameters,
'label' => $label ? $label : ucfirst(strtolower(str_replace('_', ' ', $field->getKey())))
));
return $this->renderSection($view, 'label', null === $label ? array() : array('label' => $label));
}
public function errors(/*FieldInterface */$field, array $parameters = array(), $template = null)
public function errors(FormView $view)
{
if (null === $template) {
$template = 'FrameworkBundle:Form:errors.html.php';
}
return $this->engine->render($template, array(
'field' => $field,
'params' => $parameters,
));
return $this->renderSection($view, 'errors');
}
public function hidden(/*FormInterface */$form, array $parameters = array(), $template = null)
public function rest(FormView $view, array $variables = array())
{
if (null === $template) {
$template = 'FrameworkBundle:Form:hidden.html.php';
}
return $this->engine->render($template, array(
'field' => $form,
'params' => $parameters,
));
return $this->renderSection($view, 'rest', $variables);
}
protected function lookupTemplate(/*FieldInterface */$field)
protected function renderSection(FormView $view, $section, array $variables = array())
{
$fqClassName = get_class($field);
$template = null;
$blocks = $view->get('types');
if (isset(self::$cache[$fqClassName])) {
return self::$cache[$fqClassName];
}
foreach ($blocks as &$block) {
$block = $block.'_'.$section;
$template = $this->lookupTemplate($block);
// find a template for the given class or one of its parents
$currentFqClassName = $fqClassName;
do {
$parts = explode('\\', $currentFqClassName);
$className = array_pop($parts);
$underscoredName = strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), strtr($className, '_', '.')));
if ($this->engine->exists($guess = 'FrameworkBundle:Form:'.$underscoredName.'.html.php')) {
$template = $guess;
if ($template) {
break;
}
$currentFqClassName = get_parent_class($currentFqClassName);
} while (null === $template && false !== $currentFqClassName);
if (null === $template && $field instanceof FormInterface) {
$template = 'FrameworkBundle:Form:form.html.php';
}
self::$cache[$fqClassName] = $template;
if (!$template) {
throw new FormException(sprintf('Unable to render form as none of the following blocks exist: "%s".', implode('", "', $blocks)));
}
if ('widget' === $section || 'row' === $section) {
$view->setRendered(true);
}
return $this->render($template, array_merge($view->all(), $variables));
}
public function render($template, array $variables = array())
{
array_push($this->varStack, array_merge(
count($this->varStack) > 0 ? end($this->varStack) : array(),
$variables
));
$html = $this->engine->render($template, end($this->varStack));
array_pop($this->varStack);
return $html;
}
protected function lookupTemplate($templateName)
{
if (isset(self::$cache[$templateName])) {
return self::$cache[$templateName];
}
$template = $templateName.'.html.php';
/*
if ($this->templateDir) {
$template = $this->templateDir.':'.$template;
}
*/
$template = 'FrameworkBundle:Form:'.$template;
if (!$this->engine->exists($template)) {
$template = false;
}
self::$cache[$templateName] = $template;
return $template;
}
public function getName()
{
return 'form';
}
}

View File

@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper\Fixtures;
use Symfony\Component\Templating\TemplateNameParserInterface;
use Symfony\Component\Templating\TemplateReference;
class StubTemplateNameParser implements TemplateNameParserInterface
{
private $root;
public function __construct($root)
{
$this->root = $root;
}
public function parse($name)
{
$parts = explode(':', $name);
$name = $parts[count($parts)-1];
return new TemplateReference($this->root.'/'.$name, 'php');
}
}

View File

@ -0,0 +1,35 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper\Fixtures;
use Symfony\Component\Translation\TranslatorInterface;
class StubTranslator implements TranslatorInterface
{
public function trans($id, array $parameters = array(), $domain = null, $locale = null)
{
return '[trans]'.$id.'[/trans]';
}
public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null)
{
return '[trans]'.$id.'[/trans]';
}
public function setLocale($locale)
{
}
public function getLocale()
{
}
}

View File

@ -0,0 +1,78 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper;
require_once __DIR__.'/Fixtures/StubTemplateNameParser.php';
require_once __DIR__.'/Fixtures/StubTranslator.php';
use Symfony\Bundle\FrameworkBundle\Templating\Helper\FormHelper;
use Symfony\Bundle\FrameworkBundle\Templating\Helper\TranslatorHelper;
use Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper\Fixtures\StubTemplateNameParser;
use Symfony\Bundle\FrameworkBundle\Tests\Templating\Helper\Fixtures\StubTranslator;
use Symfony\Component\Form\FormView;
use Symfony\Component\Templating\PhpEngine;
use Symfony\Component\Templating\TemplateNameParser;
use Symfony\Component\Templating\Loader\FilesystemLoader;
use Symfony\Tests\Component\Form\AbstractDivLayoutTest;
class FormHelperTest extends AbstractDivLayoutTest
{
protected $helper;
protected function setUp()
{
parent::setUp();
$root = realpath(__DIR__.'/../../../Resources/views/Form');
$templateNameParser = new StubTemplateNameParser($root);
$loader = new FilesystemLoader(array());
$engine = new PhpEngine($templateNameParser, $loader);
$this->helper = new FormHelper($engine);
$engine->setHelpers(array(
$this->helper,
new TranslatorHelper(new StubTranslator()),
));
}
protected function renderEnctype(FormView $view)
{
return (string)$this->helper->enctype($view);
}
protected function renderLabel(FormView $view, $label = null)
{
return (string)$this->helper->label($view, $label);
}
protected function renderErrors(FormView $view)
{
return (string)$this->helper->errors($view);
}
protected function renderWidget(FormView $view, array $vars = array())
{
return (string)$this->helper->widget($view, $vars);
}
protected function renderRow(FormView $view, array $vars = array())
{
return (string)$this->helper->row($view, $vars);
}
protected function renderRest(FormView $view, array $vars = array())
{
return (string)$this->helper->rest($view, $vars);
}
}

View File

@ -59,11 +59,11 @@ class Configuration implements ConfigurationInterface
->children()
->arrayNode('resources')
->addDefaultsIfNotSet()
->defaultValue(array('TwigBundle::form.html.twig'))
->defaultValue(array('TwigBundle:Form:div_layout.html.twig'))
->validate()
->always()
->then(function($v){
return array_merge(array('TwigBundle::form.html.twig'), $v);
return array_merge(array('TwigBundle:Form:div_layout.html.twig'), $v);
})
->end()
->prototype('scalar')->end()

View File

@ -1,300 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\TwigBundle\Extension;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FieldInterface;
use Symfony\Component\Form\CollectionField;
use Symfony\Component\Form\HybridField;
use Symfony\Bundle\TwigBundle\TokenParser\FormThemeTokenParser;
/**
* FormExtension extends Twig with form capabilities.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
class FormExtension extends \Twig_Extension
{
protected $resources;
protected $templates;
protected $environment;
protected $themes;
public function __construct(array $resources = array())
{
$this->themes = new \SplObjectStorage();
$this->resources = $resources;
}
/**
* {@inheritdoc}
*/
public function initRuntime(\Twig_Environment $environment)
{
$this->environment = $environment;
}
/**
* Sets a theme for a given field.
*
* @param FieldInterface $field A FieldInterface instance
* @param array $resources An array of resources
*/
public function setTheme(FieldInterface $field, array $resources)
{
$this->themes->attach($field, $resources);
}
/**
* Returns the token parser instance to add to the existing list.
*
* @return array An array of Twig_TokenParser instances
*/
public function getTokenParsers()
{
return array(
// {% form_theme form "SomeBungle::widgets.twig" %}
new FormThemeTokenParser(),
);
}
public function getFunctions()
{
return array(
'form_enctype' => new \Twig_Function_Method($this, 'renderEnctype', array('is_safe' => array('html'))),
'form_field' => new \Twig_Function_Method($this, 'renderField', array('is_safe' => array('html'))),
'form_hidden' => new \Twig_Function_Method($this, 'renderHidden', array('is_safe' => array('html'))),
'form_errors' => new \Twig_Function_Method($this, 'renderErrors', array('is_safe' => array('html'))),
'form_label' => new \Twig_Function_Method($this, 'renderLabel', array('is_safe' => array('html'))),
'form_data' => new \Twig_Function_Method($this, 'renderData', array('is_safe' => array('html'))),
'form_row' => new \Twig_Function_Method($this, 'renderRow', array('is_safe' => array('html'))),
);
}
/**
* Renders the HTML enctype in the form tag, if necessary
*
* Example usage in Twig templates:
*
* <form action="..." method="post" {{ form_enctype(form) }}>
*
* @param Form $form The form for which to render the encoding type
*/
public function renderEnctype(Form $form)
{
return $form->isMultipart() ? 'enctype="multipart/form-data"' : '';
}
/**
* Renders a field row.
*
* @param FieldInterface $field The field to render as a row
*/
public function renderRow(FieldInterface $field)
{
return $this->render($field, 'field_row', array(
'child' => $field,
));
}
/**
* Renders the HTML for an individual form field
*
* Example usage in Twig:
*
* {{ form_field(field) }}
*
* You can pass attributes element during the call:
*
* {{ form_field(field, {'class': 'foo'}) }}
*
* Some fields also accept additional variables as parameters:
*
* {{ form_field(field, {}, {'separator': '+++++'}) }}
*
* @param FieldInterface $field The field to render
* @param array $attributes HTML attributes passed to the template
* @param array $parameters Additional variables passed to the template
* @param array|string $resources A resource or array of resources
*/
public function renderField(FieldInterface $field, array $attributes = array(), array $parameters = array(), $resources = null)
{
if (null !== $resources && !is_array($resources)) {
$resources = array($resources);
}
return $this->render($field, 'field', array(
'field' => $field,
'attr' => $attributes,
'params' => $parameters,
), $resources);
}
/**
* Renders all hidden fields of the given field group
*
* @param FormInterface $group The field group
* @param array $params Additional variables passed to the
* template
*/
public function renderHidden(FormInterface $group, array $parameters = array())
{
return $this->render($group, 'hidden', array(
'field' => $group,
'params' => $parameters,
));
}
/**
* Renders the errors of the given field
*
* @param FieldInterface $field The field to render the errors for
* @param array $params Additional variables passed to the template
*/
public function renderErrors(FieldInterface $field, array $parameters = array())
{
return $this->render($field, 'errors', array(
'field' => $field,
'params' => $parameters,
));
}
/**
* Renders the label of the given field
*
* @param FieldInterface $field The field to render the label for
* @param array $params Additional variables passed to the template
*/
public function renderLabel(FieldInterface $field, $label = null, array $parameters = array())
{
return $this->render($field, 'label', array(
'field' => $field,
'params' => $parameters,
'label' => null !== $label ? $label : ucfirst(strtolower(str_replace('_', ' ', $field->getKey()))),
));
}
/**
* Renders the widget data of the given field
*
* @param FieldInterface $field The field to render the data for
*/
public function renderData(FieldInterface $field)
{
return $field->getData();
}
protected function render(FieldInterface $field, $name, array $arguments, array $resources = null)
{
if ('field' === $name) {
list($name, $template) = $this->getWidget($field, $resources);
} else {
$template = $this->getTemplate($field, $name);
}
return $template->renderBlock($name, $arguments);
}
/**
* @param FieldInterface $field The field to get the widget for
* @param array $resources An array of template resources
* @return array
*/
protected function getWidget(FieldInterface $field, array $resources = null)
{
$class = get_class($field);
$templates = $this->getTemplates($field, $resources);
// find a template for the given class or one of its parents
do {
$parts = explode('\\', $class);
$c = array_pop($parts);
// convert the base class name (e.g. TextareaField) to underscores (e.g. textarea_field)
$underscore = strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), strtr($c, '_', '.')));
if (isset($templates[$underscore])) {
return array($underscore, $templates[$underscore]);
}
} while (false !== $class = get_parent_class($class));
throw new \RuntimeException(sprintf('Unable to render the "%s" field.', $field->getKey()));
}
protected function getTemplate(FieldInterface $field, $name, array $resources = null)
{
$templates = $this->getTemplates($field, $resources);
return $templates[$name];
}
protected function getTemplates(FieldInterface $field, array $resources = null)
{
// templates are looked for in the following resources:
// * resources provided directly into the function call
// * resources from the themes (and its parents)
// * default resources
// defaults
$all = $this->resources;
// themes
$parent = $field;
do {
if (isset($this->themes[$parent])) {
$all = array_merge($all, $this->themes[$parent]);
}
} while ($parent = $parent->getParent());
// local
$all = array_merge($all, null !== $resources ? (array) $resources : array());
$templates = array();
foreach ($all as $resource) {
if (!$resource instanceof \Twig_Template) {
$resource = $this->environment->loadTemplate($resource);
}
$blocks = array();
foreach ($this->getBlockNames($resource) as $name) {
$blocks[$name] = $resource;
}
$templates = array_replace($templates, $blocks);
}
return $templates;
}
protected function getBlockNames($resource)
{
$names = $resource->getBlockNames();
$parent = $resource;
while (false !== $parent = $parent->getParent(array())) {
$names = array_merge($names, $parent->getBlockNames());
}
return array_unique($names);
}
/**
* Returns the name of the extension.
*
* @return string The extension name
*/
public function getName()
{
return 'form';
}
}

View File

@ -52,7 +52,7 @@
<tag name="twig.extension" />
</service>
<service id="twig.extension.form" class="Symfony\Bundle\TwigBundle\Extension\FormExtension" public="false">
<service id="twig.extension.form" class="Symfony\Bridge\Twig\Extension\FormExtension" public="false">
<tag name="twig.extension" />
<argument>%twig.form.resources%</argument>
</service>

View File

@ -0,0 +1,265 @@
{% block field_rows %}
{% spaceless %}
{{ form_errors(form) }}
{% for child in form.children %}
{{ form_row(child) }}
{% endfor %}
{% endspaceless %}
{% endblock field_rows %}
{% block field_enctype %}
{% spaceless %}
{% if multipart %}enctype="multipart/form-data"{% endif %}
{% endspaceless %}
{% endblock field_enctype %}
{% block field_errors %}
{% spaceless %}
{% if errors|length > 0 %}
<ul>
{% for error in errors %}
<li>{{ error.messageTemplate|trans(error.messageParameters, 'validators') }}</li>
{% endfor %}
</ul>
{% endif %}
{% endspaceless %}
{% endblock field_errors %}
{% block field_rest %}
{% spaceless %}
{% for child in form.children %}
{% if not child.rendered %}
{{ form_row(child) }}
{% endif %}
{% endfor %}
{% endspaceless %}
{% endblock field_rest %}
{% block field_label %}
{% spaceless %}
<label for="{{ id }}">{{ label|trans }}</label>
{% endspaceless %}
{% endblock field_label %}
{% block attributes %}
{% spaceless %}
id="{{ id }}" name="{{ name }}"{% if read_only %} disabled="disabled"{% endif %}{% if required %} required="required"{% endif %}{% if max_length %} maxlength="{{ max_length }}"{% endif %}
{% for attrname,attrvalue in attr %}{{attrname}}="{{attrvalue}}" {% endfor %}
{% endspaceless %}
{% endblock attributes %}
{% block field_widget %}
{% spaceless %}
{% set type = type|default('text') %}
<input type="{{ type }}" {{ block('attributes') }} value="{{ value }}" />
{% endspaceless %}
{% endblock field_widget %}
{% block text_widget %}
{% spaceless %}
{% set type = type|default('text') %}
{{ block('field_widget') }}
{% endspaceless %}
{% endblock text_widget %}
{% block password_widget %}
{% spaceless %}
{% set type = type|default('password') %}
{{ block('field_widget') }}
{% endspaceless %}
{% endblock password_widget %}
{% block hidden_widget %}
{% set type = type|default('hidden') %}
{{ block('field_widget') }}
{% endblock hidden_widget %}
{% block csrf_widget %}
{% if not form.hasParent or not form.getParent.hasParent %}
{% set type = type|default('hidden') %}
{{ block('field_widget') }}
{% endif %}
{% endblock csrf_widget %}
{% block hidden_row %}
{{ form_widget(form) }}
{% endblock hidden_row %}
{% block textarea_widget %}
{% spaceless %}
<textarea {{ block('attributes') }}>{{ value }}</textarea>
{% endspaceless %}
{% endblock textarea_widget %}
{% block options %}
{% spaceless %}
{% for choice, label in options %}
{% if form.choiceGroup(label) %}
<optgroup label="{{ choice }}">
{% for nestedChoice, nestedLabel in label %}
<option value="{{ nestedChoice }}"{% if form.choiceSelected(nestedChoice) %} selected="selected"{% endif %}>{{ nestedLabel }}</option>
{% endfor %}
</optgroup>
{% else %}
<option value="{{ choice }}"{% if form.choiceSelected(choice) %} selected="selected"{% endif %}>{{ label }}</option>
{% endif %}
{% endfor %}
{% endspaceless %}
{% endblock options %}
{% block choice_widget %}
{% spaceless %}
{% if expanded %}
<div {{ block('attributes') }}>
{% for choice, child in form %}
{{ form_widget(child) }}
{{ form_label(child) }}
{% endfor %}
</div>
{% else %}
<select {{ block('attributes') }}{% if multiple %} multiple="multiple"{% endif %}>
{% if not multiple and not required %}
<option value="">{{ empty_value }}</option>
{% endif %}
{% if preferred_choices|length > 0 %}
{% set options = preferred_choices %}
{{ block('options') }}
<option disabled="disabled">{{ separator }}</option>
{% endif %}
{% set options = choices %}
{{ block('options') }}
</select>
{% endif %}
{% endspaceless %}
{% endblock choice_widget %}
{% block checkbox_widget %}
{% spaceless %}
<input type="checkbox" {{ block('attributes') }}{% if value is defined %} value="{{ value }}"{% endif %}{% if checked %} checked="checked"{% endif %} />
{% endspaceless %}
{% endblock checkbox_widget %}
{% block radio_widget %}
{% spaceless %}
<input type="radio" {{ block('attributes') }}{% if value is defined %} value="{{ value }}"{% endif %}{% if checked %} checked="checked"{% endif %} />
{% endspaceless %}
{% endblock radio_widget %}
{% block datetime_widget %}
{% spaceless %}
<div {{ block('attributes') }}>
{{ form_errors(form.date) }}
{{ form_errors(form.time) }}
{{ form_widget(form.date) }}
{{ form_widget(form.time) }}
</div>
{% endspaceless %}
{% endblock datetime_widget %}
{% block date_widget %}
{% spaceless %}
{% if widget == 'text' %}
{{ block('text_widget') }}
{% else %}
<div {{ block('attributes') }}>
{{ date_pattern|replace({
'{{ year }}': form_widget(form.year),
'{{ month }}': form_widget(form.month),
'{{ day }}': form_widget(form.day),
})|raw }}
</div>
{% endif %}
{% endspaceless %}
{% endblock date_widget %}
{% block time_widget %}
{% spaceless %}
<div {{ block('attributes') }}>
{{ form_widget(form.hour, { 'attr': { 'size': '1' } }) }}:{{ form_widget(form.minute, { 'attr': { 'size': '1' } }) }}{% if with_seconds %}:{{ form_widget(form.second, { 'attr': { 'size': '1' } }) }}{% endif %}
</div>
{% endspaceless %}
{% endblock time_widget %}
{% block number_widget %}
{% spaceless %}
{# type="number" doesn't work with floats #}
{% set type = type|default('text') %}
{{ block('field_widget') }}
{% endspaceless %}
{% endblock number_widget %}
{% block integer_widget %}
{% spaceless %}
{% set type = type|default('number') %}
{{ block('field_widget') }}
{% endspaceless %}
{% endblock integer_widget %}
{% block money_widget %}
{% spaceless %}
{{ money_pattern|replace({ '{{ widget }}': block('field_widget') })|raw }}
{% endspaceless %}
{% endblock money_widget %}
{% block url_widget %}
{% spaceless %}
{% set type = type|default('url') %}
{{ block('field_widget') }}
{% endspaceless %}
{% endblock url_widget %}
{% block percent_widget %}
{% spaceless %}
{% set type = type|default('text') %}
{{ block('field_widget') }} %
{% endspaceless %}
{% endblock percent_widget %}
{% block file_widget %}
{% spaceless %}
<div {{ block('attributes') }}>
{{ form_widget(form.file) }}
{{ form_widget(form.token) }}
{{ form_widget(form.name) }}
</div>
{% endspaceless %}
{% endblock file_widget %}
{% block collection_widget %}
{% spaceless %}
{{ block('form_widget') }}
{% endspaceless %}
{% endblock collection_widget %}
{% block repeated_row %}
{% spaceless %}
{{ block('field_rows') }}
{% endspaceless %}
{% endblock repeated_row %}
{% block field_row %}
{% spaceless %}
<div>
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
</div>
{% endspaceless %}
{% endblock field_row %}
{% block form_widget %}
{% spaceless %}
<div {{ block('attributes') }}>
{{ block('field_rows') }}
{{ form_rest(form) }}
</div>
{% endspaceless %}
{% endblock form_widget %}
{% block email_widget %}
{% spaceless %}
{% set type = type|default('email') %}
{{ block('field_widget') }}
{% endspaceless %}
{% endblock email_widget %}

View File

@ -0,0 +1,52 @@
{% extends "TwigBundle:Form:div_layout.html.twig" %}
{% block field_row %}
{% spaceless %}
<tr>
<td>
{{ form_label(form) }}
</td>
<td>
{{ form_errors(form) }}
{{ form_widget(form) }}
</td>
</tr>
{% endspaceless %}
{% endblock field_row %}
{% block form_errors %}
{% spaceless %}
{% if errors|length > 0 %}
<tr>
<td colspan="2">
{{ block('field_errors') }}
</td>
</tr>
{% endif %}
{% endspaceless %}
{% endblock form_errors %}
{% block hidden_row %}
{% spaceless %}
<tr style="display: none">
<td colspan="2">
{{ form_widget(form) }}
</td>
</tr>
{% endspaceless %}
{% endblock hidden_row %}
{% block repeated_errors %}
{% spaceless %}
{{ block('form_errors') }}
{% endspaceless %}
{% endblock repeated_errors %}
{% block form_widget %}
{% spaceless %}
<table {{ block('attributes') }}>
{{ block('field_rows') }}
{{ form_rest(form) }}
</table>
{% endspaceless %}
{% endblock form_widget %}

View File

@ -1,203 +0,0 @@
{% block field_row %}
{% spaceless %}
<div>
{# TODO: would be nice to rename this variable to "field" #}
{{ form_label(child) }}
{{ form_errors(child) }}
{{ form_field(child) }}
</div>
{% endspaceless %}
{% endblock field_row %}
{% block form %}
{% spaceless %}
{{ form_errors(field) }}
{% for child in field.visibleFields %}
{{ block('field_row') }}
{% endfor %}
{{ form_hidden(field) }}
{% endspaceless %}
{% endblock form %}
{% block errors %}
{% spaceless %}
{% if field.hasErrors %}
<ul>
{% for error in field.errors %}
<li>{% trans error.messageTemplate with error.messageParameters from 'validators' %}</li>
{% endfor %}
</ul>
{% endif %}
{% endspaceless %}
{% endblock errors %}
{% block hidden %}
{% spaceless %}
{% for child in field.allHiddenFields %}
{{ form_field(child) }}
{% endfor %}
{% endspaceless %}
{% endblock hidden %}
{% block label %}
{% spaceless %}
<label for="{{ field.id }}">{% trans label %}</label>
{% endspaceless %}
{% endblock label %}
{% block attributes %}
{% spaceless %}
{% for key, value in attr %}
{{ key }}="{{ value }}"
{% endfor %}
{% endspaceless %}
{% endblock attributes %}
{% block field_attributes %}
{% spaceless %}
id="{{ field.id }}" name="{{ field.name }}"{% if field.disabled %} disabled="disabled"{% endif %}{% if field.required %} required="required"{% endif %}
{{ block('attributes') }}
{% endspaceless %}
{% endblock field_attributes %}
{% block text_field %}
{% spaceless %}
{% if attr.type is defined and attr.type != "text" %}
<input {{ block('field_attributes') }} value="{{ field.displayedData }}" />
{% else %}
{% set attr = attr|merge({ 'maxlength': attr.maxlength|default(field.maxlength) }) %}
<input type="text" {{ block('field_attributes') }} value="{{ field.displayedData }}" />
{% endif %}
{% endspaceless %}
{% endblock text_field %}
{% block password_field %}
{% spaceless %}
{% set attr = attr|merge({ 'maxlength': attr.maxlength|default(field.maxlength) }) %}
<input type="password" {{ block('field_attributes') }} value="{{ field.displayedData }}" />
{% endspaceless %}
{% endblock password_field %}
{% block hidden_field %}
{% spaceless %}
<input type="hidden" id="{{ field.id }}" name="{{ field.name }}"{% if field.disabled %} disabled="disabled"{% endif %} value="{{ field.displayedData }}" />
{% endspaceless %}
{% endblock hidden_field %}
{% block textarea_field %}
{% spaceless %}
<textarea {{ block('field_attributes') }}>{{ field.displayedData }}</textarea>
{% endspaceless %}
{% endblock textarea_field %}
{% block options %}
{% spaceless %}
{% for choice, label in options %}
{% if field.isChoiceGroup(label) %}
<optgroup label="{{ choice }}">
{% for nestedChoice, nestedLabel in label %}
<option value="{{ nestedChoice }}"{% if field.isChoiceSelected(nestedChoice) %} selected="selected"{% endif %}>{{ nestedLabel }}</option>
{% endfor %}
</optgroup>
{% else %}
<option value="{{ choice }}"{% if field.isChoiceSelected(choice) %} selected="selected"{% endif %}>{{ label }}</option>
{% endif %}
{% endfor %}
{% endspaceless %}
{% endblock options %}
{% block choice_field %}
{% spaceless %}
{% if field.isExpanded %}
{% for choice, child in field %}
{{ form_field(child) }}
<label for="{{ child.id }}">{{ field.label(choice) }}</label>
{% endfor %}
{% else %}
<select {{ block('field_attributes') }}{% if field.isMultipleChoice %} multiple="multiple"{% endif %}>
{% if field.preferredChoices|length > 0 %}
{% set options = field.preferredChoices %}
{{ block('options') }}
<option disabled="disabled">{{ params.separator|default('-------------------') }}</option>
{% endif %}
{% set options = field.otherChoices %}
{{ block('options') }}
</select>
{% endif %}
{% endspaceless %}
{% endblock choice_field %}
{% block checkbox_field %}
{% spaceless %}
<input type="checkbox" {{ block('field_attributes') }}{% if field.hasValue %} value="{{ field.value }}"{% endif %}{% if field.ischecked %} checked="checked"{% endif %} />
{% endspaceless %}
{% endblock checkbox_field %}
{% block radio_field %}
{% spaceless %}
<input type="radio" {{ block('field_attributes') }}{% if field.hasValue %} value="{{ field.value }}"{% endif %}{% if field.ischecked %} checked="checked"{% endif %} />
{% endspaceless %}
{% endblock radio_field %}
{% block date_time_field %}
{% spaceless %}
{{ form_errors(field.date) }}
{{ form_errors(field.time) }}
{{ form_field(field.date) }}
{{ form_field(field.time) }}
{% endspaceless %}
{% endblock date_time_field %}
{% block date_field %}
{% spaceless %}
{% if field.isField %}
{{ block('text_field') }}
{% else %}
{{ field.pattern|replace({ '{{ year }}': form_field(field.year), '{{ month }}': form_field(field.month), '{{ day }}': form_field(field.day) })|raw }}
{% endif %}
{% endspaceless %}
{% endblock date_field %}
{% block time_field %}
{% spaceless %}
{% if field.isField %}{% set attr = attr|merge({ 'size': 1 }) %}{% endif %}
{{ form_field(field.hour, attr) }}:{{ form_field(field.minute, attr) }}{% if field.isWithSeconds %}:{{ form_field(field.second, attr) }}{% endif %}
{% endspaceless %}
{% endblock time_field %}
{% block number_field %}
{% spaceless %}
{% set attr = attr|merge({ 'type': 'number' }) %}
{{ block('text_field') }}
{% endspaceless %}
{% endblock number_field %}
{% block money_field %}
{% spaceless %}
{{ field.pattern|replace({ '{{ widget }}': block('number_field') })|raw }}
{% endspaceless %}
{% endblock money_field %}
{% block url_field %}
{% spaceless %}
{% set attr = attr|merge({ 'type': 'url' }) %}
{{ block('text_field') }}
{% endspaceless %}
{% endblock url_field %}
{% block percent_field %}
{% spaceless %}
{{ block('text_field') }} %
{% endspaceless %}
{% endblock percent_field %}
{% block file_field %}
{% spaceless %}
{% set group = field %}
{% set field = group.file %}
<input type="file" {{ block('field_attributes') }} />
{{ form_field(group.token) }}
{{ form_field(group.original_name) }}
{% endspaceless %}
{% endblock file_field %}

View File

@ -35,7 +35,7 @@ class TwigExtensionTest extends TestCase
$this->assertEquals('Twig_Environment', $container->getParameter('twig.class'), '->load() loads the twig.xml file');
$this->assertFalse($container->getDefinition('twig.cache_warmer')->hasTag('kernel.cache_warmer'), '->load() does not enable cache warming by default');
$this->assertContains('TwigBundle::form.html.twig', $container->getParameter('twig.form.resources'), '->load() includes default template for form resources');
$this->assertContains('TwigBundle:Form:div_layout.html.twig', $container->getParameter('twig.form.resources'), '->load() includes default template for form resources');
// Twig options
$options = $container->getParameter('twig.options');
@ -65,7 +65,7 @@ class TwigExtensionTest extends TestCase
// Form resources
$resources = $container->getParameter('twig.form.resources');
$this->assertContains('TwigBundle::form.html.twig', $resources, '->load() includes default template for form resources');
$this->assertContains('TwigBundle:Form:div_layout.html.twig', $resources, '->load() includes default template for form resources');
$this->assertContains('MyBundle::form.html.twig', $resources, '->load() merges new templates into form resources');
// Globals

View File

@ -1,35 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form;
/**
* A field for entering a birthday date
*
* This field is a preconfigured DateField with allowed years between the
* current year and 120 years in the past.
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
class BirthdayField extends DateField
{
/**
* {@inheritDoc}
*/
protected function configure()
{
$currentYear = date('Y');
$this->addOption('years', range($currentYear-120, $currentYear));
parent::configure();
}
}

View File

@ -1,33 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form;
/**
* A checkbox field for selecting boolean values.
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
class CheckboxField extends ToggleField
{
/**
* Available options:
*
* * value: The value of the input checkbox. If the checkbox is checked,
* this value will be posted as the value of the field.
*/
protected function configure()
{
$this->addOption('value', '1');
parent::configure();
}
}

View File

@ -1,294 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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\InvalidOptionsException;
/**
* Lets the user select between different choices.
*
* Available options:
*
* * choices: An array of key-value pairs that will represent the choices
* * preferred_choices: An array of choices (by key) that should be displayed
* above all other options in the field
* * empty_value: If set to a non-false value, an "empty" option will
* be added to the top of the countries choices. A
* common value might be "Choose a country". Default: false.
*
* The multiple and expanded options control exactly which HTML element
* that should be used to render this field:
*
* * expanded = false, multiple = false A drop-down select element
* * expanded = false, multiple = true A multiple select element
* * expanded = true, multiple = false A series of input radio elements
* * expanded = true, multiple = true A series of input checkboxes
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
class ChoiceField extends HybridField
{
/**
* Stores the preferred choices with the choices as keys
* @var array
*/
protected $preferredChoices = array();
/**
* Stores the choices
* You should only access this property through getChoices()
* @var array
*/
private $choices = array();
protected function configure()
{
$this->addRequiredOption('choices');
$this->addOption('preferred_choices', array());
$this->addOption('multiple', false);
$this->addOption('expanded', false);
$this->addOption('empty_value', '');
parent::configure();
$choices = $this->getOption('choices');
if (!is_array($choices) && !$choices instanceof \Closure) {
throw new InvalidOptionsException('The choices option must be an array or a closure', array('choices'));
}
if (!is_array($this->getOption('preferred_choices'))) {
throw new InvalidOptionsException('The preferred_choices option must be an array', array('preferred_choices'));
}
if (count($this->getOption('preferred_choices')) > 0) {
$this->preferredChoices = array_flip($this->getOption('preferred_choices'));
}
if ($this->isExpanded()) {
$this->setFieldMode(self::FORM);
$choices = $this->getChoices();
foreach ($this->preferredChoices as $choice => $_) {
$this->add($this->newChoiceField($choice, $choices[$choice]));
}
foreach ($choices as $choice => $value) {
if (!isset($this->preferredChoices[$choice])) {
$this->add($this->newChoiceField($choice, $value));
}
}
} else {
$this->setFieldMode(self::FIELD);
}
}
public function getName()
{
// TESTME
$name = parent::getName();
// Add "[]" to the name in case a select tag with multiple options is
// displayed. Otherwise only one of the selected options is sent in the
// POST request.
if ($this->isMultipleChoice() && !$this->isExpanded()) {
$name .= '[]';
}
return $name;
}
/**
* Initializes the choices
*
* If the choices were given as a closure, the closure is executed now.
*
* @return array
*/
protected function initializeChoices()
{
if (!$this->choices) {
$this->choices = $this->getInitializedChoices();
if (!$this->isRequired()) {
$this->choices = array('' => $this->getOption('empty_value')) + $this->choices;
}
}
}
protected function getInitializedChoices()
{
$choices = $this->getOption('choices');
if ($choices instanceof \Closure) {
$choices = $choices->__invoke();
}
if (!is_array($choices)) {
throw new InvalidOptionsException('The "choices" option must be an array or a closure returning an array', array('choices'));
}
return $choices;
}
/**
* Returns the choices
*
* If the choices were given as a closure, the closure is executed on
* the first call of this method.
*
* @return array
*/
protected function getChoices()
{
$this->initializeChoices();
return $this->choices;
}
public function getPreferredChoices()
{
return array_intersect_key($this->getChoices(), $this->preferredChoices);
}
public function getOtherChoices()
{
return array_diff_key($this->getChoices(), $this->preferredChoices);
}
public function getLabel($choice)
{
$choices = $this->getChoices();
return isset($choices[$choice]) ? $choices[$choice] : null;
}
public function isChoiceGroup($choice)
{
return is_array($choice) || $choice instanceof \Traversable;
}
public function isChoiceSelected($choice)
{
return in_array((string) $choice, (array) $this->getDisplayedData(), true);
}
public function isMultipleChoice()
{
return $this->getOption('multiple');
}
public function isExpanded()
{
return $this->getOption('expanded');
}
/**
* Returns a new field of type radio button or checkbox.
*
* @param string $choice The key for the option
* @param string $label The label for the option
*/
protected function newChoiceField($choice, $label)
{
if ($this->isMultipleChoice()) {
return new CheckboxField($choice, array(
'value' => $choice,
));
}
return new RadioField($choice, array(
'value' => $choice,
));
}
/**
* {@inheritDoc}
*
* Takes care of converting the input from a single radio button
* to an array.
*/
public function submit($value)
{
if (!$this->isMultipleChoice() && $this->isExpanded()) {
$value = null === $value ? array() : array($value => true);
}
parent::submit($value);
}
/**
* Transforms a single choice or an array of choices to a format appropriate
* for the nested checkboxes/radio buttons.
*
* The result is an array with the options as keys and true/false as values,
* depending on whether a given option is selected. If this field is rendered
* as select tag, the value is not modified.
*
* @param mixed $value An array if "multiple" is set to true, a scalar
* value otherwise.
* @return mixed An array if "expanded" or "multiple" is set to true,
* a scalar value otherwise.
*/
protected function transform($value)
{
if ($this->isExpanded()) {
$value = parent::transform($value);
$choices = $this->getChoices();
foreach ($choices as $choice => $_) {
$choices[$choice] = $this->isMultipleChoice()
? in_array($choice, (array)$value, true)
: ($choice === $value);
}
return $choices;
}
return parent::transform($value);
}
/**
* Transforms a checkbox/radio button array to a single choice or an array
* of choices.
*
* The input value is an array with the choices as keys and true/false as
* values, depending on whether a given choice is selected. The output
* is an array with the selected choices or a single selected choice.
*
* @param mixed $value An array if "expanded" or "multiple" is set to true,
* a scalar value otherwise.
* @return mixed $value An array if "multiple" is set to true, a scalar
* value otherwise.
*/
protected function reverseTransform($value)
{
if ($this->isExpanded()) {
$choices = array();
foreach ($value as $choice => $selected) {
if ($selected) {
$choices[] = $choice;
}
}
if ($this->isMultipleChoice()) {
$value = $choices;
} else {
$value = count($choices) > 0 ? current($choices) : null;
}
}
return parent::reverseTransform($value);
}
}

View File

@ -0,0 +1,55 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ChoiceList;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
class ArrayChoiceList implements ChoiceListInterface
{
protected $choices;
protected $loaded = false;
public function __construct($choices)
{
if (!is_array($choices) && !$choices instanceof \Closure) {
throw new UnexpectedTypeException($choices, 'array or \Closure');
}
$this->choices = $choices;
}
public function getChoices()
{
if (!$this->loaded) {
$this->load();
}
return $this->choices;
}
/**
* @see Symfony\Component\Form\ChoiceList\ChoiceListInterface::getChoices
*/
protected function load()
{
$this->loaded = true;
if ($this->choices instanceof \Closure) {
$this->choices = $this->choices->__invoke();
if (!is_array($this->choices)) {
throw new UnexpectedTypeException($this->choices, 'array');
}
}
}
}

View File

@ -0,0 +1,17 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ChoiceList;
interface ChoiceListInterface
{
function getChoices();
}

View File

@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ChoiceList;
class MonthChoiceList extends PaddedChoiceList
{
private $formatter;
/**
* Generates an array of localized month choices
*
* @param array $months The month numbers to generate
* @return array The localized months respecting the configured
* locale and date format
*/
public function __construct(\IntlDateFormatter $formatter, array $months)
{
parent::__construct($months, 2, '0', STR_PAD_LEFT);
$this->formatter = $formatter;
}
protected function load()
{
parent::load();
$pattern = $this->formatter->getPattern();
$timezone = $this->formatter->getTimezoneId();
$this->formatter->setTimezoneId(\DateTimeZone::UTC);
if (preg_match('/M+/', $pattern, $matches)) {
$this->formatter->setPattern($matches[0]);
foreach ($this->choices as $choice => $value) {
// It's important to specify the first day of the month here!
$this->choices[$choice] = $this->formatter->format(gmmktime(0, 0, 0, $choice, 1));
}
// I'd like to clone the formatter above, but then we get a
// segmentation fault, so let's restore the old state instead
$this->formatter->setPattern($pattern);
}
$this->formatter->setTimezoneId($timezone);
}
}

View File

@ -0,0 +1,53 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ChoiceList;
class PaddedChoiceList extends ArrayChoiceList
{
private $padLength;
private $padString;
private $padType;
/**
* Generates an array of choices for the given values
*
* If the values are shorter than $padLength characters, they are padded with
* zeros on the left side.
*
* @param array $values The available choices
* @param integer $padLength The length to pad the choices
* @return array An array with the input values as keys and the
* padded values as values
*/
public function __construct($values, $padLength, $padString, $padType = STR_PAD_LEFT)
{
parent::__construct($values);
$this->padLength = $padLength;
$this->padString = $padString;
$this->padType = $padType;
}
protected function load()
{
parent::load();
$choices = $this->choices;
$this->choices = array();
foreach ($choices as $value) {
$this->choices[$value] = str_pad($value, $this->padLength, $this->padString, $this->padType);
}
}
}

View File

@ -9,14 +9,14 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form;
namespace Symfony\Component\Form\ChoiceList;
/**
* Represents a field where each timezone is broken down by continent.
* Represents a choice list where each timezone is broken down by continent.
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
class TimezoneField extends ChoiceField
class TimezoneChoiceList extends ArrayChoiceList
{
/**
* Stores the available timezone choices
@ -24,34 +24,13 @@ class TimezoneField extends ChoiceField
*/
protected static $timezones = array();
/**
* {@inheritDoc}
*/
public function configure()
public function __construct()
{
$this->addOption('choices', self::getTimezoneChoices());
parent::configure();
parent::__construct(array());
}
/**
* Preselects the server timezone if the field is empty and required
*
* {@inheritDoc}
*/
public function getDisplayedData()
{
$data = parent::getDisplayedData();
if (null == $data && $this->isRequired()) {
$data = date_default_timezone_get();
}
return $data;
}
/**
* Returns the timezone choices
* Loads the timezone choices
*
* The choices are generated from the ICU function
* \DateTimeZone::listIdentifiers(). They are cached during a single request,
@ -60,8 +39,10 @@ class TimezoneField extends ChoiceField
*
* @return array The timezone choices
*/
protected static function getTimezoneChoices()
protected function load()
{
parent::load();
if (count(self::$timezones) == 0) {
foreach (\DateTimeZone::listIdentifiers() as $timezone) {
$parts = explode('/', $timezone);
@ -85,6 +66,6 @@ class TimezoneField extends ChoiceField
}
}
return self::$timezones;
$this->choices = self::$timezones;
}
}

View File

@ -1,148 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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\FieldInterface;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
/**
* A field group that repeats the given field multiple times over a collection
* specified by the property path if the field.
*
* Example usage:
*
* $form->add(new CollectionField(new TextField('emails')));
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
class CollectionField extends Form
{
/**
* Remembers which fields were removed upon submitting
* @var array
*/
protected $removedFields = array();
/**
* The prototype field for the collection rows
* @var FieldInterface
*/
protected $prototype;
public function __construct($key, array $options = array())
{
// This doesn't work with addOption(), because the value of this option
// needs to be accessed before Configurable::__construct() is reached
// Setting all options in the constructor of the root field
// is conceptually flawed
if (isset($options['prototype'])) {
$this->prototype = $options['prototype'];
unset($options['prototype']);
}
parent::__construct($key, $options);
}
/**
* Available options:
*
* * modifiable: If true, elements in the collection can be added
* and removed by the presence of absence of the
* corresponding field groups. Field groups could be
* added or removed via Javascript and reflected in
* the underlying collection. Default: false.
*/
protected function configure()
{
$this->addOption('modifiable', false);
if ($this->getOption('modifiable')) {
$field = $this->newField('$$key$$', null);
// TESTME
$field->setRequired(false);
$this->add($field);
}
parent::configure();
}
public function setData($collection)
{
if (!is_array($collection) && !$collection instanceof \Traversable) {
throw new UnexpectedTypeException($collection, 'array or \Traversable');
}
foreach ($this as $name => $field) {
if (!$this->getOption('modifiable') || '$$key$$' != $name) {
$this->remove($name);
}
}
foreach ($collection as $name => $value) {
$this->add($this->newField($name, $name));
}
parent::setData($collection);
}
public function submit($data)
{
$this->removedFields = array();
if (null === $data) {
$data = array();
}
foreach ($this as $name => $field) {
if (!isset($data[$name]) && $this->getOption('modifiable') && '$$key$$' != $name) {
$this->remove($name);
$this->removedFields[] = $name;
}
}
foreach ($data as $name => $value) {
if (!isset($this[$name]) && $this->getOption('modifiable')) {
$this->add($this->newField($name, $name));
}
}
parent::submit($data);
}
protected function writeObject(&$objectOrArray)
{
parent::writeObject($objectOrArray);
foreach ($this->removedFields as $name) {
unset($objectOrArray[$name]);
}
}
protected function newField($key, $propertyPath)
{
if (null !== $propertyPath) {
$propertyPath = '['.$propertyPath.']';
}
if ($this->prototype) {
$field = clone $this->prototype;
$field->setKey($key);
$field->setPropertyPath($propertyPath);
} else {
$field = new TextField($key, array(
'property_path' => $propertyPath,
));
}
return $field;
}
}

View File

@ -1,145 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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\MissingOptionsException;
use Symfony\Component\Form\Exception\InvalidOptionsException;
/**
* A class configurable via options
*
* Options can be passed to the constructor of this class. After constructions,
* these options cannot be changed anymore. This way, options remain light
* weight. There is no need to monitor changes of options.
*
* If you want options that can change, you're recommended to implement plain
* properties with setters and getters.
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
abstract class Configurable
{
/**
* The options and their values
* @var array
*/
private $options = array();
/**
* The names of the valid options
* @var array
*/
private $knownOptions = array();
/**
* The names of the required options
* @var array
*/
private $requiredOptions = array();
/**
* Reads, validates and stores the given options
*
* @param array $options
*/
public function __construct(array $options = array())
{
$this->options = array_merge($this->options, $options);
$this->configure();
// check option names
if ($diff = array_diff_key($this->options, $this->knownOptions)) {
throw new InvalidOptionsException(sprintf('%s does not support the following options: "%s".', get_class($this), implode('", "', array_keys($diff))), array_keys($diff));
}
// check required options
if ($diff = array_diff_key($this->requiredOptions, $this->options)) {
throw new MissingOptionsException(sprintf('%s requires the following options: \'%s\'.', get_class($this), implode('", "', array_keys($diff))), array_keys($diff));
}
}
/**
* Configures the valid options
*
* This method should call addOption() or addRequiredOption() for every
* accepted option.
*/
protected function configure()
{
}
/**
* Returns an option value.
*
* @param string $name The option name
*
* @return mixed The option value
*/
public function getOption($name)
{
return isset($this->options[$name]) ? $this->options[$name] : null;
}
/**
* Adds a new option value with a default value.
*
* @param string $name The option name
* @param mixed $value The default value
*/
protected function addOption($name, $value = null, array $allowedValues = array())
{
$this->knownOptions[$name] = true;
if (!array_key_exists($name, $this->options)) {
$this->options[$name] = $value;
}
if (count($allowedValues) > 0 && !in_array($this->options[$name], $allowedValues)) {
throw new InvalidOptionsException(sprintf('The option "%s" is expected to be one of "%s", but is "%s"', $name, implode('", "', $allowedValues), $this->options[$name]), array($name));
}
}
/**
* Adds a required option.
*
* @param string $name The option name
*/
protected function addRequiredOption($name, array $allowedValues = array())
{
$this->knownOptions[$name] = true;
$this->requiredOptions[$name] = true;
// only test if the option is set, otherwise an error will be thrown
// anyway
if (isset($this->options[$name]) && count($allowedValues) > 0 && !in_array($this->options[$name], $allowedValues)) {
throw new InvalidOptionsException(sprintf('The option "%s" is expected to be one of "%s", but is "%s"', $name, implode('", "', $allowedValues), $this->options[$name]), array($name));
}
}
/**
* Returns true if the option exists.
*
* @param string $name The option name
*
* @return Boolean true if the option is set, false otherwise
*/
public function hasOption($name)
{
return isset($this->options[$name]);
}
public function getOptions()
{
return $this->options;
}
}

View File

@ -1,30 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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\Locale\Locale;
/**
* A field for selecting from a list of countries.
*
* @see Symfony\Component\Form\ChoiceField
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
class CountryField extends ChoiceField
{
protected function configure()
{
$this->addOption('choices', Locale::getDisplayCountries(\Locale::getDefault()));
parent::configure();
}
}

View File

@ -22,7 +22,7 @@ namespace Symfony\Component\Form\CsrfProvider;
*
* If you want to secure a form submission against CSRF attacks, you could
* use the class name of the form as page ID. This way you make sure that the
* form can only be submitted to pages that are designed to handle the form,
* form can only be bound to pages that are designed to handle the form,
* that is, that use the same class name to validate the CSRF token with
* isCsrfTokenValid().
*

View File

@ -1,21 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form;
/**
* Wraps errors in the form data
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
class DataError extends Error
{
}

View File

@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\DataMapper;
use Symfony\Component\Form\FormInterface;
interface DataMapperInterface
{
function mapDataToForms($data, array $forms);
function mapDataToForm($data, FormInterface $form);
function mapFormsToData(array $forms, &$data);
function mapFormToData(FormInterface $form, &$data);
}

View File

@ -0,0 +1,85 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\DataMapper;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\Util\VirtualFormAwareIterator;
use Symfony\Component\Form\Exception\FormException;
class PropertyPathMapper implements DataMapperInterface
{
/**
* Stores the class that the data of this form must be instances of
* @var string
*/
private $dataClass;
public function __construct($dataClass = null)
{
$this->dataClass = $dataClass;
}
public function mapDataToForms($data, array $forms)
{
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)) {
if ($this->dataClass && !$data instanceof $this->dataClass) {
throw new FormException(sprintf('Form data should be instance of %s', $this->dataClass));
}
$iterator = new VirtualFormAwareIterator($forms);
$iterator = new \RecursiveIteratorIterator($iterator);
foreach ($iterator as $form) {
$this->mapDataToForm($data, $form);
}
}
}
public function mapDataToForm($data, FormInterface $form)
{
if (!empty($data)) {
if ($form->getAttribute('property_path') !== null) {
$form->setData($form->getAttribute('property_path')->getValue($data));
}
}
}
public function mapFormsToData(array $forms, &$data)
{
$iterator = new VirtualFormAwareIterator($forms);
$iterator = new \RecursiveIteratorIterator($iterator);
foreach ($iterator as $form) {
$this->mapFormToData($form, $data);
}
}
public function mapFormToData(FormInterface $form, &$data)
{
if ($form->getAttribute('property_path') !== null && $form->isSynchronized()) {
$propertyPath = $form->getAttribute('property_path');
// If the data is identical to the value in $data, we are
// dealing with a reference
$isReference = $form->getData() === $propertyPath->getValue($data);
$byReference = $form->getAttribute('by_reference');
if (!(is_object($data) && $isReference && $byReference)) {
$propertyPath->setValue($data, $form->getData());
}
}
}
}

View File

@ -0,0 +1,87 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\DataTransformer;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
class ArrayToBooleanChoicesTransformer implements DataTransformerInterface
{
private $choiceList;
public function __construct(ChoiceListInterface $choiceList)
{
$this->choiceList = $choiceList;
}
/**
* Transforms a single choice or an array of choices to a format appropriate
* for the nested checkboxes/radio buttons.
*
* The result is an array with the options as keys and true/false as values,
* depending on whether a given option is selected. If this field is rendered
* as select tag, the value is not modified.
*
* @param mixed $value An array if "multiple" is set to true, a scalar
* value otherwise.
* @return mixed An array if "expanded" or "multiple" is set to true,
* a scalar value otherwise.
*/
public function transform($array)
{
if (null === $array) {
return array();
}
if (!is_array($array)) {
throw new UnexpectedTypeException($array, 'array');
}
$choices = $this->choiceList->getChoices();
foreach ($choices as $choice => $_) {
$choices[$choice] = in_array($choice, $array, true);
}
return $choices;
}
/**
* Transforms a checkbox/radio button array to a single choice or an array
* of choices.
*
* The input value is an array with the choices as keys and true/false as
* values, depending on whether a given choice is selected. The output
* is an array with the selected choices or a single selected choice.
*
* @param mixed $value An array if "expanded" or "multiple" is set to true,
* a scalar value otherwise.
* @return mixed $value An array if "multiple" is set to true, a scalar
* value otherwise.
*/
public function reverseTransform($value)
{
if (!is_array($value)) {
throw new UnexpectedTypeException($value, 'array');
}
$choices = array();
foreach ($value as $choice => $selected) {
if ($selected) {
$choices[] = $choice;
}
}
return $choices;
}
}

View File

@ -0,0 +1,44 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\DataTransformer;
use Symfony\Component\Form\Util\FormUtil;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
class ArrayToChoicesTransformer implements DataTransformerInterface
{
public function transform($array)
{
if (null === $array) {
return array();
}
if (!is_array($array)) {
throw new UnexpectedTypeException($array, 'array');
}
return FormUtil::toArrayKeys($array);
}
public function reverseTransform($array)
{
if (null === $array) {
return array();
}
if (!is_array($array)) {
throw new UnexpectedTypeException($array, 'array');
}
return $array;
}
}

View File

@ -0,0 +1,84 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\DataTransformer;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
/**
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
*/
class ArrayToPartsTransformer implements DataTransformerInterface
{
private $partMapping;
public function __construct(array $partMapping)
{
$this->partMapping = $partMapping;
}
public function transform($array)
{
if (null === $array) {
$array = array();
}
if (!is_array($array) ) {
throw new UnexpectedTypeException($array, 'array');
}
$result = array();
foreach ($this->partMapping as $partKey => $originalKeys) {
if (empty($array)) {
$result[$partKey] = null;
} else {
$result[$partKey] = array_intersect_key($array, array_flip($originalKeys));
}
}
return $result;
}
public function reverseTransform($array)
{
if (!is_array($array) ) {
throw new UnexpectedTypeException($array, 'array');
}
$result = array();
$emptyKeys = array();
foreach ($this->partMapping as $partKey => $originalKeys) {
if (!empty($array[$partKey])) {
foreach ($originalKeys as $originalKey) {
if (isset($array[$partKey][$originalKey])) {
$result[$originalKey] = $array[$partKey][$originalKey];
}
}
} else {
$emptyKeys[] = $partKey;
}
}
if (count($emptyKeys) > 0) {
if (count($emptyKeys) === count($this->partMapping)) {
// All parts empty
return null;
}
throw new TransformationFailedException(sprintf(
'The keys "%s" should not be empty', implode('", "', $emptyKeys)));
}
return $result;
}
}

View File

@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\DataTransformer;
abstract class BaseDateTimeTransformer implements DataTransformerInterface
{
protected static $formats = array(
\IntlDateFormatter::NONE,
\IntlDateFormatter::FULL,
\IntlDateFormatter::LONG,
\IntlDateFormatter::MEDIUM,
\IntlDateFormatter::SHORT,
);
protected $inputTimezone;
protected $outputTimezone;
public function __construct($inputTimezone = null, $outputTimezone = null)
{
$this->inputTimezone = $inputTimezone ?: date_default_timezone_get();
$this->outputTimezone = $outputTimezone ?: date_default_timezone_get();
}
}

View File

@ -9,9 +9,8 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ValueTransformer;
namespace Symfony\Component\Form\DataTransformer;
use Symfony\Component\Form\Configurable;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
/**
@ -20,7 +19,7 @@ use Symfony\Component\Form\Exception\UnexpectedTypeException;
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
* @author Florian Eckerstorfer <florian@eckerstorfer.org>
*/
class BooleanToStringTransformer extends Configurable implements ValueTransformerInterface
class BooleanToStringTransformer implements DataTransformerInterface
{
/**
* Transforms a Boolean into a string.
@ -49,6 +48,10 @@ class BooleanToStringTransformer extends Configurable implements ValueTransforme
*/
public function reverseTransform($value)
{
if (null === $value) {
return false;
}
if (!is_string($value)) {
throw new UnexpectedTypeException($value, 'string');
}

View File

@ -0,0 +1,35 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\DataTransformer;
class CallbackTransformer implements DataTransformerInterface
{
private $transform;
private $reverseTransform;
public function __construct(\Closure $transform, \Closure $reverseTransform)
{
$this->transform = $transform;
$this->reverseTransform = $reverseTransform;
}
public function transform($data)
{
return $this->transform->__invoke($data);
}
public function reverseTransform($data)
{
return $this->reverseTransform->__invoke($data);
}
}

View File

@ -9,14 +9,14 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ValueTransformer;
namespace Symfony\Component\Form\DataTransformer;
/**
* Passes a value through multiple value transformers
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
class ValueTransformerChain implements ValueTransformerInterface
class DataTransformerChain implements DataTransformerInterface
{
/**
* The value transformers

View File

@ -9,14 +9,14 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ValueTransformer;
namespace Symfony\Component\Form\DataTransformer;
/**
* Transforms a value between different representations.
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
interface ValueTransformerInterface
interface DataTransformerInterface
{
/**
* Transforms a value from the original representation to a transformed representation.
@ -24,8 +24,8 @@ interface ValueTransformerInterface
* This method is called on two occasions inside a form field:
*
* 1. When the form field is initialized with the data attached from the datasource (object or array).
* 2. When data from a request is bound using {@link Field::submit()} to transform the new input data
* back into the renderable format. For example if you have a date field and submit '2009-10-10' onto
* 2. When data from a request is bound using {@link Field::bind()} to transform the new input data
* back into the renderable format. For example if you have a date field and bind '2009-10-10' onto
* it you might accept this value because its easily parsed, but the transformer still writes back
* "2009/10/10" onto the form field (for further displaying or other purposes).
*
@ -42,7 +42,7 @@ interface ValueTransformerInterface
* @param mixed $value The value in the original representation
* @return mixed The value in the transformed representation
* @throws UnexpectedTypeException when the argument is no string
* @throws ValueTransformerException when the transformation fails
* @throws DataTransformerException when the transformation fails
*/
function transform($value);
@ -50,7 +50,7 @@ interface ValueTransformerInterface
* Transforms a value from the transformed representation to its original
* representation.
*
* This method is called when {@link Field::submit()} is called to transform the requests tainted data
* This method is called when {@link Field::bind()} is called to transform the requests tainted data
* into an acceptable format for your data processing/model layer.
*
* This method must be able to deal with empty values. Usually this will
@ -67,7 +67,7 @@ interface ValueTransformerInterface
* @param mixed $value The value in the transformed representation
* @throws UnexpectedTypeException when the argument is not of the
* expected type
* @throws ValueTransformerException when the transformation fails
* @throws DataTransformerException when the transformation fails
*/
function reverseTransform($value);
}

View File

@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ValueTransformer;
namespace Symfony\Component\Form\DataTransformer;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
@ -27,17 +27,20 @@ use Symfony\Component\Form\Exception\UnexpectedTypeException;
*/
class DateTimeToArrayTransformer extends BaseDateTimeTransformer
{
/**
* {@inheritDoc}
*/
protected function configure()
{
$this->addOption('input_timezone', date_default_timezone_get());
$this->addOption('output_timezone', date_default_timezone_get());
$this->addOption('pad', false);
$this->addOption('fields', array('year', 'month', 'day', 'hour', 'minute', 'second'));
private $pad;
parent::configure();
private $fields;
public function __construct($inputTimezone = null, $outputTimezone = null, $fields = null, $pad = false)
{
parent::__construct($inputTimezone, $outputTimezone);
if (is_null($fields)) {
$fields = array('year', 'month', 'day', 'hour', 'minute', 'second');
}
$this->fields = $fields;
$this->pad =$pad;
}
/**
@ -49,22 +52,22 @@ class DateTimeToArrayTransformer extends BaseDateTimeTransformer
public function transform($dateTime)
{
if (null === $dateTime) {
return array(
return array_intersect_key(array(
'year' => '',
'month' => '',
'day' => '',
'hour' => '',
'minute' => '',
'second' => '',
);
), array_flip($this->fields));
}
if (!$dateTime instanceof \DateTime) {
throw new UnexpectedTypeException($dateTime, '\DateTime');
}
$inputTimezone = $this->getOption('input_timezone');
$outputTimezone = $this->getOption('output_timezone');
$inputTimezone = $this->inputTimezone;
$outputTimezone = $this->outputTimezone;
if ($inputTimezone != $outputTimezone) {
$dateTime->setTimezone(new \DateTimeZone($outputTimezone));
@ -77,9 +80,9 @@ class DateTimeToArrayTransformer extends BaseDateTimeTransformer
'hour' => $dateTime->format('H'),
'minute' => $dateTime->format('i'),
'second' => $dateTime->format('s'),
), array_flip($this->getOption('fields')));
), array_flip($this->fields));
if (!$this->getOption('pad')) {
if (!$this->pad) {
foreach ($result as &$entry) {
// remove leading zeros
$entry = (string)(int)$entry;
@ -101,8 +104,8 @@ class DateTimeToArrayTransformer extends BaseDateTimeTransformer
return null;
}
$inputTimezone = $this->getOption('input_timezone');
$outputTimezone = $this->getOption('output_timezone');
$inputTimezone = $this->inputTimezone;
$outputTimezone = $this->outputTimezone;
if (!is_array($value)) {
throw new UnexpectedTypeException($value, 'array');
@ -112,6 +115,19 @@ class DateTimeToArrayTransformer extends BaseDateTimeTransformer
return null;
}
$emptyFields = array();
foreach ($this->fields as $field) {
if (!isset($value[$field])) {
$emptyFields[] = $field;
}
}
if (count($emptyFields) > 0) {
throw new TransformationFailedException(sprintf(
'The fields "%s" should not be empty', implode('", "', $emptyFields)));
}
try {
$dateTime = new \DateTime(sprintf(
'%s-%s-%s %s:%s:%s %s',

View File

@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ValueTransformer;
namespace Symfony\Component\Form\DataTransformer;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
@ -27,25 +27,32 @@ use Symfony\Component\Form\Exception\UnexpectedTypeException;
*/
class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
{
/**
* {@inheritDoc}
*/
protected function configure()
private $dateFormat;
private $timeFormat;
public function __construct($inputTimezone = null, $outputTimezone = null, $dateFormat = null, $timeFormat = null)
{
$this->addOption('date_format', self::MEDIUM);
$this->addOption('time_format', self::SHORT);
$this->addOption('input_timezone', 'UTC');
$this->addOption('output_timezone', 'UTC');
parent::__construct($inputTimezone, $outputTimezone);
if (!in_array($this->getOption('date_format'), self::$formats, true)) {
throw new \InvalidArgumentException(sprintf('The option "date_format" is expected to be one of "%s". Is "%s"', implode('", "', self::$formats), $this->getOption('time_format')));
if (is_null($dateFormat)) {
$dateFormat = \IntlDateFormatter::MEDIUM;
}
if (!in_array($this->getOption('time_format'), self::$formats, true)) {
throw new \InvalidArgumentException(sprintf('The option "time_format" is expected to be one of "%s". Is "%s"', implode('", "', self::$formats), $this->getOption('time_format')));
if (is_null($timeFormat)) {
$timeFormat = \IntlDateFormatter::SHORT;
}
parent::configure();
if (!in_array($dateFormat, self::$formats, true)) {
throw new \InvalidArgumentException(sprintf('The value $dateFormat is expected to be one of "%s". Is "%s"', implode('", "', self::$formats), $dateFormat));
}
if (!in_array($timeFormat, self::$formats, true)) {
throw new \InvalidArgumentException(sprintf('The value $timeFormat is expected to be one of "%s". Is "%s"', implode('", "', self::$formats), $timeFormat));
}
$this->dateFormat = $dateFormat;
$this->timeFormat = $timeFormat;
}
/**
@ -64,7 +71,7 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
throw new UnexpectedTypeException($dateTime, '\DateTime');
}
$inputTimezone = $this->getOption('input_timezone');
$inputTimezone = $this->inputTimezone;
// convert time to UTC before passing it to the formatter
if ('UTC' != $inputTimezone) {
@ -88,7 +95,7 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
*/
public function reverseTransform($value)
{
$inputTimezone = $this->getOption('input_timezone');
$inputTimezone = $this->inputTimezone;
if (!is_string($value)) {
throw new UnexpectedTypeException($value, 'string');
@ -121,9 +128,9 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
*/
protected function getIntlDateFormatter()
{
$dateFormat = $this->getIntlFormatConstant($this->getOption('date_format'));
$timeFormat = $this->getIntlFormatConstant($this->getOption('time_format'));
$timezone = $this->getOption('output_timezone');
$dateFormat = $this->dateFormat;
$timeFormat = $this->timeFormat;
$timezone = $this->outputTimezone;
return new \IntlDateFormatter(\Locale::getDefault(), $dateFormat, $timeFormat, $timezone);
}

View File

@ -9,9 +9,8 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ValueTransformer;
namespace Symfony\Component\Form\DataTransformer;
use Symfony\Component\Form\Configurable;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
/**
@ -20,18 +19,15 @@ use Symfony\Component\Form\Exception\UnexpectedTypeException;
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
* @author Florian Eckerstorfer <florian@eckerstorfer.org>
*/
class DateTimeToStringTransformer extends Configurable implements ValueTransformerInterface
class DateTimeToStringTransformer extends BaseDateTimeTransformer
{
/**
* {@inheritDoc}
*/
protected function configure()
{
$this->addOption('input_timezone', date_default_timezone_get());
$this->addOption('output_timezone', date_default_timezone_get());
$this->addOption('format', 'Y-m-d H:i:s');
private $format;
parent::configure();
public function __construct($inputTimezone = null, $outputTimezone = null, $format = 'Y-m-d H:i:s')
{
parent::__construct($inputTimezone, $outputTimezone);
$this->format = $format;
}
/**
@ -51,9 +47,9 @@ class DateTimeToStringTransformer extends Configurable implements ValueTransform
throw new UnexpectedTypeException($value, '\DateTime');
}
$value->setTimezone(new \DateTimeZone($this->getOption('output_timezone')));
$value->setTimezone(new \DateTimeZone($this->outputTimezone));
return $value->format($this->getOption('format'));
return $value->format($this->format);
}
/**
@ -72,8 +68,8 @@ class DateTimeToStringTransformer extends Configurable implements ValueTransform
throw new UnexpectedTypeException($value, 'string');
}
$outputTimezone = $this->getOption('output_timezone');
$inputTimezone = $this->getOption('input_timezone');
$outputTimezone = $this->outputTimezone;
$inputTimezone = $this->inputTimezone;
try {
$dateTime = new \DateTime("$value $outputTimezone");

Some files were not shown because too many files have changed in this diff Show More