Admin.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  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 Bundle\BaseApplicationBundle\Admin;
  11. use Symfony\Component\DependencyInjection\ContainerAware;
  12. use Symfony\Component\Form\Form;
  13. abstract class Admin extends ContainerAware
  14. {
  15. protected $class;
  16. protected $list_fields = false;
  17. protected $form_fields = false;
  18. protected $filter_fields = array(); // by default there is no filter
  19. protected $filter_datagrid;
  20. protected $max_per_page = 25;
  21. protected $base_route = '';
  22. protected $base_controller_name;
  23. // note : don't like this, but havn't find a better way to do it
  24. protected $configuration_pool;
  25. protected $code;
  26. protected $label;
  27. public function configure()
  28. {
  29. $this->buildFormFields();
  30. $this->buildListFields();
  31. }
  32. public function getClass()
  33. {
  34. return $this->class;
  35. }
  36. public function getEntityManager()
  37. {
  38. return $this->container->get('doctrine.orm.default_entity_manager');
  39. }
  40. public function getClassMetaData()
  41. {
  42. $em = $this->getEntityManager();
  43. return $em->getClassMetaData($this->getClass());
  44. }
  45. public function getBatchActions()
  46. {
  47. return array(
  48. 'delete' => 'action_delete'
  49. );
  50. }
  51. public function getUrls()
  52. {
  53. return array(
  54. 'list' => array(
  55. 'url' => $this->base_route.'_list',
  56. 'params' => array(),
  57. ),
  58. 'create' => array(
  59. 'url' => $this->base_route.'_create',
  60. 'params' => array(),
  61. ),
  62. 'update' => array(
  63. 'url' => $this->base_route.'_update',
  64. 'params' => array()
  65. ),
  66. 'delete' => array(
  67. 'url' => $this->base_route.'_delete',
  68. 'params' => array()
  69. ),
  70. 'edit' => array(
  71. 'url' => $this->base_route.'_edit',
  72. 'params' => array()
  73. ),
  74. 'batch' => array(
  75. 'url' => $this->base_route.'_batch',
  76. 'params' => array()
  77. )
  78. );
  79. }
  80. public function getUrl($name)
  81. {
  82. $urls = $this->getUrls();
  83. if(!isset($urls[$name])) {
  84. return false;
  85. }
  86. return $urls[$name];
  87. }
  88. public function generateUrl($name, $params = array())
  89. {
  90. $url = $this->getUrl($name);
  91. if(!$url) {
  92. throw new \RuntimeException(sprintf('unable to find the url `%s`', $name));
  93. }
  94. if(!is_array($params)) {
  95. $params = array();
  96. }
  97. return $this->container->get('router')->generate($url['url'], array_merge($url['params'], $params));
  98. }
  99. public function getListTemplate()
  100. {
  101. return 'BaseApplicationBundle:CRUD:list.twig';
  102. }
  103. public function getEditTemplate()
  104. {
  105. return 'BaseApplicationBundle:CRUD:edit.twig';
  106. }
  107. public function getReflectionFields()
  108. {
  109. return $this->getClassMetaData()->reflFields;
  110. }
  111. /**
  112. * make sure the base field are set in the correct format
  113. *
  114. * @param $selected_fields
  115. * @return array
  116. */
  117. static public function getBaseFields($metadata, $selected_fields)
  118. {
  119. // if nothing is defined we display all fields
  120. if(!$selected_fields) {
  121. $selected_fields = array_keys($metadata->reflFields) + array_keys($metadata->associationMappings);
  122. }
  123. // make sure we works with array
  124. foreach($selected_fields as $name => $options) {
  125. if(is_array($options)) {
  126. $fields[$name] = $options;
  127. } else {
  128. $fields[$options] = array();
  129. $name = $options;
  130. }
  131. if(isset($metadata->fieldMappings[$name])) {
  132. $fields[$name] = array_merge(
  133. $metadata->fieldMappings[$name],
  134. $fields[$name]
  135. );
  136. }
  137. if(isset($metadata->associationMappings[$name])) {
  138. $fields[$name] = array_merge(
  139. $metadata->associationMappings[$name],
  140. $fields[$name]
  141. );
  142. }
  143. if(isset($metadata->reflFields[$name])) {
  144. $fields[$name]['reflection'] =& $metadata->reflFields[$name];
  145. }
  146. }
  147. return $fields;
  148. }
  149. /**
  150. * @return void
  151. */
  152. public function configureFormFields()
  153. {
  154. }
  155. /**
  156. * return the target objet
  157. *
  158. * @param $id
  159. * @return
  160. */
  161. public function getObject($id)
  162. {
  163. return $this->getEntityManager()
  164. ->find($this->getClass(), $id);
  165. }
  166. /**
  167. * build the fields to use in the form
  168. *
  169. * @throws RuntimeException
  170. * @return
  171. */
  172. public function buildFormFields()
  173. {
  174. $this->form_fields = self::getBaseFields($this->getClassMetaData(), $this->form_fields);
  175. foreach($this->form_fields as $name => $options) {
  176. if(!isset($this->form_fields[$name]['type'])) {
  177. throw new \RuntimeException(sprintf('You must declare a type for the field `%s`', $name));
  178. }
  179. // make sure the options field is set
  180. if(!isset($this->form_fields[$name]['options'])) {
  181. $this->form_fields[$name]['options'] = array();
  182. }
  183. // fix template value for doctrine association fields
  184. if(!isset($this->form_fields[$name]['template']) && isset($this->form_fields[$name]['type'])) {
  185. $this->form_fields[$name]['template'] = sprintf('BaseApplicationBundle:CRUD:edit_%s.twig', $this->form_fields[$name]['type']);
  186. if($this->form_fields[$name]['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::ONE_TO_ONE)
  187. {
  188. $this->form_fields[$name]['template'] = 'BaseApplicationBundle:CRUD:edit_one_to_one.twig';
  189. }
  190. if($this->form_fields[$name]['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_ONE)
  191. {
  192. $this->form_fields[$name]['template'] = 'BaseApplicationBundle:CRUD:edit_many_to_one.twig';
  193. $this->form_fields[$name]['configuration'] = $this->getConfigurationPool()
  194. ->getConfigurationByClass($this->form_fields[$name]['targetEntity']);
  195. }
  196. if($this->form_fields[$name]['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY)
  197. {
  198. $this->form_fields[$name]['template'] = 'BaseApplicationBundle:CRUD:edit_many_to_many.twig';
  199. $this->form_fields[$name]['configuration'] = $this->getConfigurationPool()
  200. ->getConfigurationByClass($this->form_fields[$name]['targetEntity']);
  201. }
  202. }
  203. // set correct default value
  204. if($this->form_fields[$name]['type'] == 'datetime') {
  205. if(!isset($this->form_fields[$name]['options']['date_widget'])) {
  206. $this->form_fields[$name]['options']['date_widget'] = \Symfony\Component\Form\DateField::CHOICE;
  207. }
  208. if(!isset($this->form_fields[$name]['options']['years'])) {
  209. $this->form_fields[$name]['options']['years'] = range(1900, 2100);
  210. }
  211. }
  212. // unset the identifier field as it is not required to update an object
  213. if(isset($this->form_fields[$name]['id'])) {
  214. unset($this->form_fields[$name]);
  215. }
  216. }
  217. $this->configureFormFields();
  218. return $this->form_fields;
  219. }
  220. /**
  221. * build the field to use in the list view
  222. *
  223. * @return void
  224. */
  225. public function buildListFields()
  226. {
  227. $this->list_fields = self::getBaseFields($this->getClassMetaData(), $this->list_fields);
  228. foreach($this->list_fields as $name => $options) {
  229. $this->list_fields[$name]['code'] = $name;
  230. // set the label if none is set
  231. if(!isset($this->list_fields[$name]['label']))
  232. {
  233. $this->list_fields[$name]['label'] = $name;
  234. }
  235. // set the default type if none is set
  236. if(!isset($this->list_fields[$name]['type'])) {
  237. $this->list_fields[$name]['type'] = 'string';
  238. }
  239. // fix template for mapping
  240. if($this->list_fields[$name]['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_ONE) {
  241. $this->list_fields[$name]['template'] = 'BaseApplicationBundle:CRUD:list_many_to_one.twig';
  242. $this->list_fields[$name]['configuration'] = $this->getConfigurationPool()
  243. ->getConfigurationByClass($this->list_fields[$name]['targetEntity']);
  244. }
  245. if($this->list_fields[$name]['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::ONE_TO_MANY) {
  246. $this->list_fields[$name]['template'] = 'BaseApplicationBundle:CRUD:list_one_to_many.twig';
  247. }
  248. if($this->list_fields[$name]['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY) {
  249. $this->list_fields[$name]['template'] = 'BaseApplicationBundle:CRUD:list_many_to_many.twig';
  250. }
  251. // define the default template
  252. if(!isset($this->list_fields[$name]['template'])) {
  253. $this->list_fields[$name]['template'] = sprintf('BaseApplicationBundle:CRUD:list_%s.twig', $this->list_fields[$name]['type']);
  254. }
  255. // define the default template for identifier field
  256. if(isset($this->list_fields[$name]['id'])) {
  257. $this->list_fields[$name]['template'] = 'BaseApplicationBundle:CRUD:list_identifier.twig';
  258. }
  259. }
  260. if(!isset($this->list_fields['_batch'])) {
  261. $this->list_fields = array('_batch' => array(
  262. 'code' => '_batch',
  263. 'template' => 'BaseApplicationBundle:CRUD:list__batch.twig',
  264. 'label' => 'batch',
  265. ) ) + $this->list_fields;
  266. }
  267. $this->configureListFields();
  268. return $this->list_fields;
  269. }
  270. public function configureListFields()
  271. {
  272. }
  273. public function configureFilterFields()
  274. {
  275. }
  276. public function getFilterDatagrid()
  277. {
  278. if(!$this->filter_datagrid) {
  279. $this->filter_datagrid = new \Bundle\BaseApplicationBundle\Tool\Datagrid(
  280. $this->getClass(),
  281. $this->getEntityManager()
  282. );
  283. $this->filter_datagrid->setMaxPerPage($this->max_per_page);
  284. $this->configureFilterFields();
  285. $this->filter_datagrid->setFilterFields($this->filter_fields);
  286. $this->filter_datagrid->buildFilterFields();
  287. }
  288. return $this->filter_datagrid;
  289. }
  290. /**
  291. * Construct and build the form field definitions
  292. *
  293. * @return list form field definition
  294. */
  295. public function getFormFields()
  296. {
  297. return $this->form_fields;
  298. }
  299. public function getListFields()
  300. {
  301. return $this->list_fields;
  302. }
  303. public function getChoices($description)
  304. {
  305. $targets = $this->getEntityManager()
  306. ->createQueryBuilder()
  307. ->select('t')
  308. ->from($description['targetEntity'], 't')
  309. ->getQuery()
  310. ->execute();
  311. $choices = array();
  312. foreach($targets as $target) {
  313. // todo : puts this into a configuration option and use reflection
  314. foreach(array('getTitle', 'getName', '__toString') as $getter) {
  315. if(method_exists($target, $getter)) {
  316. $choices[$target->getId()] = $target->$getter();
  317. break;
  318. }
  319. }
  320. }
  321. return $choices;
  322. }
  323. public function getForm($object, $fields)
  324. {
  325. $this->container->get('session')->start();
  326. $form = new Form('data', $object, $this->container->get('validator'));
  327. foreach($fields as $name => $description) {
  328. if(!isset($description['type'])) {
  329. continue;
  330. }
  331. switch($description['type']) {
  332. case \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY:
  333. $transformer = new \Symfony\Bundle\DoctrineBundle\Form\ValueTransformer\CollectionToChoiceTransformer(array(
  334. 'em' => $this->getEntityManager(),
  335. 'className' => $description['targetEntity']
  336. ));
  337. $field = new \Symfony\Component\Form\ChoiceField($name, array_merge(array(
  338. 'expanded' => true,
  339. 'multiple' => true,
  340. 'choices' => $this->getChoices($description),
  341. 'value_transformer' => $transformer,
  342. ), $description['options']));
  343. break;
  344. case \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_ONE:
  345. case \Doctrine\ORM\Mapping\ClassMetadataInfo::ONE_TO_ONE:
  346. $transformer = new \Symfony\Bundle\DoctrineBundle\Form\ValueTransformer\EntityToIDTransformer(array(
  347. 'em' => $this->getEntityManager(),
  348. 'className' => $description['targetEntity']
  349. ));
  350. $field = new \Symfony\Component\Form\ChoiceField($name, array_merge(array(
  351. 'expanded' => false,
  352. 'choices' => $this->getChoices($description),
  353. 'value_transformer' => $transformer,
  354. ), $description['options']));
  355. break;
  356. case 'string':
  357. $field = new \Symfony\Component\Form\TextField($name, $description['options']);
  358. break;
  359. case 'text':
  360. $field = new \Symfony\Component\Form\TextareaField($name, $description['options']);
  361. break;
  362. case 'boolean':
  363. $field = new \Symfony\Component\Form\CheckboxField($name, $description['options']);
  364. break;
  365. case 'integer':
  366. $field = new \Symfony\Component\Form\IntegerField($name, $description['options']);
  367. break;
  368. case 'decimal':
  369. $field = new \Symfony\Component\Form\NumberField($name, $description['options']);
  370. break;
  371. case 'datetime':
  372. $field = new \Symfony\Component\Form\DateTimeField($name, $description['options']);
  373. break;
  374. case 'date':
  375. $field = new \Symfony\Component\Form\DateField($name, $description['options']);
  376. break;
  377. case 'choice':
  378. $field = new \Symfony\Component\Form\ChoiceField($name, $description['options']);
  379. break;
  380. case 'array':
  381. $field = new \Symfony\Component\Form\FieldGroup($name, $description['options']);
  382. $values = $description['reflection']->getValue($object);
  383. foreach((array)$values as $k => $v) {
  384. $field->add(new \Symfony\Component\Form\TextField($k));
  385. }
  386. break;
  387. default:
  388. throw new \RuntimeException(sprintf('unknow type `%s`', $description['type']));
  389. }
  390. $form->add($field);
  391. }
  392. return $form;
  393. }
  394. public function setBaseControllerName($base_controller_name)
  395. {
  396. $this->base_controller_name = $base_controller_name;
  397. }
  398. public function getBaseControllerName()
  399. {
  400. return $this->base_controller_name;
  401. }
  402. public function setBaseRoute($base_route)
  403. {
  404. $this->base_route = $base_route;
  405. }
  406. public function getBaseRoute()
  407. {
  408. return $this->base_route;
  409. }
  410. public function setConfigurationPool($configuration_pool)
  411. {
  412. $this->configuration_pool = $configuration_pool;
  413. }
  414. public function getConfigurationPool()
  415. {
  416. return $this->configuration_pool;
  417. }
  418. public function setCode($code)
  419. {
  420. $this->code = $code;
  421. }
  422. public function getCode()
  423. {
  424. return $this->code;
  425. }
  426. public function setLabel($label)
  427. {
  428. $this->label = $label;
  429. }
  430. public function getLabel()
  431. {
  432. return $this->label;
  433. }
  434. public function setFilterFields($filter_fields)
  435. {
  436. $this->filter_fields = $filter_fields;
  437. }
  438. public function getFilterFields()
  439. {
  440. return $this->filter_fields;
  441. }
  442. public function setMaxPerPage($max_per_page)
  443. {
  444. $this->max_per_page = $max_per_page;
  445. }
  446. public function getMaxPerPage()
  447. {
  448. return $this->max_per_page;
  449. }
  450. }