vendor/symfony/form/FormConfigBuilder.php line 116

Open in your IDE?
  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. private $action = '';
  74. private $method = 'POST';
  75. /**
  76. * @var RequestHandlerInterface|null
  77. */
  78. private $requestHandler;
  79. private $autoInitialize = false;
  80. private $options;
  81. private $isEmptyCallback;
  82. /**
  83. * Creates an empty form configuration.
  84. *
  85. * @param string|null $name The form name
  86. * @param string|null $dataClass The class of the form's data
  87. *
  88. * @throws InvalidArgumentException if the data class is not a valid class or if
  89. * the name contains invalid characters
  90. */
  91. public function __construct(?string $name, ?string $dataClass, EventDispatcherInterface $dispatcher, array $options = [])
  92. {
  93. self::validateName($name);
  94. if (null !== $dataClass && !class_exists($dataClass) && !interface_exists($dataClass, false)) {
  95. throw new InvalidArgumentException(sprintf('Class "%s" not found. Is the "data_class" form option set correctly?', $dataClass));
  96. }
  97. $this->name = (string) $name;
  98. $this->dataClass = $dataClass;
  99. $this->dispatcher = $dispatcher;
  100. $this->options = $options;
  101. }
  102. /**
  103. * {@inheritdoc}
  104. */
  105. public function addEventListener(string $eventName, callable $listener, int $priority = 0)
  106. {
  107. if ($this->locked) {
  108. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  109. }
  110. $this->dispatcher->addListener($eventName, $listener, $priority);
  111. return $this;
  112. }
  113. /**
  114. * {@inheritdoc}
  115. */
  116. public function addEventSubscriber(EventSubscriberInterface $subscriber)
  117. {
  118. if ($this->locked) {
  119. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  120. }
  121. $this->dispatcher->addSubscriber($subscriber);
  122. return $this;
  123. }
  124. /**
  125. * {@inheritdoc}
  126. */
  127. public function addViewTransformer(DataTransformerInterface $viewTransformer, bool $forcePrepend = false)
  128. {
  129. if ($this->locked) {
  130. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  131. }
  132. if ($forcePrepend) {
  133. array_unshift($this->viewTransformers, $viewTransformer);
  134. } else {
  135. $this->viewTransformers[] = $viewTransformer;
  136. }
  137. return $this;
  138. }
  139. /**
  140. * {@inheritdoc}
  141. */
  142. public function resetViewTransformers()
  143. {
  144. if ($this->locked) {
  145. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  146. }
  147. $this->viewTransformers = [];
  148. return $this;
  149. }
  150. /**
  151. * {@inheritdoc}
  152. */
  153. public function addModelTransformer(DataTransformerInterface $modelTransformer, bool $forceAppend = false)
  154. {
  155. if ($this->locked) {
  156. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  157. }
  158. if ($forceAppend) {
  159. $this->modelTransformers[] = $modelTransformer;
  160. } else {
  161. array_unshift($this->modelTransformers, $modelTransformer);
  162. }
  163. return $this;
  164. }
  165. /**
  166. * {@inheritdoc}
  167. */
  168. public function resetModelTransformers()
  169. {
  170. if ($this->locked) {
  171. throw new BadMethodCallException('FormConfigBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
  172. }
  173. $this->modelTransformers = [];
  174. return $this;
  175. }
  176. /**
  177. * {@inheritdoc}
  178. */
  179. public function getEventDispatcher()
  180. {
  181. if ($this->locked && !$this->dispatcher instanceof ImmutableEventDispatcher) {
  182. $this->dispatcher = new ImmutableEventDispatcher($this->dispatcher);
  183. }
  184. return $this->dispatcher;
  185. }
  186. /**
  187. * {@inheritdoc}
  188. */
  189. public function getName()
  190. {
  191. return $this->name;
  192. }
  193. /**
  194. * {@inheritdoc}
  195. */
  196. public function getPropertyPath()
  197. {
  198. return $this->propertyPath;
  199. }
  200. /**
  201. * {@inheritdoc}
  202. */
  203. public function getMapped()
  204. {
  205. return $this->mapped;
  206. }
  207. /**
  208. * {@inheritdoc}
  209. */
  210. public function getByReference()
  211. {
  212. return $this->byReference;
  213. }
  214. /**
  215. * {@inheritdoc}
  216. */
  217. public function getInheritData()
  218. {
  219. return $this->inheritData;
  220. }
  221. /**
  222. * {@inheritdoc}
  223. */
  224. public function getCompound()
  225. {
  226. return $this->compound;
  227. }
  228. /**
  229. * {@inheritdoc}
  230. */
  231. public function getType()
  232. {
  233. return $this->type;
  234. }
  235. /**
  236. * {@inheritdoc}
  237. */
  238. public function getViewTransformers()
  239. {
  240. return $this->viewTransformers;
  241. }
  242. /**
  243. * {@inheritdoc}
  244. */
  245. public function getModelTransformers()
  246. {
  247. return $this->modelTransformers;
  248. }
  249. /**
  250. * {@inheritdoc}
  251. */
  252. public function getDataMapper()
  253. {
  254. return $this->dataMapper;
  255. }
  256. /**
  257. * {@inheritdoc}
  258. */
  259. public function getRequired()
  260. {
  261. return $this->required;
  262. }
  263. /**
  264. * {@inheritdoc}
  265. */
  266. public function getDisabled()
  267. {
  268. return $this->disabled;
  269. }
  270. /**
  271. * {@inheritdoc}
  272. */
  273. public function getErrorBubbling()
  274. {
  275. return $this->errorBubbling;
  276. }
  277. /**
  278. * {@inheritdoc}
  279. */
  280. public function getEmptyData()
  281. {
  282. return $this->emptyData;
  283. }
  284. /**
  285. * {@inheritdoc}
  286. */
  287. public function getAttributes()
  288. {
  289. return $this->attributes;
  290. }
  291. /**
  292. * {@inheritdoc}
  293. */
  294. public function hasAttribute(string $name)
  295. {
  296. return \array_key_exists($name, $this->attributes);
  297. }
  298. /**
  299. * {@inheritdoc}
  300. */
  301. public function getAttribute(string $name, $default = null)
  302. {
  303. return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default;
  304. }
  305. /**
  306. * {@inheritdoc}
  307. */
  308. public function getData()
  309. {
  310. return $this->data;
  311. }
  312. /**
  313. * {@inheritdoc}
  314. */
  315. public function getDataClass()
  316. {
  317. return $this->dataClass;
  318. }
  319. /**
  320. * {@inheritdoc}
  321. */
  322. public function getDataLocked()
  323. {
  324. return $this->dataLocked;
  325. }
  326. /**
  327. * {@inheritdoc}
  328. */
  329. public function getFormFactory()
  330. {
  331. if (!isset($this->formFactory)) {
  332. throw new BadMethodCallException('The form factory must be set before retrieving it.');
  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. }