FormConfigBuilder.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811
  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\EventDispatcher\EventDispatcherInterface;
  12. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  13. use Symfony\Component\EventDispatcher\ImmutableEventDispatcher;
  14. use Symfony\Component\Form\Exception\BadMethodCallException;
  15. use Symfony\Component\Form\Exception\InvalidArgumentException;
  16. use Symfony\Component\PropertyAccess\PropertyPath;
  17. use Symfony\Component\PropertyAccess\PropertyPathInterface;
  18. /**
  19. * A basic form configuration.
  20. *
  21. * @author Bernhard Schussek <bschussek@gmail.com>
  22. */
  23. class FormConfigBuilder implements FormConfigBuilderInterface
  24. {
  25. /**
  26. * Caches a globally unique {@link NativeRequestHandler} instance.
  27. *
  28. * @var NativeRequestHandler
  29. */
  30. private static $nativeRequestHandler;
  31. protected $locked = false;
  32. private $dispatcher;
  33. private $name;
  34. /**
  35. * @var PropertyPathInterface|string|null
  36. */
  37. private $propertyPath;
  38. private $mapped = true;
  39. private $byReference = true;
  40. private $inheritData = false;
  41. private $compound = false;
  42. /**
  43. * @var ResolvedFormTypeInterface
  44. */
  45. private $type;
  46. private $viewTransformers = [];
  47. private $modelTransformers = [];
  48. /**
  49. * @var DataMapperInterface|null
  50. */
  51. private $dataMapper;
  52. private $required = true;
  53. private $disabled = false;
  54. private $errorBubbling = false;
  55. /**
  56. * @var mixed
  57. */
  58. private $emptyData;
  59. private $attributes = [];
  60. /**
  61. * @var mixed
  62. */
  63. private $data;
  64. /**
  65. * @var string|null
  66. */
  67. private $dataClass;
  68. private $dataLocked = false;
  69. /**
  70. * @var FormFactoryInterface|null
  71. */
  72. private $formFactory;
  73. /**
  74. * @var string|null
  75. */
  76. private $action;
  77. private $method = 'POST';
  78. /**
  79. * @var RequestHandlerInterface|null
  80. */
  81. private $requestHandler;
  82. private $autoInitialize = false;
  83. private $options;
  84. private $isEmptyCallback;
  85. /**
  86. * Creates an empty form configuration.
  87. *
  88. * @param string|null $name The form name
  89. * @param string|null $dataClass The class of the form's data
  90. *
  91. * @throws InvalidArgumentException if the data class is not a valid class or if
  92. * the name contains invalid characters
  93. */
  94. public function __construct(?string $name, ?string $dataClass, EventDispatcherInterface $dispatcher, array $options = [])
  95. {
  96. self::validateName($name);
  97. if (null !== $dataClass && !class_exists($dataClass) && !interface_exists($dataClass, false)) {
  98. throw new InvalidArgumentException(sprintf('Class "%s" not found. Is the "data_class" form option set correctly?', $dataClass));
  99. }
  100. $this->name = (string) $name;
  101. $this->dataClass = $dataClass;
  102. $this->dispatcher = $dispatcher;
  103. $this->options = $options;
  104. }
  105. /**
  106. * {@inheritdoc}
  107. */
  108. public function addEventListener(string $eventName, callable $listener, int $priority = 0)
  109. {
  110. if ($this->locked) {
  111. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  112. }
  113. $this->dispatcher->addListener($eventName, $listener, $priority);
  114. return $this;
  115. }
  116. /**
  117. * {@inheritdoc}
  118. */
  119. public function addEventSubscriber(EventSubscriberInterface $subscriber)
  120. {
  121. if ($this->locked) {
  122. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  123. }
  124. $this->dispatcher->addSubscriber($subscriber);
  125. return $this;
  126. }
  127. /**
  128. * {@inheritdoc}
  129. */
  130. public function addViewTransformer(DataTransformerInterface $viewTransformer, bool $forcePrepend = false)
  131. {
  132. if ($this->locked) {
  133. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  134. }
  135. if ($forcePrepend) {
  136. array_unshift($this->viewTransformers, $viewTransformer);
  137. } else {
  138. $this->viewTransformers[] = $viewTransformer;
  139. }
  140. return $this;
  141. }
  142. /**
  143. * {@inheritdoc}
  144. */
  145. public function resetViewTransformers()
  146. {
  147. if ($this->locked) {
  148. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  149. }
  150. $this->viewTransformers = [];
  151. return $this;
  152. }
  153. /**
  154. * {@inheritdoc}
  155. */
  156. public function addModelTransformer(DataTransformerInterface $modelTransformer, bool $forceAppend = false)
  157. {
  158. if ($this->locked) {
  159. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  160. }
  161. if ($forceAppend) {
  162. $this->modelTransformers[] = $modelTransformer;
  163. } else {
  164. array_unshift($this->modelTransformers, $modelTransformer);
  165. }
  166. return $this;
  167. }
  168. /**
  169. * {@inheritdoc}
  170. */
  171. public function resetModelTransformers()
  172. {
  173. if ($this->locked) {
  174. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  175. }
  176. $this->modelTransformers = [];
  177. return $this;
  178. }
  179. /**
  180. * {@inheritdoc}
  181. */
  182. public function getEventDispatcher()
  183. {
  184. if ($this->locked && !$this->dispatcher instanceof ImmutableEventDispatcher) {
  185. $this->dispatcher = new ImmutableEventDispatcher($this->dispatcher);
  186. }
  187. return $this->dispatcher;
  188. }
  189. /**
  190. * {@inheritdoc}
  191. */
  192. public function getName()
  193. {
  194. return $this->name;
  195. }
  196. /**
  197. * {@inheritdoc}
  198. */
  199. public function getPropertyPath()
  200. {
  201. return $this->propertyPath;
  202. }
  203. /**
  204. * {@inheritdoc}
  205. */
  206. public function getMapped()
  207. {
  208. return $this->mapped;
  209. }
  210. /**
  211. * {@inheritdoc}
  212. */
  213. public function getByReference()
  214. {
  215. return $this->byReference;
  216. }
  217. /**
  218. * {@inheritdoc}
  219. */
  220. public function getInheritData()
  221. {
  222. return $this->inheritData;
  223. }
  224. /**
  225. * {@inheritdoc}
  226. */
  227. public function getCompound()
  228. {
  229. return $this->compound;
  230. }
  231. /**
  232. * {@inheritdoc}
  233. */
  234. public function getType()
  235. {
  236. return $this->type;
  237. }
  238. /**
  239. * {@inheritdoc}
  240. */
  241. public function getViewTransformers()
  242. {
  243. return $this->viewTransformers;
  244. }
  245. /**
  246. * {@inheritdoc}
  247. */
  248. public function getModelTransformers()
  249. {
  250. return $this->modelTransformers;
  251. }
  252. /**
  253. * {@inheritdoc}
  254. */
  255. public function getDataMapper()
  256. {
  257. return $this->dataMapper;
  258. }
  259. /**
  260. * {@inheritdoc}
  261. */
  262. public function getRequired()
  263. {
  264. return $this->required;
  265. }
  266. /**
  267. * {@inheritdoc}
  268. */
  269. public function getDisabled()
  270. {
  271. return $this->disabled;
  272. }
  273. /**
  274. * {@inheritdoc}
  275. */
  276. public function getErrorBubbling()
  277. {
  278. return $this->errorBubbling;
  279. }
  280. /**
  281. * {@inheritdoc}
  282. */
  283. public function getEmptyData()
  284. {
  285. return $this->emptyData;
  286. }
  287. /**
  288. * {@inheritdoc}
  289. */
  290. public function getAttributes()
  291. {
  292. return $this->attributes;
  293. }
  294. /**
  295. * {@inheritdoc}
  296. */
  297. public function hasAttribute(string $name)
  298. {
  299. return \array_key_exists($name, $this->attributes);
  300. }
  301. /**
  302. * {@inheritdoc}
  303. */
  304. public function getAttribute(string $name, $default = null)
  305. {
  306. return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default;
  307. }
  308. /**
  309. * {@inheritdoc}
  310. */
  311. public function getData()
  312. {
  313. return $this->data;
  314. }
  315. /**
  316. * {@inheritdoc}
  317. */
  318. public function getDataClass()
  319. {
  320. return $this->dataClass;
  321. }
  322. /**
  323. * {@inheritdoc}
  324. */
  325. public function getDataLocked()
  326. {
  327. return $this->dataLocked;
  328. }
  329. /**
  330. * {@inheritdoc}
  331. */
  332. public function getFormFactory()
  333. {
  334. return $this->formFactory;
  335. }
  336. /**
  337. * {@inheritdoc}
  338. */
  339. public function getAction()
  340. {
  341. return $this->action;
  342. }
  343. /**
  344. * {@inheritdoc}
  345. */
  346. public function getMethod()
  347. {
  348. return $this->method;
  349. }
  350. /**
  351. * {@inheritdoc}
  352. */
  353. public function getRequestHandler()
  354. {
  355. if (null === $this->requestHandler) {
  356. if (null === self::$nativeRequestHandler) {
  357. self::$nativeRequestHandler = new NativeRequestHandler();
  358. }
  359. $this->requestHandler = self::$nativeRequestHandler;
  360. }
  361. return $this->requestHandler;
  362. }
  363. /**
  364. * {@inheritdoc}
  365. */
  366. public function getAutoInitialize()
  367. {
  368. return $this->autoInitialize;
  369. }
  370. /**
  371. * {@inheritdoc}
  372. */
  373. public function getOptions()
  374. {
  375. return $this->options;
  376. }
  377. /**
  378. * {@inheritdoc}
  379. */
  380. public function hasOption(string $name)
  381. {
  382. return \array_key_exists($name, $this->options);
  383. }
  384. /**
  385. * {@inheritdoc}
  386. */
  387. public function getOption(string $name, $default = null)
  388. {
  389. return \array_key_exists($name, $this->options) ? $this->options[$name] : $default;
  390. }
  391. /**
  392. * {@inheritdoc}
  393. */
  394. public function getIsEmptyCallback(): ?callable
  395. {
  396. return $this->isEmptyCallback;
  397. }
  398. /**
  399. * {@inheritdoc}
  400. */
  401. public function setAttribute(string $name, $value)
  402. {
  403. if ($this->locked) {
  404. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  405. }
  406. $this->attributes[$name] = $value;
  407. return $this;
  408. }
  409. /**
  410. * {@inheritdoc}
  411. */
  412. public function setAttributes(array $attributes)
  413. {
  414. if ($this->locked) {
  415. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  416. }
  417. $this->attributes = $attributes;
  418. return $this;
  419. }
  420. /**
  421. * {@inheritdoc}
  422. */
  423. public function setDataMapper(DataMapperInterface $dataMapper = null)
  424. {
  425. if ($this->locked) {
  426. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  427. }
  428. $this->dataMapper = $dataMapper;
  429. return $this;
  430. }
  431. /**
  432. * {@inheritdoc}
  433. */
  434. public function setDisabled(bool $disabled)
  435. {
  436. if ($this->locked) {
  437. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  438. }
  439. $this->disabled = $disabled;
  440. return $this;
  441. }
  442. /**
  443. * {@inheritdoc}
  444. */
  445. public function setEmptyData($emptyData)
  446. {
  447. if ($this->locked) {
  448. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  449. }
  450. $this->emptyData = $emptyData;
  451. return $this;
  452. }
  453. /**
  454. * {@inheritdoc}
  455. */
  456. public function setErrorBubbling(bool $errorBubbling)
  457. {
  458. if ($this->locked) {
  459. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  460. }
  461. $this->errorBubbling = $errorBubbling;
  462. return $this;
  463. }
  464. /**
  465. * {@inheritdoc}
  466. */
  467. public function setRequired(bool $required)
  468. {
  469. if ($this->locked) {
  470. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  471. }
  472. $this->required = $required;
  473. return $this;
  474. }
  475. /**
  476. * {@inheritdoc}
  477. */
  478. public function setPropertyPath($propertyPath)
  479. {
  480. if ($this->locked) {
  481. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  482. }
  483. if (null !== $propertyPath && !$propertyPath instanceof PropertyPathInterface) {
  484. $propertyPath = new PropertyPath($propertyPath);
  485. }
  486. $this->propertyPath = $propertyPath;
  487. return $this;
  488. }
  489. /**
  490. * {@inheritdoc}
  491. */
  492. public function setMapped(bool $mapped)
  493. {
  494. if ($this->locked) {
  495. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  496. }
  497. $this->mapped = $mapped;
  498. return $this;
  499. }
  500. /**
  501. * {@inheritdoc}
  502. */
  503. public function setByReference(bool $byReference)
  504. {
  505. if ($this->locked) {
  506. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  507. }
  508. $this->byReference = $byReference;
  509. return $this;
  510. }
  511. /**
  512. * {@inheritdoc}
  513. */
  514. public function setInheritData(bool $inheritData)
  515. {
  516. if ($this->locked) {
  517. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  518. }
  519. $this->inheritData = $inheritData;
  520. return $this;
  521. }
  522. /**
  523. * {@inheritdoc}
  524. */
  525. public function setCompound(bool $compound)
  526. {
  527. if ($this->locked) {
  528. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  529. }
  530. $this->compound = $compound;
  531. return $this;
  532. }
  533. /**
  534. * {@inheritdoc}
  535. */
  536. public function setType(ResolvedFormTypeInterface $type)
  537. {
  538. if ($this->locked) {
  539. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  540. }
  541. $this->type = $type;
  542. return $this;
  543. }
  544. /**
  545. * {@inheritdoc}
  546. */
  547. public function setData($data)
  548. {
  549. if ($this->locked) {
  550. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  551. }
  552. $this->data = $data;
  553. return $this;
  554. }
  555. /**
  556. * {@inheritdoc}
  557. */
  558. public function setDataLocked(bool $locked)
  559. {
  560. if ($this->locked) {
  561. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  562. }
  563. $this->dataLocked = $locked;
  564. return $this;
  565. }
  566. /**
  567. * {@inheritdoc}
  568. */
  569. public function setFormFactory(FormFactoryInterface $formFactory)
  570. {
  571. if ($this->locked) {
  572. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  573. }
  574. $this->formFactory = $formFactory;
  575. return $this;
  576. }
  577. /**
  578. * {@inheritdoc}
  579. */
  580. public function setAction(string $action)
  581. {
  582. if ($this->locked) {
  583. throw new BadMethodCallException('The config builder cannot be modified anymore.');
  584. }
  585. $this->action = $action;
  586. return $this;
  587. }
  588. /**
  589. * {@inheritdoc}
  590. */
  591. public function setMethod(string $method)
  592. {
  593. if ($this->locked) {
  594. throw new BadMethodCallException('The config builder cannot be modified anymore.');
  595. }
  596. $this->method = strtoupper($method);
  597. return $this;
  598. }
  599. /**
  600. * {@inheritdoc}
  601. */
  602. public function setRequestHandler(RequestHandlerInterface $requestHandler)
  603. {
  604. if ($this->locked) {
  605. throw new BadMethodCallException('The config builder cannot be modified anymore.');
  606. }
  607. $this->requestHandler = $requestHandler;
  608. return $this;
  609. }
  610. /**
  611. * {@inheritdoc}
  612. */
  613. public function setAutoInitialize(bool $initialize)
  614. {
  615. if ($this->locked) {
  616. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  617. }
  618. $this->autoInitialize = $initialize;
  619. return $this;
  620. }
  621. /**
  622. * {@inheritdoc}
  623. */
  624. public function getFormConfig()
  625. {
  626. if ($this->locked) {
  627. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  628. }
  629. // This method should be idempotent, so clone the builder
  630. $config = clone $this;
  631. $config->locked = true;
  632. return $config;
  633. }
  634. /**
  635. * {@inheritdoc}
  636. */
  637. public function setIsEmptyCallback(?callable $isEmptyCallback)
  638. {
  639. $this->isEmptyCallback = $isEmptyCallback;
  640. return $this;
  641. }
  642. /**
  643. * Validates whether the given variable is a valid form name.
  644. *
  645. * @throws InvalidArgumentException if the name contains invalid characters
  646. *
  647. * @internal
  648. */
  649. final public static function validateName(?string $name)
  650. {
  651. if (!self::isValidName($name)) {
  652. throw new InvalidArgumentException(sprintf('The name "%s" contains illegal characters. Names should start with a letter, digit or underscore and only contain letters, digits, numbers, underscores ("_"), hyphens ("-") and colons (":").', $name));
  653. }
  654. }
  655. /**
  656. * Returns whether the given variable contains a valid form name.
  657. *
  658. * A name is accepted if it
  659. *
  660. * * is empty
  661. * * starts with a letter, digit or underscore
  662. * * contains only letters, digits, numbers, underscores ("_"),
  663. * hyphens ("-") and colons (":")
  664. */
  665. final public static function isValidName(?string $name): bool
  666. {
  667. return '' === $name || null === $name || preg_match('/^[a-zA-Z0-9_][a-zA-Z0-9_\-:]*$/D', $name);
  668. }
  669. }