Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

413 lines (349 sloc) 13.125 kb
<?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\FormException;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Form\Exception\TypeDefinitionException;
use Symfony\Component\Form\Exception\CreationException;
class FormFactory implements FormFactoryInterface
{
private static $requiredOptions = array(
'data',
'required',
'max_length',
);
/**
* Extensions
* @var array An array of FormExtensionInterface
*/
private $extensions = array();
/**
* All known types (cache)
* @var array An array of FormTypeInterface
*/
private $types = array();
/**
* The guesser chain
* @var FormTypeGuesserChain
*/
private $guesser;
/**
* Constructor.
*
* @param array $extensions An array of FormExtensionInterface
*
* @throws UnexpectedTypeException if any extension does not implement FormExtensionInterface
*/
public function __construct(array $extensions)
{
foreach ($extensions as $extension) {
if (!$extension instanceof FormExtensionInterface) {
throw new UnexpectedTypeException($extension, 'Symfony\Component\Form\FormExtensionInterface');
}
}
$this->extensions = $extensions;
}
/**
* Returns whether the given type is supported.
*
* @param string $name The name of the type
*
* @return Boolean Whether the type is supported
*/
public function hasType($name)
{
if (isset($this->types[$name])) {
return true;
}
try {
$this->loadType($name);
} catch (FormException $e) {
return false;
}
return true;
}
/**
* Add a type.
*
* @param FormTypeInterface $type The type
*/
public function addType(FormTypeInterface $type)
{
$this->loadTypeExtensions($type);
$this->validateFormTypeName($type);
$this->types[$type->getName()] = $type;
}
/**
* Returns a type by name.
*
* This methods registers the type extensions from the form extensions.
*
* @param string|FormTypeInterface $name The name of the type or a type instance
*
* @return FormTypeInterface The type
*
* @throws FormException if the type can not be retrieved from any extension
*/
public function getType($name)
{
if (!is_string($name)) {
throw new UnexpectedTypeException($name, 'string');
}
if (!isset($this->types[$name])) {
$this->loadType($name);
}
return $this->types[$name];
}
/**
* Returns a form.
*
* @see createBuilder()
*
* @param string|FormTypeInterface $type The type of the form
* @param mixed $data The initial data
* @param array $options The options
*
* @return Form The form named after the type
*
* @throws FormException if any given option is not applicable to the given type
*/
public function create($type, $data = null, array $options = array())
{
return $this->createBuilder($type, $data, $options)->getForm();
}
/**
* Returns a form.
*
* @see createNamedBuilder()
*
* @param string|FormTypeInterface $type The type of the form
* @param string $name The name of the form
* @param mixed $data The initial data
* @param array $options The options
*
* @return Form The form
*
* @throws FormException if any given option is not applicable to the given type
*/
public function createNamed($type, $name, $data = null, array $options = array())
{
return $this->createNamedBuilder($type, $name, $data, $options)->getForm();
}
/**
* Returns a form for a property of a class.
*
* @see createBuilderForProperty()
*
* @param string $class The fully qualified class name
* @param string $property The name of the property to guess for
* @param mixed $data The initial data
* @param array $options The options for the builder
*
* @return Form The form named after the property
*
* @throws FormException if any given option is not applicable to the form type
*/
public function createForProperty($class, $property, $data = null, array $options = array())
{
return $this->createBuilderForProperty($class, $property, $data, $options)->getForm();
}
/**
* Returns a form builder
*
* @param string|FormTypeInterface $type The type of the form
* @param mixed $data The initial data
* @param array $options The options
*
* @return FormBuilder The form builder
*
* @throws FormException if any given option is not applicable to the given type
*/
public function createBuilder($type, $data = null, array $options = array())
{
$name = is_object($type) ? $type->getName() : $type;
return $this->createNamedBuilder($type, $name, $data, $options);
}
/**
* Returns a form builder.
*
* @param string|FormTypeInterface $type The type of the form
* @param string $name The name of the form
* @param mixed $data The initial data
* @param array $options The options
*
* @return FormBuilder The form builder
*
* @throws FormException if any given option is not applicable to the given type
*/
public function createNamedBuilder($type, $name, $data = null, array $options = array())
{
$builder = null;
$types = array();
$knownOptions = array();
$passedOptions = array_keys($options);
$optionValues = array();
if (!array_key_exists('data', $options)) {
$options['data'] = $data;
}
while (null !== $type) {
if ($type instanceof FormTypeInterface) {
$this->addType($type);
} else {
$type = $this->getType($type);
}
$defaultOptions = $type->getDefaultOptions($options);
$optionValues = array_merge_recursive($optionValues, $type->getAllowedOptionValues($options));
foreach ($type->getExtensions() as $typeExtension) {
$defaultOptions = array_replace($defaultOptions, $typeExtension->getDefaultOptions($options));
$optionValues = array_merge_recursive($optionValues, $typeExtension->getAllowedOptionValues($options));
}
$options = array_replace($defaultOptions, $options);
$knownOptions = array_merge($knownOptions, array_keys($defaultOptions));
array_unshift($types, $type);
$type = $type->getParent($options);
}
$type = end($types);
$diff = array_diff(self::$requiredOptions, $knownOptions);
if (count($diff) > 0) {
throw new TypeDefinitionException(sprintf('Type "%s" should support the option(s) "%s"', $type->getName(), implode('", "', $diff)));
}
$diff = array_diff($passedOptions, $knownOptions);
if (count($diff) > 1) {
throw new CreationException(sprintf('The options "%s" do not exist', implode('", "', $diff)));
}
if (count($diff) > 0) {
throw new CreationException(sprintf('The option "%s" does not exist', current($diff)));
}
foreach ($optionValues as $option => $allowedValues) {
if (!in_array($options[$option], $allowedValues, true)) {
throw new CreationException(sprintf('The option "%s" has the value "%s", but is expected to be one of "%s"', $option, $options[$option], implode('", "', $allowedValues)));
}
}
for ($i = 0, $l = count($types); $i < $l && !$builder; ++$i) {
$builder = $types[$i]->createBuilder($name, $this, $options);
}
if (!$builder) {
throw new TypeDefinitionException(sprintf('Type "%s" or any of its parents should return a FormBuilder instance from createBuilder()', $type->getName()));
}
$builder->setTypes($types);
$builder->setCurrentLoadingType($type->getName());
foreach ($types as $type) {
$type->buildForm($builder, $options);
foreach ($type->getExtensions() as $typeExtension) {
$typeExtension->buildForm($builder, $options);
}
}
$builder->setCurrentLoadingType(null);
return $builder;
}
/**
* Returns a form builder for a property of a class.
*
* If any of the 'max_length', 'required' and type options can be guessed,
* and are not provided in the options argument, the guessed value is used.
*
* @param string $class The fully qualified class name
* @param string $property The name of the property to guess for
* @param mixed $data The initial data
* @param array $options The options for the builder
*
* @return FormBuilder The form builder named after the property
*
* @throws FormException if any given option is not applicable to the form type
*/
public function createBuilderForProperty($class, $property, $data = null, array $options = array())
{
if (!$this->guesser) {
$this->loadGuesser();
}
$typeGuess = $this->guesser->guessType($class, $property);
$maxLengthGuess = $this->guesser->guessMaxLength($class, $property);
$minLengthGuess = $this->guesser->guessMinLength($class, $property);
$requiredGuess = $this->guesser->guessRequired($class, $property);
$type = $typeGuess ? $typeGuess->getType() : 'text';
if ($maxLengthGuess) {
$options = array_merge(array('max_length' => $maxLengthGuess->getValue()), $options);
}
if ($minLengthGuess) {
if ($maxLengthGuess) {
$options = array_merge(array('pattern' => '.{'.$minLengthGuess->getValue().','.$maxLengthGuess->getValue().'}'), $options);
} else {
$options = array_merge(array('pattern' => '.{'.$minLengthGuess->getValue().',}'), $options);
}
}
if ($requiredGuess) {
$options = array_merge(array('required' => $requiredGuess->getValue()), $options);
}
// user options may override guessed options
if ($typeGuess) {
$options = array_merge($typeGuess->getOptions(), $options);
}
return $this->createNamedBuilder($type, $property, $data, $options);
}
/**
* Initializes the guesser chain.
*/
private function loadGuesser()
{
$guessers = array();
foreach ($this->extensions as $extension) {
$guesser = $extension->getTypeGuesser();
if ($guesser) {
$guessers[] = $guesser;
}
}
$this->guesser = new FormTypeGuesserChain($guessers);
}
/**
* Loads a type.
*
* @param string $name The type name
*
* @throws FormException if the type is not provided by any registered extension
*/
private function loadType($name)
{
$type = null;
foreach ($this->extensions as $extension) {
if ($extension->hasType($name)) {
$type = $extension->getType($name);
break;
}
}
if (!$type) {
throw new FormException(sprintf('Could not load type "%s"', $name));
}
$this->loadTypeExtensions($type);
$this->validateFormTypeName($type);
$this->types[$name] = $type;
}
/**
* Loads the extensions for a given type.
*
* @param FormTypeInterface $type The type
*/
private function loadTypeExtensions(FormTypeInterface $type)
{
$typeExtensions = array();
foreach ($this->extensions as $extension) {
$typeExtensions = array_merge(
$typeExtensions,
$extension->getTypeExtensions($type->getName())
);
}
$type->setExtensions($typeExtensions);
}
private function validateFormTypeName(FormTypeInterface $type)
{
if (!preg_match('/^[a-z0-9_]+$/i', $type->getName())) {
throw new FormException(sprintf('The "%s" form type name ("%s") is not valid. Names must only contain letters, numbers, and "_".', get_class($type), $type->getName()));
}
}
}
Jump to Line
Something went wrong with that request. Please try again.