CRUDController.php 17 KB

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