EntityAdmin.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  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\BaseApplicationBundle\Admin;
  11. use Symfony\Component\DependencyInjection\ContainerAware;
  12. use Symfony\Component\Form\Form;
  13. use Doctrine\ORM\Mapping\ClassMetadataInfo;
  14. use Sonata\BaseApplicationBundle\Tool\Datagrid;
  15. abstract class EntityAdmin extends Admin
  16. {
  17. /**
  18. * make sure the base fields are set in the correct format
  19. *
  20. * @param $selected_fields
  21. * @return array
  22. */
  23. static public function getBaseFields(ClassMetadataInfo $metadata, $selectedFields)
  24. {
  25. // if nothing is defined we display all fields
  26. if (!$selectedFields) {
  27. $selectedFields = array_keys($metadata->reflFields) + array_keys($metadata->associationMappings);
  28. }
  29. $fields = array();
  30. // make sure we works with array
  31. foreach ($selectedFields as $name => $options) {
  32. $description = new FieldDescription;
  33. if (is_array($options)) {
  34. // remove property value
  35. if (isset($options['type'])) {
  36. $description->setType($options['type']);
  37. unset($options['type']);
  38. }
  39. // remove property value
  40. if (isset($options['template'])) {
  41. $description->setTemplate($options['template']);
  42. unset($options['template']);
  43. }
  44. $description->setOptions($options);
  45. } else {
  46. $name = $options;
  47. }
  48. $description->setName($name);
  49. if (isset($metadata->fieldMappings[$name])) {
  50. $description->setFieldMapping($metadata->fieldMappings[$name]);
  51. }
  52. if (isset($metadata->associationMappings[$name])) {
  53. $description->setAssociationMapping($metadata->associationMappings[$name]);
  54. }
  55. $fields[$name] = $description;
  56. }
  57. return $fields;
  58. }
  59. /**
  60. * return the entity manager
  61. *
  62. * @return EntityManager
  63. */
  64. public function getEntityManager()
  65. {
  66. return $this->container->get('doctrine.orm.default_entity_manager');
  67. }
  68. /**
  69. * build the field to use in the list view
  70. *
  71. * @return void
  72. */
  73. protected function buildListFields()
  74. {
  75. if ($this->loaded['list_fields']) {
  76. return;
  77. }
  78. $this->loaded['list_fields'] = true;
  79. $this->listFields = self::getBaseFields($this->getClassMetaData(), $this->listFields);
  80. // normalize field
  81. foreach ($this->listFields as $name => $fieldDescription) {
  82. $fieldDescription->setOption('code', $fieldDescription->getOption('code', $name));
  83. $fieldDescription->setOption('label', $fieldDescription->getOption('label', $name));
  84. // set the default type if none is set
  85. if (!$fieldDescription->getType()) {
  86. $fieldDescription->setType('string');
  87. }
  88. $fieldDescription->setAdmin($this);
  89. if (!$fieldDescription->getTemplate()) {
  90. $fieldDescription->setTemplate(sprintf('SonataBaseApplicationBundle:CRUD:list_%s.twig.html', $fieldDescription->getType()));
  91. if ($fieldDescription->getType() == ClassMetadataInfo::MANY_TO_ONE) {
  92. $fieldDescription->setTemplate('SonataBaseApplicationBundle:CRUD:list_many_to_one.twig.html');
  93. $this->attachAdminClass($fieldDescription);
  94. }
  95. if ($fieldDescription->getType() == ClassMetadataInfo::ONE_TO_ONE) {
  96. $fieldDescription->setTemplate('SonataBaseApplicationBundle:CRUD:list_one_to_one.twig.html');
  97. $this->attachAdminClass($fieldDescription);
  98. }
  99. if ($fieldDescription->getType() == ClassMetadataInfo::ONE_TO_MANY) {
  100. $fieldDescription->setTemplate('SonataBaseApplicationBundle:CRUD:list_one_to_many.twig.html');
  101. $this->attachAdminClass($fieldDescription);
  102. }
  103. if ($fieldDescription->getType() == ClassMetadataInfo::MANY_TO_MANY) {
  104. $fieldDescription->setTemplate('SonataBaseApplicationBundle:CRUD:list_many_to_many.twig.html');
  105. $this->attachAdminClass($fieldDescription);
  106. }
  107. }
  108. }
  109. $this->configureListFields();
  110. if (!isset($this->listFields['_batch'])) {
  111. $fieldDescription = new FieldDescription();
  112. $fieldDescription->setOptions(array(
  113. 'label' => 'batch',
  114. 'code' => '_batch'
  115. ));
  116. $fieldDescription->setTemplate('SonataBaseApplicationBundle:CRUD:list__batch.twig.html');
  117. $this->listFields = array( '_batch' => $fieldDescription ) + $this->listFields;
  118. }
  119. return $this->listFields;
  120. }
  121. /**
  122. * return the list of choices for one entity
  123. *
  124. * @param FieldDescription $description
  125. * @return array
  126. */
  127. protected function getChoices(FieldDescription $description, $prependChoices = array())
  128. {
  129. /*
  130. UNUSED RIGHT NOW
  131. if (!isset($this->choicesCache[$description->getTargetEntity()])) {
  132. $targets = $this->getEntityManager()
  133. ->createQueryBuilder()
  134. ->select('t')
  135. ->from($description->getTargetEntity(), 't')
  136. ->getQuery()
  137. ->execute();
  138. $choices = array();
  139. foreach ($targets as $target) {
  140. // todo : puts this into a configuration option and use reflection
  141. foreach (array('getTitle', 'getName', '__toString') as $getter) {
  142. if (method_exists($target, $getter)) {
  143. $choices[$target->getId()] = $target->$getter();
  144. break;
  145. }
  146. }
  147. }
  148. $this->choicesCache[$description->getTargetEntity()] = $choices;
  149. }
  150. return $prependChoices + $this->choicesCache[$description->getTargetEntity()];
  151. */
  152. }
  153. /**
  154. * return the field associated to a FieldDescription
  155. * ie : build the embedded form from the related Admin instance
  156. *
  157. * @throws RuntimeException
  158. * @param $object
  159. * @param FieldDescription $fieldDescription
  160. * @param null $fieldName
  161. * @return FieldGroup
  162. */
  163. protected function getRelatedAssociatedField($object, FieldDescription $fieldDescription, $fieldName = null)
  164. {
  165. /*
  166. UNUSED RIGHT NOW
  167. $fieldName = $fieldName ?: $fieldDescription->getFieldName();
  168. $associatedAdmin = $fieldDescription->getAssociationAdmin();
  169. if (!$associatedAdmin) {
  170. throw new \RuntimeException(sprintf('inline mode for field `%s` required an Admin definition', $fieldName));
  171. }
  172. // retrieve the related object
  173. $targetObject = $associatedAdmin->getNewInstance();
  174. // retrieve the related form
  175. $targetFields = $associatedAdmin->getFormFields();
  176. $targetForm = $associatedAdmin->getForm($targetObject, $targetFields);
  177. // create the transformer
  178. $transformer = new \Sonata\BaseApplicationBundle\Form\ValueTransformer\ArrayToObjectTransformer(array(
  179. 'em' => $this->getEntityManager(),
  180. 'className' => $fieldDescription->getTargetEntity()
  181. ));
  182. // create the "embedded" field
  183. if ($fieldDescription->getType() == ClassMetadataInfo::ONE_TO_MANY) {
  184. $field = new \Sonata\BaseApplicationBundle\Form\EditableFieldGroup($fieldName, array(
  185. 'value_transformer' => $transformer,
  186. ));
  187. } else {
  188. $field = new \Symfony\Component\Form\FieldGroup($fieldName, array(
  189. 'value_transformer' => $transformer,
  190. ));
  191. }
  192. foreach ($targetForm->getFields() as $name => $formField) {
  193. if ($name == '_token') {
  194. continue;
  195. }
  196. $field->add($formField);
  197. }
  198. return $field;
  199. */
  200. }
  201. /**
  202. * return the class associated to a FieldDescription
  203. *
  204. * @throws RuntimeException
  205. * @param FieldDescription $fieldDescription
  206. * @return bool
  207. */
  208. public function getFormFieldClass(FieldDescription $fieldDescription)
  209. {
  210. /*
  211. UNUSED RIGHT NOW
  212. $class = isset($this->formFieldClasses[$fieldDescription->getType()]) ? $this->formFieldClasses[$fieldDescription->getType()] : false;
  213. $class = $fieldDescription->getOption('form_field_widget', $class);
  214. if(!$class) {
  215. throw new \RuntimeException(sprintf('unknow type `%s`', $fieldDescription->getType()));
  216. }
  217. if(!class_exists($class)) {
  218. throw new \RuntimeException(sprintf('The class `%s` does not exist for field `%s`', $class, $fieldDescription->getType()));
  219. }
  220. return $class;
  221. */
  222. }
  223. /**
  224. * Add a new instance to the related FieldDescription value
  225. *
  226. * @param $object
  227. * @param FieldDescription $fieldDescription
  228. * @return void
  229. */
  230. public function addNewInstance($object, FieldDescription $fieldDescription)
  231. {
  232. /*
  233. UNUSED RIGHT NOW
  234. $instance = $fieldDescription->getAssociationAdmin()->getNewInstance();
  235. $mapping = $fieldDescription->getAssociationMapping();
  236. $method = sprintf('add%s', FieldDescription::camelize($mapping['fieldName']));
  237. $object->$method($instance);
  238. */
  239. }
  240. /**
  241. * return an OneToOne associated field
  242. *
  243. * @param $object
  244. * @param FieldDescription $fieldDescription
  245. * @return ChoiceField
  246. */
  247. protected function getOneToOneField($object, FieldDescription $fieldDescription)
  248. {
  249. /*
  250. UNUSED RIGHT NOW
  251. // tweak the widget depend on the edit mode
  252. if ($fieldDescription->getOption('edit') == 'inline') {
  253. return $this->getRelatedAssociatedField($object, $fieldDescription);
  254. }
  255. $options = array(
  256. 'value_transformer' => new \Symfony\Bundle\DoctrineBundle\Form\ValueTransformer\EntityToIDTransformer(array(
  257. 'em' => $this->getEntityManager(),
  258. 'className' => $fieldDescription->getTargetEntity()
  259. ))
  260. );
  261. $options = array_merge($options, $fieldDescription->getOption('form_field_options', array()));
  262. if ($fieldDescription->getOption('edit') == 'list') {
  263. return new \Symfony\Component\Form\TextField($fieldDescription->getFieldName(), $options);
  264. }
  265. $class = $fieldDescription->getOption('form_field_widget', 'Symfony\\Component\\Form\\ChoiceField');
  266. // set valid default value
  267. if ($class == 'Symfony\\Component\\Form\\ChoiceField') {
  268. $choices = array();
  269. if($fieldDescription->getOption('add_empty', false)) {
  270. $choices = array(
  271. $fieldDescription->getOption('add_empty_value', '') => $fieldDescription->getOption('add_empty_value', '')
  272. );
  273. }
  274. $options = array_merge(array(
  275. 'expanded' => false,
  276. 'choices' => $this->getChoices($fieldDescription, $choices),
  277. ), $options);
  278. }
  279. return new $class($fieldDescription->getFieldName(), $options);
  280. */
  281. }
  282. /**
  283. * return the OneToMany associated field
  284. *
  285. * @param $object
  286. * @param FieldDescription $fieldDescription
  287. * @return ChoiceField|CollectionField
  288. */
  289. protected function getOneToManyField($object, FieldDescription $fieldDescription)
  290. {
  291. /*
  292. UNUSED RIGHT NOW
  293. if ($fieldDescription->getOption('edit') == 'inline') {
  294. $prototype = $this->getRelatedAssociatedField($object, $fieldDescription);
  295. $value = $fieldDescription->getValue($object);
  296. // add new instances if the min number is not matched
  297. if ($fieldDescription->getOption('min', 0) > count($value)) {
  298. $diff = $fieldDescription->getOption('min', 0) - count($value);
  299. foreach (range(1, $diff) as $i) {
  300. $this->addNewInstance($object, $fieldDescription);
  301. }
  302. }
  303. // use custom one to expose the newfield method
  304. return new \Sonata\BaseApplicationBundle\Form\EditableCollectionField($prototype);
  305. }
  306. return $this->getManyToManyField($object, $fieldDescription);
  307. */
  308. }
  309. protected function getManyToManyField($object, FieldDescription $fieldDescription)
  310. {
  311. /*
  312. UNUSED RIGHT NOW
  313. $options = array(
  314. 'value_transformer' => new \Symfony\Bundle\DoctrineBundle\Form\ValueTransformer\CollectionToChoiceTransformer(array(
  315. 'em' => $this->getEntityManager(),
  316. 'className' => $fieldDescription->getTargetEntity()
  317. ))
  318. );
  319. $options = array_merge($options, $fieldDescription->getOption('form_field_options', array()));
  320. $class = $fieldDescription->getOption('form_field_widget', 'Symfony\\Component\\Form\\ChoiceField');
  321. // set valid default value
  322. if ($class == 'Symfony\\Component\\Form\\ChoiceField') {
  323. $choices = array();
  324. if($fieldDescription->getOption('add_empty', false)) {
  325. $choices = array(
  326. $fieldDescription->getOption('add_empty_value', '') => $fieldDescription->getOption('add_empty_value', '')
  327. );
  328. }
  329. $options = array_merge(array(
  330. 'expanded' => true,
  331. 'multiple' => true,
  332. 'choices' => $this->getChoices($fieldDescription, $choices),
  333. ), $options);
  334. }
  335. return new $class($fieldDescription->getFieldName(), $options);
  336. */
  337. }
  338. protected function getManyToOneField($object, FieldDescription $fieldDescription)
  339. {
  340. /*
  341. UNUSED RIGHT NOW
  342. // tweak the widget depend on the edit mode
  343. if ($fieldDescription->getOption('edit') == 'inline') {
  344. return $this->getRelatedAssociatedField($object, $fieldDescription);
  345. }
  346. $options = array(
  347. 'value_transformer' => new \Symfony\Bundle\DoctrineBundle\Form\ValueTransformer\EntityToIDTransformer(array(
  348. 'em' => $this->getEntityManager(),
  349. 'className' => $fieldDescription->getTargetEntity()
  350. ))
  351. );
  352. $options = array_merge($options, $fieldDescription->getOption('form_field_options', array()));
  353. if ($fieldDescription->getOption('edit') == 'list') {
  354. return new \Symfony\Component\Form\TextField($fieldDescription->getFieldName(), $options);
  355. }
  356. $class = $fieldDescription->getOption('form_field_widget', 'Symfony\\Component\\Form\\ChoiceField');
  357. // set valid default value
  358. if ($class == 'Symfony\\Component\\Form\\ChoiceField') {
  359. $choices = array();
  360. if($fieldDescription->getOption('add_empty', false)) {
  361. $choices = array(
  362. $fieldDescription->getOption('add_empty_value', '') => $fieldDescription->getOption('add_empty_value', '')
  363. );
  364. }
  365. $options = array_merge(array(
  366. 'expanded' => false,
  367. 'choices' => $this->getChoices($fieldDescription, $choices),
  368. ), $options);
  369. }
  370. return new $class($fieldDescription->getFieldName(), $options);
  371. */
  372. }
  373. protected function getFormFieldInstance($object, FieldDescription $fieldDescription)
  374. {
  375. /*
  376. UNUSED RIGHT NOW
  377. switch ($fieldDescription->getType()) {
  378. case ClassMetadataInfo::ONE_TO_MANY:
  379. return $this->getOneToManyField($object, $fieldDescription);
  380. case ClassMetadataInfo::MANY_TO_MANY:
  381. return $this->getManyToManyField($object, $fieldDescription);
  382. case ClassMetadataInfo::MANY_TO_ONE:
  383. return $this->getManyToOneField($object, $fieldDescription);
  384. case ClassMetadataInfo::ONE_TO_ONE:
  385. return $this->getOneToOneField($object, $fieldDescription);
  386. default:
  387. $class = $this->getFormFieldClass($fieldDescription);
  388. $options = $fieldDescription->getOption('form_field_options', array());
  389. return new $class($fieldDescription->getFieldName(), $options);
  390. }
  391. */
  392. }
  393. }