PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/src/Symfony/Component/Form/FormFactory.php

https://github.com/ratasxy/symfony
PHP | 412 lines | 204 code | 63 blank | 145 comment | 26 complexity | 35037e5f3ac5f31a890548aa4190aedb MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Form;
  11. use Symfony\Component\Form\Exception\FormException;
  12. use Symfony\Component\Form\Exception\UnexpectedTypeException;
  13. use Symfony\Component\Form\Exception\TypeDefinitionException;
  14. use Symfony\Component\Form\Exception\CreationException;
  15. class FormFactory implements FormFactoryInterface
  16. {
  17. private static $requiredOptions = array(
  18. 'data',
  19. 'required',
  20. 'max_length',
  21. );
  22. /**
  23. * Extensions
  24. * @var array An array of FormExtensionInterface
  25. */
  26. private $extensions = array();
  27. /**
  28. * All known types (cache)
  29. * @var array An array of FormTypeInterface
  30. */
  31. private $types = array();
  32. /**
  33. * The guesser chain
  34. * @var FormTypeGuesserChain
  35. */
  36. private $guesser;
  37. /**
  38. * Constructor.
  39. *
  40. * @param array $extensions An array of FormExtensionInterface
  41. *
  42. * @throws UnexpectedTypeException if any extension does not implement FormExtensionInterface
  43. */
  44. public function __construct(array $extensions)
  45. {
  46. foreach ($extensions as $extension) {
  47. if (!$extension instanceof FormExtensionInterface) {
  48. throw new UnexpectedTypeException($extension, 'Symfony\Component\Form\FormExtensionInterface');
  49. }
  50. }
  51. $this->extensions = $extensions;
  52. }
  53. /**
  54. * Returns whether the given type is supported.
  55. *
  56. * @param string $name The name of the type
  57. *
  58. * @return Boolean Whether the type is supported
  59. */
  60. public function hasType($name)
  61. {
  62. if (isset($this->types[$name])) {
  63. return true;
  64. }
  65. try {
  66. $this->loadType($name);
  67. } catch (FormException $e) {
  68. return false;
  69. }
  70. return true;
  71. }
  72. /**
  73. * Add a type.
  74. *
  75. * @param FormTypeInterface $type The type
  76. */
  77. public function addType(FormTypeInterface $type)
  78. {
  79. $this->loadTypeExtensions($type);
  80. $this->validateFormTypeName($type);
  81. $this->types[$type->getName()] = $type;
  82. }
  83. /**
  84. * Returns a type by name.
  85. *
  86. * This methods registers the type extensions from the form extensions.
  87. *
  88. * @param string|FormTypeInterface $name The name of the type or a type instance
  89. *
  90. * @return FormTypeInterface The type
  91. *
  92. * @throws FormException if the type can not be retrieved from any extension
  93. */
  94. public function getType($name)
  95. {
  96. if (!is_string($name)) {
  97. throw new UnexpectedTypeException($name, 'string');
  98. }
  99. if (!isset($this->types[$name])) {
  100. $this->loadType($name);
  101. }
  102. return $this->types[$name];
  103. }
  104. /**
  105. * Returns a form.
  106. *
  107. * @see createBuilder()
  108. *
  109. * @param string|FormTypeInterface $type The type of the form
  110. * @param mixed $data The initial data
  111. * @param array $options The options
  112. *
  113. * @return Form The form named after the type
  114. *
  115. * @throws FormException if any given option is not applicable to the given type
  116. */
  117. public function create($type, $data = null, array $options = array())
  118. {
  119. return $this->createBuilder($type, $data, $options)->getForm();
  120. }
  121. /**
  122. * Returns a form.
  123. *
  124. * @see createNamedBuilder()
  125. *
  126. * @param string|FormTypeInterface $type The type of the form
  127. * @param string $name The name of the form
  128. * @param mixed $data The initial data
  129. * @param array $options The options
  130. *
  131. * @return Form The form
  132. *
  133. * @throws FormException if any given option is not applicable to the given type
  134. */
  135. public function createNamed($type, $name, $data = null, array $options = array())
  136. {
  137. return $this->createNamedBuilder($type, $name, $data, $options)->getForm();
  138. }
  139. /**
  140. * Returns a form for a property of a class.
  141. *
  142. * @see createBuilderForProperty()
  143. *
  144. * @param string $class The fully qualified class name
  145. * @param string $property The name of the property to guess for
  146. * @param mixed $data The initial data
  147. * @param array $options The options for the builder
  148. *
  149. * @return Form The form named after the property
  150. *
  151. * @throws FormException if any given option is not applicable to the form type
  152. */
  153. public function createForProperty($class, $property, $data = null, array $options = array())
  154. {
  155. return $this->createBuilderForProperty($class, $property, $data, $options)->getForm();
  156. }
  157. /**
  158. * Returns a form builder
  159. *
  160. * @param string|FormTypeInterface $type The type of the form
  161. * @param mixed $data The initial data
  162. * @param array $options The options
  163. *
  164. * @return FormBuilder The form builder
  165. *
  166. * @throws FormException if any given option is not applicable to the given type
  167. */
  168. public function createBuilder($type, $data = null, array $options = array())
  169. {
  170. $name = is_object($type) ? $type->getName() : $type;
  171. return $this->createNamedBuilder($type, $name, $data, $options);
  172. }
  173. /**
  174. * Returns a form builder.
  175. *
  176. * @param string|FormTypeInterface $type The type of the form
  177. * @param string $name The name of the form
  178. * @param mixed $data The initial data
  179. * @param array $options The options
  180. *
  181. * @return FormBuilder The form builder
  182. *
  183. * @throws FormException if any given option is not applicable to the given type
  184. */
  185. public function createNamedBuilder($type, $name, $data = null, array $options = array())
  186. {
  187. $builder = null;
  188. $types = array();
  189. $knownOptions = array();
  190. $passedOptions = array_keys($options);
  191. $optionValues = array();
  192. if (!array_key_exists('data', $options)) {
  193. $options['data'] = $data;
  194. }
  195. while (null !== $type) {
  196. if ($type instanceof FormTypeInterface) {
  197. $this->addType($type);
  198. } else {
  199. $type = $this->getType($type);
  200. }
  201. $defaultOptions = $type->getDefaultOptions($options);
  202. $optionValues = array_merge_recursive($optionValues, $type->getAllowedOptionValues($options));
  203. foreach ($type->getExtensions() as $typeExtension) {
  204. $defaultOptions = array_replace($defaultOptions, $typeExtension->getDefaultOptions($options));
  205. $optionValues = array_merge_recursive($optionValues, $typeExtension->getAllowedOptionValues($options));
  206. }
  207. $options = array_replace($defaultOptions, $options);
  208. $knownOptions = array_merge($knownOptions, array_keys($defaultOptions));
  209. array_unshift($types, $type);
  210. $type = $type->getParent($options);
  211. }
  212. $type = end($types);
  213. $diff = array_diff(self::$requiredOptions, $knownOptions);
  214. if (count($diff) > 0) {
  215. throw new TypeDefinitionException(sprintf('Type "%s" should support the option(s) "%s"', $type->getName(), implode('", "', $diff)));
  216. }
  217. $diff = array_diff($passedOptions, $knownOptions);
  218. if (count($diff) > 1) {
  219. throw new CreationException(sprintf('The options "%s" do not exist', implode('", "', $diff)));
  220. }
  221. if (count($diff) > 0) {
  222. throw new CreationException(sprintf('The option "%s" does not exist', current($diff)));
  223. }
  224. foreach ($optionValues as $option => $allowedValues) {
  225. if (!in_array($options[$option], $allowedValues, true)) {
  226. throw new CreationException(sprintf('The option "%s" has the value "%s", but is expected to be one of "%s"', $option, $options[$option], implode('", "', $allowedValues)));
  227. }
  228. }
  229. for ($i = 0, $l = count($types); $i < $l && !$builder; ++$i) {
  230. $builder = $types[$i]->createBuilder($name, $this, $options);
  231. }
  232. if (!$builder) {
  233. throw new TypeDefinitionException(sprintf('Type "%s" or any of its parents should return a FormBuilder instance from createBuilder()', $type->getName()));
  234. }
  235. $builder->setTypes($types);
  236. $builder->setCurrentLoadingType($type->getName());
  237. foreach ($types as $type) {
  238. $type->buildForm($builder, $options);
  239. foreach ($type->getExtensions() as $typeExtension) {
  240. $typeExtension->buildForm($builder, $options);
  241. }
  242. }
  243. $builder->setCurrentLoadingType(null);
  244. return $builder;
  245. }
  246. /**
  247. * Returns a form builder for a property of a class.
  248. *
  249. * If any of the 'max_length', 'required' and type options can be guessed,
  250. * and are not provided in the options argument, the guessed value is used.
  251. *
  252. * @param string $class The fully qualified class name
  253. * @param string $property The name of the property to guess for
  254. * @param mixed $data The initial data
  255. * @param array $options The options for the builder
  256. *
  257. * @return FormBuilder The form builder named after the property
  258. *
  259. * @throws FormException if any given option is not applicable to the form type
  260. */
  261. public function createBuilderForProperty($class, $property, $data = null, array $options = array())
  262. {
  263. if (!$this->guesser) {
  264. $this->loadGuesser();
  265. }
  266. $typeGuess = $this->guesser->guessType($class, $property);
  267. $maxLengthGuess = $this->guesser->guessMaxLength($class, $property);
  268. $minLengthGuess = $this->guesser->guessMinLength($class, $property);
  269. $requiredGuess = $this->guesser->guessRequired($class, $property);
  270. $type = $typeGuess ? $typeGuess->getType() : 'text';
  271. if ($maxLengthGuess) {
  272. $options = array_merge(array('max_length' => $maxLengthGuess->getValue()), $options);
  273. }
  274. if ($minLengthGuess) {
  275. if ($maxLengthGuess) {
  276. $options = array_merge(array('pattern' => '.{'.$minLengthGuess->getValue().','.$maxLengthGuess->getValue().'}'), $options);
  277. } else {
  278. $options = array_merge(array('pattern' => '.{'.$minLengthGuess->getValue().',}'), $options);
  279. }
  280. }
  281. if ($requiredGuess) {
  282. $options = array_merge(array('required' => $requiredGuess->getValue()), $options);
  283. }
  284. // user options may override guessed options
  285. if ($typeGuess) {
  286. $options = array_merge($typeGuess->getOptions(), $options);
  287. }
  288. return $this->createNamedBuilder($type, $property, $data, $options);
  289. }
  290. /**
  291. * Initializes the guesser chain.
  292. */
  293. private function loadGuesser()
  294. {
  295. $guessers = array();
  296. foreach ($this->extensions as $extension) {
  297. $guesser = $extension->getTypeGuesser();
  298. if ($guesser) {
  299. $guessers[] = $guesser;
  300. }
  301. }
  302. $this->guesser = new FormTypeGuesserChain($guessers);
  303. }
  304. /**
  305. * Loads a type.
  306. *
  307. * @param string $name The type name
  308. *
  309. * @throws FormException if the type is not provided by any registered extension
  310. */
  311. private function loadType($name)
  312. {
  313. $type = null;
  314. foreach ($this->extensions as $extension) {
  315. if ($extension->hasType($name)) {
  316. $type = $extension->getType($name);
  317. break;
  318. }
  319. }
  320. if (!$type) {
  321. throw new FormException(sprintf('Could not load type "%s"', $name));
  322. }
  323. $this->loadTypeExtensions($type);
  324. $this->validateFormTypeName($type);
  325. $this->types[$name] = $type;
  326. }
  327. /**
  328. * Loads the extensions for a given type.
  329. *
  330. * @param FormTypeInterface $type The type
  331. */
  332. private function loadTypeExtensions(FormTypeInterface $type)
  333. {
  334. $typeExtensions = array();
  335. foreach ($this->extensions as $extension) {
  336. $typeExtensions = array_merge(
  337. $typeExtensions,
  338. $extension->getTypeExtensions($type->getName())
  339. );
  340. }
  341. $type->setExtensions($typeExtensions);
  342. }
  343. private function validateFormTypeName(FormTypeInterface $type)
  344. {
  345. if (!preg_match('/^[a-z0-9_]+$/i', $type->getName())) {
  346. 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()));
  347. }
  348. }
  349. }