Admin.php 18 KB

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