architecture.rst 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. Architecture
  2. ============
  3. The architecture of the ``SonataAdminBundle`` is primarily inspired by the Django Admin
  4. Project, which is truly a great project. More information can be found at the
  5. `Django Project Website`_.
  6. If you followed the instructions on the :doc:`getting_started` page, you should by
  7. now have an ``Admin`` class and an ``Admin`` service. In this chapter, we'll discuss more in
  8. depth how it works.
  9. The Admin Class
  10. ---------------
  11. The ``Admin`` class maps a specific model to the rich CRUD interface provided by
  12. ``SonataAdminBundle``. In other words, using your ``Admin`` classes, you can configure
  13. what is shown by ``SonataAdminBundle`` in each CRUD action for the associated model.
  14. By now you've seen 3 of those actions in the ``getting started`` page: list,
  15. filter and form (for creation/editing). However, a fully configured ``Admin`` class
  16. can define more actions:
  17. ============= =========================================================================
  18. Actions Description
  19. ============= =========================================================================
  20. list The fields displayed in the list table
  21. filter The fields available for filtering the list
  22. form The fields used to create/edit the entity
  23. show The fields used to show the entity
  24. batch actions Actions that can be performed on a group of entities (e.g. bulk delete)
  25. ============= =========================================================================
  26. The ``Sonata\AdminBundle\Admin\AbstractAdmin`` class is provided as an easy way to
  27. map your models, by extending it. However, any implementation of the
  28. ``Sonata\AdminBundle\Admin\AdminInterface`` can be used to define an ``Admin``
  29. service. For each ``Admin`` service, the following required dependencies are
  30. automatically injected by the bundle:
  31. ========================= =========================================================================
  32. Class Description
  33. ========================= =========================================================================
  34. ConfigurationPool configuration pool where all Admin class instances are stored
  35. ModelManager service which handles specific code relating to your persistence layer (e.g. Doctrine ORM)
  36. FormContractor builds the forms for the edit/create views using the Symfony ``FormBuilder``
  37. ShowBuilder builds the show fields
  38. ListBuilder builds the list fields
  39. DatagridBuilder builds the filter fields
  40. Request the received http request
  41. RouteBuilder allows you to add routes for new actions and remove routes for default actions
  42. RouterGenerator generates the different URLs
  43. SecurityHandler handles permissions for model instances and actions
  44. Validator handles model validation
  45. Translator generates translations
  46. LabelTranslatorStrategy a strategy to use when generating labels
  47. MenuFactory generates the side menu, depending on the current action
  48. ========================= =========================================================================
  49. .. note::
  50. Each of these dependencies is used for a specific task, briefly described above.
  51. If you wish to learn more about how they are used, check the respective documentation
  52. chapter. In most cases, you won't need to worry about their underlying implementation.
  53. All of these dependencies have default values that you can override when declaring any of
  54. your ``Admin`` services. This is done using a ``call`` to the matching ``setter`` :
  55. .. configuration-block::
  56. .. code-block:: xml
  57. <service id="app.admin.post" class="AppBundle\Admin\PostAdmin">
  58. <tag name="sonata.admin" manager_type="orm" group="Content" label="Post" />
  59. <argument />
  60. <argument>AppBundle\Entity\Post</argument>
  61. <argument />
  62. <call method="setLabelTranslatorStrategy">
  63. <argument type="service" id="sonata.admin.label.strategy.underscore" />
  64. </call>
  65. </service>
  66. .. code-block:: yaml
  67. services:
  68. app.admin.post:
  69. class: AppBundle\Admin\PostAdmin
  70. tags:
  71. - { name: sonata.admin, manager_type: orm, group: "Content", label: "Post" }
  72. arguments:
  73. - ~
  74. - AppBundle\Entity\Post
  75. - ~
  76. calls:
  77. - [ setLabelTranslatorStrategy, ["@sonata.admin.label.strategy.underscore"]]
  78. public: true
  79. Here, we declare the same ``Admin`` service as in the :doc:`getting_started` chapter, but using a
  80. different label translator strategy, replacing the default one. Notice that
  81. ``sonata.admin.label.strategy.underscore`` is a service provided by ``SonataAdminBundle``,
  82. but you could just as easily use a service of your own.
  83. CRUDController
  84. --------------
  85. The ``CRUDController`` contains the actions you have available to manipulate
  86. your model instances, like create, list, edit or delete. It uses the ``Admin``
  87. class to determine its behavior, like which fields to display in the edit form,
  88. or how to build the list view. Inside the ``CRUDController``, you can access the
  89. ``Admin`` class instance via the ``$admin`` variable.
  90. .. note::
  91. `CRUD`_ is an acronym for "Create, Read, Update and Delete"
  92. The ``CRUDController`` is no different from any other Symfony controller, meaning
  93. that you have all the usual options available to you, like getting services from
  94. the Dependency Injection Container (DIC).
  95. This is particularly useful if you decide to extend the ``CRUDController`` to
  96. add new actions or change the behavior of existing ones. You can specify which controller
  97. to use when declaring the ``Admin`` service by passing it as the 3rd argument. For example
  98. to set the controller to ``AppBundle:PostAdmin``:
  99. .. configuration-block::
  100. .. code-block:: xml
  101. <service id="app.admin.post" class="AppBundle\Admin\PostAdmin">
  102. <tag name="sonata.admin" manager_type="orm" group="Content" label="Post" />
  103. <argument />
  104. <argument>AppBundle\Entity\Post</argument>
  105. <argument>AppBundle:PostAdmin</argument>
  106. <call method="setTranslationDomain">
  107. <argument>AppBundle</argument>
  108. </call>
  109. </service>
  110. .. code-block:: yaml
  111. services:
  112. app.admin.post:
  113. class: AppBundle\Admin\PostAdmin
  114. tags:
  115. - { name: sonata.admin, manager_type: orm, group: "Content", label: "Post" }
  116. arguments:
  117. - ~
  118. - AppBundle\Entity\Post
  119. - AppBundle:PostAdmin
  120. calls:
  121. - [ setTranslationDomain, [AppBundle]]
  122. public: true
  123. When extending ``CRUDController``, remember that the ``Admin`` class already has
  124. a set of automatically injected dependencies that are useful when implementing several
  125. scenarios. Refer to the existing ``CRUDController`` actions for examples of how to get
  126. the best out of them.
  127. In your overloaded CRUDController you can overload also these methods to limit
  128. the number of duplicated code from SonataAdmin:
  129. * ``preCreate``: called from ``createAction``
  130. * ``preEdit``: called from ``editAction``
  131. * ``preDelete``: called from ``deleteAction``
  132. * ``preShow``: called from ``showAction``
  133. * ``preList``: called from ``listAction``
  134. These methods are called after checking the access rights and after retrieving the object
  135. from database. You can use them if you need to redirect user to some other page under certain conditions.
  136. Fields Definition
  137. -----------------
  138. Your ``Admin`` class defines which of your model's fields will be available in each
  139. action defined in your ``CRUDController``. So, for each action, a list of field mappings
  140. is generated. These lists are implemented using the ``FieldDescriptionCollection`` class
  141. which stores instances of ``FieldDescriptionInterface``. Picking up on our previous
  142. ``PostAdmin`` class example:
  143. .. code-block:: php
  144. <?php
  145. // src/AppBundle/Admin/PostAdmin.php
  146. namespace AppBundle\Admin;
  147. use Sonata\AdminBundle\Admin\AbstractAdmin;
  148. use Sonata\AdminBundle\Datagrid\ListMapper;
  149. use Sonata\AdminBundle\Datagrid\DatagridMapper;
  150. use Sonata\AdminBundle\Form\FormMapper;
  151. use Sonata\AdminBundle\Show\ShowMapper;
  152. class PostAdmin extends AbstractAdmin
  153. {
  154. // Fields to be shown on create/edit forms
  155. protected function configureFormFields(FormMapper $formMapper)
  156. {
  157. $formMapper
  158. ->add('title', 'text', array(
  159. 'label' => 'Post Title'
  160. ))
  161. ->add('author', 'entity', array(
  162. 'class' => 'AppBundle\Entity\User'
  163. ))
  164. // if no type is specified, SonataAdminBundle tries to guess it
  165. ->add('body')
  166. // ...
  167. ;
  168. }
  169. // Fields to be shown on filter forms
  170. protected function configureDatagridFilters(DatagridMapper $datagridMapper)
  171. {
  172. $datagridMapper
  173. ->add('title')
  174. ->add('author')
  175. ;
  176. }
  177. // Fields to be shown on lists
  178. protected function configureListFields(ListMapper $listMapper)
  179. {
  180. $listMapper
  181. ->addIdentifier('title')
  182. ->add('slug')
  183. ->add('author')
  184. ;
  185. }
  186. // Fields to be shown on show action
  187. protected function configureShowFields(ShowMapper $showMapper)
  188. {
  189. $showMapper
  190. ->add('id')
  191. ->add('title')
  192. ->add('slug')
  193. ->add('author')
  194. ;
  195. }
  196. }
  197. Internally, the provided ``Admin`` class will use these three functions to create three
  198. ``FieldDescriptionCollection`` instances:
  199. * ``$formFieldDescriptions``, containing three ``FieldDescriptionInterface`` instances
  200. for title, author and body
  201. * ``$filterFieldDescriptions``, containing two ``FieldDescriptionInterface`` instances
  202. for title and author
  203. * ``$listFieldDescriptions``, containing three ``FieldDescriptionInterface`` instances
  204. for title, slug and author
  205. * ``$showFieldDescriptions``, containing four ``FieldDescriptionInterface`` instances
  206. for id, title, slug and author
  207. The actual ``FieldDescription`` implementation is provided by the storage abstraction
  208. bundle that you choose during the installation process, based on the
  209. ``BaseFieldDescription`` abstract class provided by ``SonataAdminBundle``.
  210. Each ``FieldDescription`` contains various details about a field mapping. Some of
  211. them are independent of the action in which they are used, like ``name`` or ``type``,
  212. while others are used only in specific actions. More information can be found in the
  213. ``BaseFieldDescription`` class file.
  214. In most scenarios, you will not actually need to handle the ``FieldDescription`` yourself.
  215. However, it is important that you know it exists and how it is used, as it sits at the
  216. core of ``SonataAdminBundle``.
  217. Templates
  218. ---------
  219. Like most actions, ``CRUDController`` actions use view files to render their output.
  220. ``SonataAdminBundle`` provides ready to use views as well as ways to easily customize them.
  221. The current implementation uses ``Twig`` as the template engine. All templates
  222. are located in the ``Resources/views`` directory of the bundle.
  223. There are two base templates, one of these is ultimately used in every action:
  224. * ``SonataAdminBundle::standard_layout.html.twig``
  225. * ``SonataAdminBundle::ajax_layout.html.twig``
  226. Like the names say, one if for standard calls, the other one for AJAX.
  227. The subfolders include Twig files for specific sections of ``SonataAdminBundle``:
  228. Block:
  229. ``SonataBlockBundle`` block views. By default there is only one, which
  230. displays all the mapped classes on the dashboard
  231. Button:
  232. Buttons such as ``Add new`` or ``Delete`` that you can see across several
  233. CRUD actions
  234. CRUD:
  235. Base views for every CRUD action, plus several field views for each field type
  236. Core:
  237. Dashboard view, together with deprecated and stub twig files.
  238. Form:
  239. Views related to form rendering
  240. Helper:
  241. A view providing a short object description, as part of a specific form field
  242. type provided by ``SonataAdminBundle``
  243. Pager:
  244. Pagination related view files
  245. These will be discussed in greater detail in the specific :doc:`templates` section, where
  246. you will also find instructions on how to configure ``SonataAdminBundle`` to use your templates
  247. instead of the default ones.
  248. Managing ``Admin`` Service
  249. --------------------------
  250. Your ``Admin`` service definitions are parsed when Symfony is loaded, and handled by
  251. the ``Pool`` class. This class, available as the ``sonata.admin.pool`` service from the
  252. DIC, handles the ``Admin`` classes, lazy-loading them on demand (to reduce overhead)
  253. and matching each of them to a group. It is also responsible for handling the top level
  254. template files, administration panel title and logo.
  255. Create child admins
  256. -------------------
  257. Let us say you have a ``PlaylistAdmin`` and a ``VideoAdmin``. You can optionally declare the ``VideoAdmin``
  258. to be a child of the ``PlaylistAdmin``. This will create new routes like, for example, ``/playlist/{id}/video/list``,
  259. where the videos will automatically be filtered by post.
  260. To do this, you first need to call the ``addChild`` method in your ``PlaylistAdmin`` service configuration:
  261. .. configuration-block::
  262. .. code-block:: xml
  263. <!-- app/config/config.xml -->
  264. <service id="sonata.admin.playlist" class="AppBundle\Admin\PlaylistAdmin">
  265. <!-- ... -->
  266. <call method="addChild">
  267. <argument type="service" id="sonata.admin.video" />
  268. </call>
  269. </service>
  270. Then, you have to set the VideoAdmin ``parentAssociationMapping`` attribute to ``playlist`` :
  271. .. code-block:: php
  272. <?php
  273. namespace AppBundle\Admin;
  274. // ...
  275. class VideoAdmin extends AbstractAdmin
  276. {
  277. protected $parentAssociationMapping = 'playlist';
  278. // OR
  279. public function getParentAssociationMapping()
  280. {
  281. return 'playlist';
  282. }
  283. }
  284. To display the ``VideoAdmin`` extend the menu in your ``PlaylistAdmin`` class:
  285. .. code-block:: php
  286. <?php
  287. namespace AppBundle\Admin;
  288. use Knp\Menu\ItemInterface as MenuItemInterface;
  289. use Sonata\AdminBundle\Admin\AbstractAdmin;
  290. use Sonata\AdminBundle\Admin\AdminInterface;
  291. class PlaylistAdmin extends AbstractAdmin
  292. {
  293. // ...
  294. protected function configureSideMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null)
  295. {
  296. if (!$childAdmin && !in_array($action, array('edit', 'show'))) {
  297. return;
  298. }
  299. $admin = $this->isChild() ? $this->getParent() : $this;
  300. $id = $admin->getRequest()->get('id');
  301. $menu->addChild('View Playlist', array('uri' => $admin->generateUrl('show', array('id' => $id))));
  302. if ($this->isGranted('EDIT')) {
  303. $menu->addChild('Edit Playlist', array('uri' => $admin->generateUrl('edit', array('id' => $id))));
  304. }
  305. if ($this->isGranted('LIST')) {
  306. $menu->addChild('Manage Videos', array(
  307. 'uri' => $admin->generateUrl('sonata.admin.video.list', array('id' => $id))
  308. ));
  309. }
  310. }
  311. }
  312. It also possible to set a dot-separated value, like ``post.author``, if your parent and child admins are not directly related.
  313. Be wary that being a child admin is optional, which means that regular routes
  314. will be created regardless of whether you actually need them or not. To get rid
  315. of them, you may override the ``configureRoutes`` method::
  316. <?php
  317. namespace Sonata\NewsBundle\Admin;
  318. use Sonata\AdminBundle\Admin\AbstractAdmin;
  319. use Sonata\AdminBundle\Route\RouteCollection;
  320. class CommentAdmin extends AbstractAdmin
  321. {
  322. protected $parentAssociationMapping = 'post';
  323. protected function configureRoutes(RouteCollection $collection)
  324. {
  325. if ($this->isChild()) {
  326. // This is the route configuration as a child
  327. $collection->clearExcept(['show', 'edit']);
  328. return;
  329. }
  330. // This is the route configuration as a parent
  331. $collection->clear();
  332. }
  333. }
  334. .. _`Django Project Website`: http://www.djangoproject.com/
  335. .. _`CRUD`: http://en.wikipedia.org/wiki/CRUD