Admin.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  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. public function getNewInstance()
  159. {
  160. $class = $this->getClass();
  161. return new $class;
  162. }
  163. /**
  164. * return the target objet
  165. *
  166. * @param $id
  167. * @return
  168. */
  169. public function getObject($id)
  170. {
  171. return $this->getEntityManager()
  172. ->find($this->getClass(), $id);
  173. }
  174. public function buildFormGroups()
  175. {
  176. if(!$this->form_groups) {
  177. $this->form_groups = array(
  178. false => array('fields' => array_keys($this->form_fields))
  179. );
  180. return;
  181. }
  182. // normalize array
  183. foreach($this->form_groups as $name => &$group) {
  184. if(!isset($group['collapsed'])) {
  185. $group['collapsed'] = false;
  186. }
  187. }
  188. }
  189. public function preUpdate($object)
  190. {
  191. }
  192. public function postUpdate($object)
  193. {
  194. }
  195. public function preInsert($object)
  196. {
  197. }
  198. public function postInsert($object)
  199. {
  200. }
  201. /**
  202. * build the fields to use in the form
  203. *
  204. * @throws RuntimeException
  205. * @return
  206. */
  207. public function buildFormFields()
  208. {
  209. $this->form_fields = self::getBaseFields($this->getClassMetaData(), $this->form_fields);
  210. foreach($this->form_fields as $name => $options) {
  211. if(!isset($this->form_fields[$name]['type'])) {
  212. throw new \RuntimeException(sprintf('You must declare a type for the field `%s`', $name));
  213. }
  214. // make sure the options field is set
  215. if(!isset($this->form_fields[$name]['fieldName'])) {
  216. $this->form_fields[$name]['fieldName'] = $name;
  217. }
  218. // make sure the options field is set
  219. if(!isset($this->form_fields[$name]['options'])) {
  220. $this->form_fields[$name]['options'] = array();
  221. }
  222. // fix template value for doctrine association fields
  223. if(!isset($this->form_fields[$name]['template']) && isset($this->form_fields[$name]['type'])) {
  224. $this->form_fields[$name]['template'] = sprintf('Sonata\BaseApplicationBundle:CRUD:edit_%s.twig', $this->form_fields[$name]['type']);
  225. if($this->form_fields[$name]['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::ONE_TO_ONE)
  226. {
  227. $this->form_fields[$name]['template'] = 'Sonata\BaseApplicationBundle:CRUD:edit_one_to_one.twig';
  228. $this->form_fields[$name]['configuration'] = $this->getConfigurationPool()
  229. ->getConfigurationByClass($this->form_fields[$name]['targetEntity']);
  230. }
  231. if($this->form_fields[$name]['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_ONE)
  232. {
  233. $this->form_fields[$name]['template'] = 'Sonata\BaseApplicationBundle:CRUD:edit_many_to_one.twig';
  234. $this->form_fields[$name]['configuration'] = $this->getConfigurationPool()
  235. ->getConfigurationByClass($this->form_fields[$name]['targetEntity']);
  236. }
  237. if($this->form_fields[$name]['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY)
  238. {
  239. $this->form_fields[$name]['template'] = 'Sonata\BaseApplicationBundle:CRUD:edit_many_to_many.twig';
  240. $this->form_fields[$name]['configuration'] = $this->getConfigurationPool()
  241. ->getConfigurationByClass($this->form_fields[$name]['targetEntity']);
  242. }
  243. if($this->form_fields[$name]['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::ONE_TO_MANY)
  244. {
  245. $this->form_fields[$name]['template'] = 'Sonata\BaseApplicationBundle:CRUD:edit_one_to_many.twig';
  246. $this->form_fields[$name]['configuration'] = $this->getConfigurationPool()
  247. ->getConfigurationByClass($this->form_fields[$name]['targetEntity']);
  248. }
  249. }
  250. // set correct default value
  251. if($this->form_fields[$name]['type'] == 'datetime') {
  252. if(!isset($this->form_fields[$name]['options']['date_widget'])) {
  253. $this->form_fields[$name]['options']['date_widget'] = \Symfony\Component\Form\DateField::CHOICE;
  254. }
  255. if(!isset($this->form_fields[$name]['options']['years'])) {
  256. $this->form_fields[$name]['options']['years'] = range(1900, 2100);
  257. }
  258. }
  259. // unset the identifier field as it is not required to update an object
  260. if(isset($this->form_fields[$name]['id'])) {
  261. unset($this->form_fields[$name]);
  262. }
  263. }
  264. $this->configureFormFields();
  265. return $this->form_fields;
  266. }
  267. /**
  268. * build the field to use in the list view
  269. *
  270. * @return void
  271. */
  272. public function buildListFields()
  273. {
  274. $this->list_fields = self::getBaseFields($this->getClassMetaData(), $this->list_fields);
  275. $this->configureListFields();
  276. // normalize field
  277. foreach($this->list_fields as $name => $options) {
  278. $this->list_fields[$name]['code'] = $name;
  279. // set the label if none is set
  280. if(!isset($this->list_fields[$name]['label']))
  281. {
  282. $this->list_fields[$name]['label'] = $name;
  283. }
  284. // set the default type if none is set
  285. if(!isset($this->list_fields[$name]['type'])) {
  286. $this->list_fields[$name]['type'] = 'string';
  287. }
  288. // fix template for mapping
  289. if($this->list_fields[$name]['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_ONE) {
  290. $this->list_fields[$name]['template'] = 'Sonata/BaseApplicationBundle:CRUD:list_many_to_one.twig';
  291. $this->list_fields[$name]['configuration'] = $this->getConfigurationPool()
  292. ->getConfigurationByClass($this->list_fields[$name]['targetEntity']);
  293. }
  294. // fix template for mapping
  295. if($this->list_fields[$name]['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::ONE_TO_ONE) {
  296. $this->list_fields[$name]['template'] = 'Sonata/BaseApplicationBundle:CRUD:list_one_to_one.twig';
  297. $this->list_fields[$name]['configuration'] = $this->getConfigurationPool()
  298. ->getConfigurationByClass($this->list_fields[$name]['targetEntity']);
  299. }
  300. if($this->list_fields[$name]['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::ONE_TO_MANY) {
  301. $this->list_fields[$name]['template'] = 'Sonata/BaseApplicationBundle:CRUD:list_one_to_many.twig';
  302. }
  303. if($this->list_fields[$name]['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY) {
  304. $this->list_fields[$name]['template'] = 'Sonata/BaseApplicationBundle:CRUD:list_many_to_many.twig';
  305. }
  306. // define the default template
  307. if(!isset($this->list_fields[$name]['template'])) {
  308. $this->list_fields[$name]['template'] = sprintf('Sonata/BaseApplicationBundle:CRUD:list_%s.twig', $this->list_fields[$name]['type']);
  309. }
  310. // define the default template for identifier field
  311. if(isset($this->list_fields[$name]['id'])) {
  312. $this->list_fields[$name]['identifier'] = true;
  313. }
  314. if(!isset($this->list_fields[$name]['identifier'])) {
  315. $this->list_fields[$name]['identifier'] = false;
  316. }
  317. }
  318. if(!isset($this->list_fields['_batch'])) {
  319. $this->list_fields = array('_batch' => array(
  320. 'code' => '_batch',
  321. 'template' => 'Sonata/BaseApplicationBundle:CRUD:list__batch.twig',
  322. 'label' => 'batch',
  323. 'identifier' => false
  324. ) ) + $this->list_fields;
  325. }
  326. return $this->list_fields;
  327. }
  328. public function configureListFields()
  329. {
  330. }
  331. public function configureFilterFields()
  332. {
  333. }
  334. public function getFilterDatagrid()
  335. {
  336. if(!$this->filter_datagrid) {
  337. $this->filter_datagrid = new Datagrid(
  338. $this->getClass(),
  339. $this->getEntityManager()
  340. );
  341. $this->filter_datagrid->setMaxPerPage($this->max_per_page);
  342. $this->configureFilterFields();
  343. $this->filter_datagrid->setFilterFields($this->filter_fields);
  344. $this->filter_datagrid->buildFilterFields();
  345. }
  346. return $this->filter_datagrid;
  347. }
  348. /**
  349. * Construct and build the form field definitions
  350. *
  351. * @return list form field definition
  352. */
  353. public function getFormFields()
  354. {
  355. return $this->form_fields;
  356. }
  357. public function getListFields()
  358. {
  359. return $this->list_fields;
  360. }
  361. public function getChoices($description)
  362. {
  363. $targets = $this->getEntityManager()
  364. ->createQueryBuilder()
  365. ->select('t')
  366. ->from($description['targetEntity'], 't')
  367. ->getQuery()
  368. ->execute();
  369. $choices = array();
  370. foreach($targets as $target) {
  371. // todo : puts this into a configuration option and use reflection
  372. foreach(array('getTitle', 'getName', '__toString') as $getter) {
  373. if(method_exists($target, $getter)) {
  374. $choices[$target->getId()] = $target->$getter();
  375. break;
  376. }
  377. }
  378. }
  379. return $choices;
  380. }
  381. public function getForm($object, $fields)
  382. {
  383. $this->container->get('session')->start();
  384. $form = new Form('data', $object, $this->container->get('validator'));
  385. foreach($fields as $name => $description) {
  386. if(!isset($description['type'])) {
  387. continue;
  388. }
  389. switch($description['type']) {
  390. case \Doctrine\ORM\Mapping\ClassMetadataInfo::ONE_TO_MANY:
  391. case \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY:
  392. $transformer = new \Symfony\Bundle\DoctrineBundle\Form\ValueTransformer\CollectionToChoiceTransformer(array(
  393. 'em' => $this->getEntityManager(),
  394. 'className' => $description['targetEntity']
  395. ));
  396. $field = new \Symfony\Component\Form\ChoiceField($name, array_merge(array(
  397. 'expanded' => true,
  398. 'multiple' => true,
  399. 'choices' => $this->getChoices($description),
  400. 'value_transformer' => $transformer,
  401. ), $description['options']));
  402. break;
  403. case \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_ONE:
  404. case \Doctrine\ORM\Mapping\ClassMetadataInfo::ONE_TO_ONE:
  405. $transformer = new \Symfony\Bundle\DoctrineBundle\Form\ValueTransformer\EntityToIDTransformer(array(
  406. 'em' => $this->getEntityManager(),
  407. 'className' => $description['targetEntity']
  408. ));
  409. $field = new \Symfony\Component\Form\ChoiceField($name, array_merge(array(
  410. 'expanded' => false,
  411. 'choices' => $this->getChoices($description),
  412. 'value_transformer' => $transformer,
  413. ), $description['options']));
  414. break;
  415. case 'string':
  416. $field = new \Symfony\Component\Form\TextField($name, $description['options']);
  417. break;
  418. case 'text':
  419. $field = new \Symfony\Component\Form\TextareaField($name, $description['options']);
  420. break;
  421. case 'boolean':
  422. $field = new \Symfony\Component\Form\CheckboxField($name, $description['options']);
  423. break;
  424. case 'integer':
  425. $field = new \Symfony\Component\Form\IntegerField($name, $description['options']);
  426. break;
  427. case 'decimal':
  428. $field = new \Symfony\Component\Form\NumberField($name, $description['options']);
  429. break;
  430. case 'datetime':
  431. $field = new \Symfony\Component\Form\DateTimeField($name, $description['options']);
  432. break;
  433. case 'date':
  434. $field = new \Symfony\Component\Form\DateField($name, $description['options']);
  435. break;
  436. case 'choice':
  437. $field = new \Symfony\Component\Form\ChoiceField($name, $description['options']);
  438. break;
  439. case 'array':
  440. $field = new \Symfony\Component\Form\FieldGroup($name, $description['options']);
  441. $values = $description['reflection']->getValue($object);
  442. foreach((array)$values as $k => $v) {
  443. $field->add(new \Symfony\Component\Form\TextField($k));
  444. }
  445. break;
  446. default:
  447. throw new \RuntimeException(sprintf('unknow type `%s`', $description['type']));
  448. }
  449. $form->add($field);
  450. }
  451. return $form;
  452. }
  453. public function setBaseControllerName($base_controller_name)
  454. {
  455. $this->base_controller_name = $base_controller_name;
  456. }
  457. public function getBaseControllerName()
  458. {
  459. return $this->base_controller_name;
  460. }
  461. public function setBaseRoute($base_route)
  462. {
  463. $this->base_route = $base_route;
  464. }
  465. public function getBaseRoute()
  466. {
  467. return $this->base_route;
  468. }
  469. public function setConfigurationPool($configuration_pool)
  470. {
  471. $this->configuration_pool = $configuration_pool;
  472. }
  473. public function getConfigurationPool()
  474. {
  475. return $this->configuration_pool;
  476. }
  477. public function setCode($code)
  478. {
  479. $this->code = $code;
  480. }
  481. public function getCode()
  482. {
  483. return $this->code;
  484. }
  485. public function setLabel($label)
  486. {
  487. $this->label = $label;
  488. }
  489. public function getLabel()
  490. {
  491. return $this->label;
  492. }
  493. public function setFilterFields($filter_fields)
  494. {
  495. $this->filter_fields = $filter_fields;
  496. }
  497. public function getFilterFields()
  498. {
  499. return $this->filter_fields;
  500. }
  501. public function setMaxPerPage($max_per_page)
  502. {
  503. $this->max_per_page = $max_per_page;
  504. }
  505. public function getMaxPerPage()
  506. {
  507. return $this->max_per_page;
  508. }
  509. public function setFormGroups($form_groups)
  510. {
  511. $this->form_groups = $form_groups;
  512. }
  513. public function getFormGroups()
  514. {
  515. return $this->form_groups;
  516. }
  517. }