FormContractor.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. <?php
  2. /*
  3. * This file is part of the Sonata package.
  4. *
  5. * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
  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 Sonata\AdminBundle\Builder\ORM;
  11. use Sonata\AdminBundle\Admin\AdminInterface;
  12. use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
  13. use Sonata\AdminBundle\Admin\ORM\FieldDescription;
  14. use Sonata\AdminBundle\Form\EditableCollectionField;
  15. use Sonata\AdminBundle\Form\EditableFieldGroup;
  16. use Sonata\AdminBundle\Model\ModelManagerInterface;
  17. use Sonata\AdminBundle\Builder\FormContractorInterface;
  18. use Sonata\AdminBundle\Form\Type\AdminType;
  19. use Sonata\AdminBundle\Form\Type\ModelType;
  20. use Symfony\Component\Form\FormBuilder;
  21. use Symfony\Component\Form\FormInterface;
  22. use Symfony\Component\Form\FormFactoryInterface;
  23. use Doctrine\ORM\Mapping\ClassMetadataInfo;
  24. class FormContractor implements FormContractorInterface
  25. {
  26. protected $fieldFactory;
  27. protected $validator;
  28. /**
  29. * built-in definition
  30. *
  31. * @var array
  32. */
  33. protected $formTypes = array(
  34. 'string' => 'text',
  35. 'text' => 'textarea',
  36. 'boolean' => 'checkbox',
  37. 'checkbox' => 'checkbox',
  38. 'integer' => 'integer',
  39. 'tinyint' => 'integer',
  40. 'smallint' => 'integer',
  41. 'mediumint' => 'integer',
  42. 'bigint' => 'integer',
  43. 'decimal' => 'number',
  44. 'datetime' => 'datetime',
  45. 'date' => 'date',
  46. 'choice' => 'choice',
  47. 'array' => 'collection',
  48. 'country' => 'country',
  49. );
  50. public function __construct(FormFactoryInterface $formFactory)
  51. {
  52. $this->formFactory = $formFactory;
  53. }
  54. /**
  55. * Returns the field associated to a FieldDescriptionInterface
  56. * ie : build the embedded form from the related AdminInterface instance
  57. *
  58. * @throws RuntimeException
  59. * @param \Symfony\Component\Form\FormBuilder $formBuilder
  60. * @param \Sonata\AdminBundle\Admin\FieldDescriptionInterface $fieldDescription
  61. * @param null $fieldName
  62. * @return FieldGroup
  63. */
  64. protected function defineChildFormBuilder(FormBuilder $formBuilder, FieldDescriptionInterface $fieldDescription, $fieldName = null)
  65. {
  66. $fieldName = $fieldName ?: $fieldDescription->getFieldName();
  67. $associatedAdmin = $fieldDescription->getAssociationAdmin();
  68. if (!$associatedAdmin) {
  69. throw new \RuntimeException(sprintf('inline mode for field `%s` required an Admin definition', $fieldName));
  70. }
  71. // retrieve the related object
  72. $childBuilder = $formBuilder->create($fieldName, 'sonata_model_admin', array(
  73. 'field_description' => $fieldDescription
  74. ));
  75. $formBuilder->add($childBuilder);
  76. $associatedAdmin->defineFormBuilder($childBuilder);
  77. }
  78. /**
  79. * Returns the class associated to a FieldDescriptionInterface if any defined
  80. *
  81. * @throws RuntimeException
  82. * @param \Sonata\AdminBundle\Admin\FieldDescriptionInterface $fieldDescription
  83. * @return bool|string
  84. */
  85. public function getFormTypeName(FieldDescriptionInterface $fieldDescription)
  86. {
  87. $typeName = false;
  88. // the user redefined the mapping type, use the default built in definition
  89. if (!$fieldDescription->getFieldMapping() || $fieldDescription->getType() != $fieldDescription->getMappingType()) {
  90. $typeName = array_key_exists($fieldDescription->getType(), $this->formTypes) ? $this->formTypes[$fieldDescription->getType()] : false;
  91. } else if ($fieldDescription->getOption('form_field_type', false)) {
  92. $typeName = $fieldDescription->getOption('form_field_type', false);
  93. } else if (array_key_exists($fieldDescription->getType(), $this->formTypes)) {
  94. $typeName = $this->formTypes[$fieldDescription->getType()];
  95. }
  96. if (!$typeName) {
  97. throw new \RuntimeException(sprintf('No known form type for field `%s` (`%s`) is not implemented', $fieldDescription->getFieldName(), $fieldDescription->getType()));
  98. }
  99. return $typeName;
  100. }
  101. /**
  102. * Returns an OneToOne associated field
  103. *
  104. * @param \Symfony\Component\Form\FormBuilder $formBuilder
  105. * @param \Sonata\AdminBundle\Admin\FieldDescriptionInterface $fieldDescription
  106. * @return \Symfony\Component\Form\Type\FormTypeInterface
  107. */
  108. protected function defineOneToOneField(FormBuilder $formBuilder, FieldDescriptionInterface $fieldDescription)
  109. {
  110. // tweak the widget depend on the edit mode
  111. if ($fieldDescription->getOption('edit') == 'inline') {
  112. return $this->defineChildFormBuilder($formBuilder, $fieldDescription);
  113. }
  114. $type = new ModelType($fieldDescription->getAssociationAdmin()->getModelManager());
  115. $options = $fieldDescription->getOption('form_field_options', array());
  116. $options['class'] = $fieldDescription->getTargetEntity();
  117. if ($fieldDescription->getOption('edit') == 'list') {
  118. $options['parent'] = 'text';
  119. }
  120. $formBuilder->add($fieldDescription->getFieldName(), $type, $options);
  121. }
  122. /**
  123. * Returns the OneToMany associated field
  124. *
  125. * @param \Symfony\Component\Form\FormBuilder $formBuilder
  126. * @param \Sonata\AdminBundle\Admin\FieldDescriptionInterface $fieldDescription
  127. * @return \Symfony\Component\Form\Type\FormTypeInterface
  128. */
  129. protected function getOneToManyField(FormBuilder $formBuilder, FieldDescriptionInterface $fieldDescription)
  130. {
  131. if ($fieldDescription->getOption('edit') == 'inline') {
  132. // create a collection type with the generated prototype
  133. $options = $fieldDescription->getOption('form_field_options', array());
  134. $options['type'] = 'sonata_model_admin';
  135. $options['modifiable'] = true;
  136. $options['type_options'] = array(
  137. 'field_description' => $fieldDescription,
  138. );
  139. $formBuilder->add($fieldDescription->getFieldName(), 'sonata_admin_collection', $options);
  140. return;
  141. // $value = $fieldDescription->getValue($formBuilder->getData());
  142. //
  143. // // add new instances if the min number is not matched
  144. // if ($fieldDescription->getOption('min', 0) > count($value)) {
  145. //
  146. // $diff = $fieldDescription->getOption('min', 0) - count($value);
  147. // foreach (range(1, $diff) as $i) {
  148. // $this->addNewInstance($formBuilder->getData(), $fieldDescription);
  149. // }
  150. // }
  151. // use custom one to expose the newfield method
  152. // return new \Sonata\AdminBundle\Form\EditableCollectionField($prototype);
  153. }
  154. return $this->defineManyToManyField($formBuilder, $fieldDescription);
  155. }
  156. /**
  157. * @param \Symfony\Component\Form\FormBuilder $formBuilder
  158. * @param \Sonata\AdminBundle\Admin\FieldDescriptionInterface $fieldDescription
  159. * @return \Symfony\Component\Form\Type\FormTypeInterface
  160. */
  161. protected function defineManyToManyField(FormBuilder $formBuilder, FieldDescriptionInterface $fieldDescription)
  162. {
  163. $type = $fieldDescription->getOption('form_field_type', 'sonata_admin_model');
  164. $options = $fieldDescription->getOption('form_field_options', array());
  165. if ($type == 'sonata_admin_model') {
  166. $options['class'] = $fieldDescription->getTargetEntity();
  167. $options['multiple'] = true;
  168. $options['field_description'] = $fieldDescription;
  169. $options['parent'] = 'choice';
  170. $options['model_manager'] = $fieldDescription->getAdmin()->getModelManager();
  171. }
  172. $formBuilder->add($fieldDescription->getName(), $type, $options);
  173. }
  174. /**
  175. * Add a new field type into the provided FormBuilder
  176. *
  177. * @param \Symfony\Component\Form\FormBuilder $form
  178. * @param \Sonata\AdminBundle\Admin\FieldDescriptionInterface $name
  179. * @return void
  180. */
  181. public function addField(FormBuilder $formBuilder, FieldDescriptionInterface $fieldDescription)
  182. {
  183. switch ($fieldDescription->getType()) {
  184. case ClassMetadataInfo::ONE_TO_MANY:
  185. $this->getOneToManyField($formBuilder, $fieldDescription);
  186. break;
  187. case ClassMetadataInfo::MANY_TO_MANY:
  188. $this->defineManyToManyField($formBuilder, $fieldDescription);
  189. break;
  190. case ClassMetadataInfo::MANY_TO_ONE:
  191. case ClassMetadataInfo::ONE_TO_ONE:
  192. $this->defineOneToOneField($formBuilder, $fieldDescription);
  193. break;
  194. default:
  195. $formBuilder->add(
  196. $fieldDescription->getFieldName(),
  197. $this->getFormTypeName($fieldDescription),
  198. $fieldDescription->getOption('form_field_options', array())
  199. );
  200. }
  201. }
  202. /**
  203. * The method defines the correct default settings for the provided FieldDescription
  204. *
  205. * @param \Sonata\AdminBundle\Admin\AdminInterface
  206. * @param \Sonata\AdminBundle\Admin\FieldDescriptionInterface $fieldDescription
  207. * @param array $options
  208. * @return void
  209. */
  210. public function fixFieldDescription(AdminInterface $admin, FieldDescriptionInterface $fieldDescription, array $options = array())
  211. {
  212. $fieldDescription->mergeOptions($options);
  213. if ($admin->getModelManager()->hasMetadata($admin->getClass()))
  214. {
  215. $metadata = $admin->getModelManager()->getMetadata($admin->getClass());
  216. // set the default field mapping
  217. if (isset($metadata->fieldMappings[$fieldDescription->getName()])) {
  218. $fieldDescription->setFieldMapping($metadata->fieldMappings[$fieldDescription->getName()]);
  219. }
  220. // set the default association mapping
  221. if (isset($metadata->associationMappings[$fieldDescription->getName()])) {
  222. $fieldDescription->setAssociationMapping($metadata->associationMappings[$fieldDescription->getName()]);
  223. }
  224. }
  225. if (!$fieldDescription->getType()) {
  226. throw new \RuntimeException(sprintf('Please define a type for field `%s` in `%s`', $fieldDescription->getName(), get_class($admin)));
  227. }
  228. $fieldDescription->setAdmin($admin);
  229. $fieldDescription->setOption('edit', $fieldDescription->getOption('edit', 'standard'));
  230. // fix template value for doctrine association fields
  231. if (!$fieldDescription->getTemplate()) {
  232. $fieldDescription->setTemplate(sprintf('SonataAdminBundle:CRUD:edit_%s.html.twig', $fieldDescription->getType()));
  233. }
  234. if ($fieldDescription->getType() == ClassMetadataInfo::ONE_TO_ONE) {
  235. $fieldDescription->setTemplate('SonataAdminBundle:CRUD:edit_orm_one_to_one.html.twig');
  236. $admin->attachAdminClass($fieldDescription);
  237. }
  238. if ($fieldDescription->getType() == ClassMetadataInfo::MANY_TO_ONE) {
  239. $fieldDescription->setTemplate('SonataAdminBundle:CRUD:edit_orm_many_to_one.html.twig');
  240. $admin->attachAdminClass($fieldDescription);
  241. }
  242. if ($fieldDescription->getType() == ClassMetadataInfo::MANY_TO_MANY) {
  243. $fieldDescription->setTemplate('SonataAdminBundle:CRUD:edit_orm_many_to_many.html.twig');
  244. $admin->attachAdminClass($fieldDescription);
  245. }
  246. if ($fieldDescription->getType() == ClassMetadataInfo::ONE_TO_MANY) {
  247. $fieldDescription->setTemplate('SonataAdminBundle:CRUD:edit_orm_one_to_many.html.twig');
  248. if ($fieldDescription->getOption('edit') == 'inline' && !$fieldDescription->getOption('widget_form_field')) {
  249. $fieldDescription->setOption('widget_form_field', 'Bundle\\Sonata\\AdminBundle\\Form\\EditableFieldGroup');
  250. }
  251. $admin->attachAdminClass($fieldDescription);
  252. }
  253. // set correct default value
  254. if ($fieldDescription->getType() == 'datetime') {
  255. $options = $fieldDescription->getOption('form_field_options', array());
  256. if (!isset($options['years'])) {
  257. $options['years'] = range(1900, 2100);
  258. }
  259. $fieldDescription->setOption('form_field', $options);
  260. }
  261. }
  262. public function getFormFactory()
  263. {
  264. return $this->formFactory;
  265. }
  266. /**
  267. * @param string $name
  268. * @param array $options
  269. * @return \Symfony\Component\Form\FormBuilder
  270. */
  271. public function getFormBuilder($name, array $options = array())
  272. {
  273. return $this->getFormFactory()->createNamedBuilder('form', $name, $options);
  274. }
  275. }