14-Generators.txt 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979
  1. Chapter 14 - Admin Generator
  2. ============================
  3. >**Caution**
  4. >This chapter describes the admin generator system which still uses the 1.0 form system. The information about the CRUD generator have been moved to the "[symfony Forms in Action](http://www.symfony-project.org/book/forms/1_1/en/04-Propel-Integration)" book as it uses the new form framework.
  5. Many applications are based on data stored in a database and offer an interface to access it. Symfony automates the repetitive task of creating a module providing data manipulation capabilities based on a Propel object. If your object model is properly defined, symfony can even generate an entire site administration automatically. This chapter describes the use of the administration generator, which is bundled with the Propel plugin. It relies on a special configuration file with a complete syntax, so most of this chapter describes the various possibilities of the administration generator.
  6. Code Generation Based on the Model
  7. ----------------------------------
  8. In a web application, data access operations can be categorized as one of the following:
  9. * Creation of a record
  10. * Retrieval of records
  11. * Update of a record (and modification of its columns)
  12. * Deletion of a record
  13. These operations are so common that they have a dedicated acronym: CRUD. Many pages can be reduced to one of them. For instance, in a forum application, the list of latest posts is a retrieve operation, and the reply to a post corresponds to a create operation.
  14. The basic actions and templates that implement the CRUD operations for a given table are repeatedly created in web applications. In symfony, the model layer contains enough information to allow generating the CRUD operations code, so as to speed up the early part of the back-end interfaces.
  15. ### Example Data Model
  16. Throughout this chapter, the listings will demonstrate the capabilities of the symfony admin generator based on a simple example, which will remind you of Chapter 8. This is the well-known example of the weblog application, containing two `Article` and `Comment` classes. Listing 14-1 shows its schema, illustrated in Figure 14-1.
  17. Listing 14-1 - `schema.yml` of the Example Weblog Application
  18. propel:
  19. blog_article:
  20. _attributes: { phpName: Article }
  21. id:
  22. title: varchar(255)
  23. content: longvarchar
  24. created_at:
  25. blog_comment:
  26. _attributes: { phpName: Comment }
  27. id:
  28. article_id:
  29. author: varchar(255)
  30. content: longvarchar
  31. created_at:
  32. Figure 14-1 - Example data model
  33. ![Example data model](/images/book/F1401.png "Example data model")
  34. There is no particular rule to follow during the schema creation to allow code generation. Symfony will use the schema as is and interpret its attributes to generate an administration.
  35. >**TIP**
  36. >To get the most out of this chapter, you need to actually do the examples. You will get a better understanding of what symfony generates and what can be done with the generated code if you have a view of every step described in the listings. So you are invited to create a data structure such as the one described previously, to create a database with a `blog_article` and a `blog_comment` table, and to populate this database with sample data.
  37. Administration
  38. --------------
  39. Symfony can generate modules, based on model class definitions from the `schema.yml` file, for the back-end of your applications. You can create an entire site administration with only generated administration modules. The examples of this section will describe administration modules added to a `backend` application. If your project doesn't have a `backend` application, create its skeleton now by calling the `generate:app` task:
  40. > php symfony generate:app backend
  41. Administration modules interpret the model by way of a special configuration file called `generator.yml`, which can be altered to extend all the generated components and the module look and feel. Such modules benefit from the usual module mechanisms described in previous chapters (layout, validation, routing, custom configuration, autoloading, and so on). You can also override the generated action or templates, in order to integrate your own features into the generated administration, but `generator.yml` should take care of the most common requirements and restrict the use of PHP code only to the very specific.
  42. >**NOTE**
  43. >As the admin generator uses symfony 1.0 validation system, the `sfCompat10` plugin is automatically enabled for you.
  44. ### Initiating an Administration Module
  45. With symfony, you build an administration on a per-module basis. A module is generated based on a Propel object using the `propel:init-admin` task:
  46. > php symfony propel:init-admin backend article Article
  47. This call is enough to create an `article` module in the `backend` application based on the `Article` class definition, and is accessible by the following:
  48. http://localhost/backend.php/article
  49. The look and feel of a generated module, illustrated in Figures 14-5 and 14-6, is sophisticated enough to make it usable out of the box for a commercial application.
  50. Figure 14-5 - `list` view of the `article` module in the `backend` application
  51. ![list view of the article module in the backend application](/images/book/F1405.png "list view of the article module in the backend application")
  52. Figure 14-6 - `edit` view of the `article` module in the `backend` application
  53. ![edit view of the article module in the backend application](/images/book/F1406.png "edit view of the article module in the backend application")
  54. ### A Look at the Generated Code
  55. The code of the Article administration module, in the `apps/backend/modules/article/` directory, is empty because it is only initiated. The best way to review the generated code of this module is to interact with it using the browser, and then check the contents of the `cache/` folder. Listing 14-4 lists the generated actions and the templates found in the cache.
  56. Listing 14-4 - Generated Administration Elements, in `cache/backend/ENV/modules/article/`
  57. // In actions/actions.class.php
  58. create // Forwards to edit
  59. delete // Deletes a record
  60. edit // Displays a form to modify the fields of a record
  61. // And handles the form submission
  62. index // Forwards to list
  63. list // Displays the list of all the records of the table
  64. save // Forwards to edit
  65. // In templates/
  66. _edit_actions.php
  67. _edit_footer.php
  68. _edit_form.php
  69. _edit_header.php
  70. _edit_messages.php
  71. _filters.php
  72. _list.php
  73. _list_actions.php
  74. _list_footer.php
  75. _list_header.php
  76. _list_messages.php
  77. _list_td_actions.php
  78. _list_td_stacked.php
  79. _list_td_tabular.php
  80. _list_th_stacked.php
  81. _list_th_tabular.php
  82. editSuccess.php
  83. listSuccess.php
  84. This shows that a generated administration module is composed mainly of two views, `edit` and `list`. If you have a look at the code, you will find it to be very modular, readable, and extensible.
  85. ### Introducing the generator.yml Configuration File
  86. The generated administration modules rely on parameters found in the `generator.yml` YAML configuration file. To see the default configuration of a newly created administration module, open the `generator.yml` file, located in the `backend/modules/article/config/generator.yml` directory and reproduced in Listing 14-5.
  87. Listing 14-5 - Default Generator Configuration, in `backend/modules/article/config/generator.yml`
  88. generator:
  89. class: sfPropelAdminGenerator
  90. param:
  91. model_class: Article
  92. theme: default
  93. This configuration is enough to generate the basic administration. Any customization is added under the `param` key, after the `theme` line (which means that all lines added at the bottom of the `generator.yml` file must at least start with four blank spaces to be properly indented). Listing 14-6 shows a typical customized `generator.yml`.
  94. Listing 14-6 - Typical Complete Generator Configuration
  95. generator:
  96. class: sfPropelAdminGenerator
  97. param:
  98. model_class: Article
  99. theme: default
  100. fields:
  101. author_id: { name: Article author }
  102. list:
  103. title: List of all articles
  104. display: [title, author_id, category_id]
  105. fields:
  106. published_on: { params: date_format='dd/MM/yy' }
  107. layout: stacked
  108. params: |
  109. %%is_published%%<strong>%%=title%%</strong><br /><em>by %%author%%
  110. in %%category%% (%%published_on%%)</em><p>%%content_summary%%</p>
  111. filters: [title, category_id, author_id, is_published]
  112. max_per_page: 2
  113. edit:
  114. title: Editing article "%%title%%"
  115. display:
  116. "Post": [title, category_id, content]
  117. "Workflow": [author_id, is_published, created_on]
  118. fields:
  119. category_id: { params: disabled=true }
  120. is_published: { type: plain}
  121. created_on: { type: plain, params: date_format='dd/MM/yy' }
  122. author_id: { params: size=5 include_custom=>> Choose an author << }
  123. published_on: { credentials: }
  124. content: { params: rich=true tinymce_options=height:150 }
  125. The following sections explain in detail all the parameters that can be used in this configuration file.
  126. Generator Configuration
  127. -----------------------
  128. The generator configuration file is very powerful, allowing you to alter the generated administration in many ways. But such capabilities come with a price: The overall syntax description is long to read and learn, making this chapter one of the longest in this book. The symfony website proposes an additional resource that will help you learn this syntax: the administration generator cheat sheet, reproduced in Figure 14-7. Download it from [http://www.symfony-project.org/uploads/assets/sfAdminGeneratorRefCard.pdf](http://www.symfony-project.org/uploads/assets/sfAdminGeneratorRefCard.pdf), and keep it close to you when you read the following examples of this chapter.
  129. The examples of this section will tweak the `article` administration module, as well as the `comment` administration module, based on the `Comment` class definition. Create the latter with the `propel:init-admin` task:
  130. > php symfony propel:init-admin backend comment Comment
  131. Figure 14-7 - The administration generator cheat sheet
  132. ![The administration generator cheat sheet](/images/book/F1407.png "The administration generator cheat sheet")
  133. ### Fields
  134. By default, the columns of the `list` view and the fields of the `edit` view are the columns defined in `schema.yml`. With `generator.yml`, you can choose which fields are displayed, which ones are hidden, and add fields of your own--even if they don't have a direct correspondence in the object model.
  135. #### Field Settings
  136. The administration generator creates a `field` for each column in the `schema.yml` file. Under the `fields` key, you can modify the way each field is displayed, formatted, etc. For instance, the field settings shown in Listing 14-7 define a custom label class and input type for the `title` field, and a label and a tooltip for the `content` field. The following sections will describe in detail how each parameter works.
  137. Listing 14-7 - Setting a Custom Label for a Column
  138. generator:
  139. class: sfPropelAdminGenerator
  140. param:
  141. model_class: Article
  142. theme: default
  143. fields:
  144. title: { name: Article Title, type: textarea_tag, params: class=foo }
  145. content: { name: Body, help: Fill in the article body }
  146. In addition to this default definition for all the views, you can override the field settings for a given view (`list` and `edit`), as demonstrated in Listing 14-8.
  147. Listing 14-8 - Overriding Global Settings View per View
  148. generator:
  149. class: sfPropelAdminGenerator
  150. param:
  151. model_class: Article
  152. theme: default
  153. fields:
  154. title: { name: Article Title }
  155. content: { name: Body }
  156. list:
  157. fields:
  158. title: { name: Title }
  159. edit:
  160. fields:
  161. content: { name: Body of the article }
  162. This is a general principle: Any settings that are set for the whole module under the `fields` key can be overridden by view-specific (`list` and `edit`) areas that follow.
  163. #### Adding Fields to the Display
  164. The fields that you define in the `fields` section can be displayed, hidden, ordered, and grouped in various ways for each view. The `display` key is used for that purpose. For instance, to arrange the fields of the `comment` module, use the code of Listing 14-9.
  165. Listing 14-9 - Choosing the Fields to Display, in `modules/comment/config/generator.yml`
  166. generator:
  167. class: sfPropelAdminGenerator
  168. param:
  169. model_class: Comment
  170. theme: default
  171. fields:
  172. article_id: { name: Article }
  173. created_at: { name: Published on }
  174. content: { name: Body }
  175. list:
  176. display: [id, article_id, content]
  177. edit:
  178. display:
  179. NONE: [article_id]
  180. Editable: [author, content, created_at]
  181. The `list` will then display three columns, as in Figure 14-8, and the `edit` form will display four fields, assembled in two groups, as in Figure 14-9.
  182. Figure 14-8 - Custom column setting in the `list` view of the `comment` module
  183. ![Custom column setting in the list view of the comment module](/images/book/F1408.png "Custom column setting in the list view of the comment module")
  184. Figure 14-9 - Grouping fields in the `edit` view of the `comment` module
  185. ![Grouping fields in the edit view of the comment module](/images/book/F1409.png "Grouping fields in the edit view of the comment module")
  186. So you can use the `display` setting in two ways:
  187. * To select the columns to display and the order in which they appear, put the fields in a simple array--as in the previous `list` view.
  188. * To group fields, use an associative array with the group name as a key, or `NONE` for a group with no name. The value is still an array of ordered column names.
  189. >**TIP**
  190. >By default, the primary key columns never appear in either view.
  191. #### Custom Fields
  192. As a matter of fact, the fields configured in `generator.yml` don't even need to correspond to actual columns defined in the schema. If the related class offers a custom getter, it can be used as a field for the `list` view; if there is a getter and/or a setter, it can also be used in the `edit` view. For instance, you can extend the `Article` model with a `getNbComments()` method similar to the one in Listing 14-10.
  193. Listing 14-10 - Adding a Custom Getter in the Model, in `lib/model/Article.php`
  194. [php]
  195. public function getNbComments()
  196. {
  197. return $this->countComments();
  198. }
  199. Then `nb_comments` is available as a field in the generated module (notice that the getter uses a camelCase version of the field name), as in Listing 14-11.
  200. Listing 14-11 - Custom Getters Provide Additional Columns for Administration Modules, in `backend/modules/article/config/generator.yml`
  201. generator:
  202. class: sfPropelAdminGenerator
  203. param:
  204. model_class: Article
  205. theme: default
  206. list:
  207. display: [id, title, nb_comments, created_at]
  208. The resulting `list` view of the `article` module is shown in Figure 14-10.
  209. Figure 14-10 - Custom field in the `list` view of the `article` module
  210. ![Custom field in the list view of the article module](/images/book/F1410.png "Custom field in the list view of the article module")
  211. Custom fields can even return HTML code to display more than raw data. For instance, you can extend the `Comment` class with a `getArticleLink()` method as in Listing 14-12.
  212. Listing 14-12 - Adding a Custom Getter Returning HTML, in `lib/model/Comment.class.php`
  213. [php]
  214. public function getArticleLink()
  215. {
  216. return link_to($this->getArticle()->getTitle(), 'article/edit?id='.$this->getArticleId());
  217. }
  218. You can use this new getter as a custom field in the `comment/list` view with the same syntax as in Listing 14-11. See the example in Listing 14-13, and the result in Figure 14-11, where the HTML code output by the getter (a hyperlink to the article) appears in the second column instead of the article primary key.
  219. Listing 14-13 - Custom Getters Returning HTML Can Also Be Used As Additional Columns, in `modules/comment/config/generator.yml`
  220. generator:
  221. class: sfPropelAdminGenerator
  222. param:
  223. model_class: Comment
  224. theme: default
  225. list:
  226. display: [id, article_link, content]
  227. Figure 14-11 - Custom field in the `list` view of the `comment` module
  228. ![Custom field in the list view of the comment module](/images/book/F1411.png "Custom field in the list view of the comment module")
  229. #### Partial Fields
  230. The code located in the model must be independent from the presentation. The example of the `getArticleLink()` method earlier doesn't respect this principle of layer separation, because some view code appears in the model layer. To achieve the same goal in a correct way, you'd better put the code that outputs HTML for a custom field in a partial. Fortunately, the administration generator allows it if you declare a field name prefixed by an underscore. In that case, the `generator.yml` file of Listing 14-13 is to be modified as in Listing 14-14.
  231. Listing 14-14 - Partials Can Be Used As Additional Columns--Use the `_` Prefix
  232. list:
  233. display: [id, _article_link, created_at]
  234. For this to work, an `_article_link.php` partial must be created in the `modules/comment/templates/` directory, as in Listing 14-15.
  235. Listing 14-15 - Example Partial for the `list` View, in `modules/comment/templates/_article_link.php`
  236. <?php echo link_to($comment->getArticle()->getTitle(), 'article/edit?id='.$comment->getArticleId()) ?>
  237. Notice that the partial template of a partial field has access to the current object through a variable named by the class (`$comment` in this example). For instance, for a module built for a class called `UserGroup`, the partial will have access to the current object through the `$user_group` variable.
  238. The result is the same as in Figure 14-11, except that the layer separation is respected. If you get used to respecting the layer separation, you will end up with more maintainable applications.
  239. If you need to customize the parameters of a partial field, do the same as for a normal field, under the `field` key. Just don't include the leading underscore (`_`) in the key--see an example in Listing 14-16.
  240. Listing 14-16 - Partial Field Properties Can Be Customized Under the `fields` Key
  241. fields:
  242. article_link: { name: Article }
  243. If your partial becomes crowded with logic, you'll probably want to replace it with a component. Change the `_` prefix to `~` and you can define a component field, as you can see in Listing 14-17.
  244. Listing 14-17 - Components Can Be Used As Additional Columns--Use the `~` Prefix
  245. ...
  246. list:
  247. display: [id, ~article_link, created_at]
  248. In the generated template, this will result by a call to the `articleLink` component of the current module.
  249. >**NOTE**
  250. >Custom and partial fields can be used in the `list` view, the `edit` view, and for filters. If you use the same partial for several views, the context (`'list'`, `'edit'`, or `'filter'`) is stored in the `$type` variable.
  251. ### View Customization
  252. To change the `edit` and `list` views' appearance, you could be tempted to alter the templates. But because they are automatically generated, doing so isn't a very good idea. Instead, you should use the `generator.yml` configuration file, because it can do almost everything that you need without sacrificing modularity.
  253. #### Changing the View Title
  254. In addition to a custom set of fields, the `list` and `edit` pages can have a custom page title. For instance, if you want to customize the title of the `article` views, do as in Listing 14-18. The resulting `edit` view is illustrated in Figure 14-12.
  255. Listing 14-18 - Setting a Custom Title for Each View, in `backend/modules/article/config/generator.yml`
  256. list:
  257. title: List of Articles
  258. ...
  259. edit:
  260. title: Body of article %%title%%
  261. display: [content]
  262. Figure 14-12 - Custom title in the `edit` view of the `article` module
  263. ![Custom title in the edit view of the article module](/images/book/F1412.png "Custom title in the edit view of the article module")
  264. As the default titles use the class name, they are often good enough--provided that your model uses explicit class names.
  265. >**TIP**
  266. >In the string values of `generator.yml`, the value of a field can be accessed via the name of the field surrounded by `%%`.
  267. #### Adding Tooltips
  268. In the `list` and `edit` views, you can add tooltips to help describe the fields that are displayed. For instance, to add a tooltip to the `article_id` field of the `edit` view of the `comment` module, add a `help` property in the `fields` definition as in Listing 14-19. The result is shown in Figure 14-13.
  269. Listing 14-19 - Setting a Tooltip in the `edit` View, in `modules/comment/config/generator.yml`
  270. edit:
  271. fields:
  272. ...
  273. article_id: { help: The current comment relates to this article }
  274. Figure 14-13 - Tooltip in the `edit` view of the `comment` module
  275. ![Tooltip in the edit view of the comment module](/images/book/F1413.png "Tooltip in the edit view of the comment module")
  276. In the `list` view, tooltips are displayed in the column header; in the `edit` view, they appear under the input.
  277. #### Modifying the Date Format
  278. Dates can be displayed using a custom format as soon as you use the `date_format` param, as demonstrated in Listing 14-20.
  279. Listing 14-20 - Formatting a Date in the `list` View
  280. list:
  281. fields:
  282. created_at: { name: Published, params: date_format='dd/MM' }
  283. It takes the same format parameter as the `format_date()` helper described in the previous chapter.
  284. >**SIDEBAR**
  285. >Administration templates are i18N ready
  286. >
  287. >All of the text found in the generated templates is automatically internationalized (i.e., enclosed in a call to the `__()` helper). This means that you can easily translate a generated administration by adding the translations of the phrases in an XLIFF file, in your `apps/frontend/i18n/` directory, as explained in the previous chapter.
  288. ### List View-Specific Customization
  289. The `list` view can display the details of a record in a tabular way, or with all the details stacked in one line. It also contains filters, pagination, and sorting features. These features can be altered by configuration, as described in the next sections.
  290. #### Changing the Layout
  291. By default, the hyperlink between the `list` view and the `edit` view is borne by the primary key column. If you refer back to Figure 14-11, you will see that the `id` column in the comment list not only shows the primary key of each comment, but also provides a hyperlink allowing users to access the `edit` view.
  292. If you prefer the hyperlink to the detail of the record to appear on another column, prefix the column name by an equal sign (`=`) in the `display` key. Listing 14-21 shows how to remove the `id` from the displayed fields of the comment `list` and to put the hyperlink on the `content` field instead. Check Figure 14-14 for a screenshot.
  293. Listing 14-21 - Moving the Hyperlink for the `edit` View in the `list` View, in `modules/comment/config/generator.yml`
  294. list:
  295. display: [article_link, =content]
  296. Figure 14-14 - Moving the link to the `edit` view on another column, in the `list` view of the `comment` module
  297. ![Moving the link to the edit view on another column, in the list view of the comment module](/images/book/F1414.png "Moving the link to the edit view on another column, in the list view of the comment module")
  298. By default, the `list` view uses the `tabular` layout, where the fields appear as columns, as shown previously. But you can also use the `stacked` layout and concatenate the fields into a single string that expands on the full length of the table. If you choose the `stacked` layout, you must set in the `params` key the pattern defining the value of each line of the list. For instance, Listing 14-22 defines a stacked layout for the list view of the comment module. The result appears in Figure 14-15.
  299. Listing 14-22 - Using a `stacked` Layout in the `list` View, in `modules/comment/config/generator.yml`
  300. list:
  301. layout: stacked
  302. params: |
  303. %%=content%% <br />
  304. (sent by %%author%% on %%created_at%% about %%article_link%%)
  305. display: [created_at, author, content]
  306. Figure 14-15 - Stacked layout in the `list` view of the `comment` module
  307. ![Stacked layout in the list view of the comment module](/images/book/F1415.png "Stacked layout in the list view of the comment module")
  308. Notice that a `tabular` layout expects an array of fields under the `display` key, but a `stacked` layout uses the `params` key for the HTML code generated for each record. However, the `display` array is still used in a `stacked` layout to determine which column headers are available for the interactive sorting.
  309. #### Filtering the Results
  310. In a `list` view, you can add a set of filter interactions. With these filters, users can both display fewer results and get to the ones they want faster. Configure the filters under the `filters` key, with an array of field names. For instance, add a filter on the `article_id`, `author`, and `created_at` fields to the comment `list` view, as in Listing 14-23, to display a filter box similar to the one in Figure 14-16. You will need to add a `__toString()` method to the `Article` class (returning, for instance, the article `title`) for this to work.
  311. Listing 14-23 - Setting the Filters in the `list` View, in `modules/comment/config/generator.yml`
  312. list:
  313. filters: [article_id, author, created_at]
  314. layout: stacked
  315. params: |
  316. %%=content%% <br />
  317. (sent by %%author%% on %%created_at%% about %%article_link%%)
  318. display: [created_at, author, content]
  319. Figure 14-16 - Filters in the `list` view of the `comment` module
  320. ![Filters in the list view of the comment module](/images/book/F1416.png "Filters in the list view of the comment module")
  321. The filters displayed by symfony depend on the column type:
  322. * For text columns (like the `author` field in the `comment` module), the filter is a text input allowing text-based search with wildcards (`*`).
  323. * For foreign keys (like the `article_id` field in the `comment` module), the filter is a drop-down list of the records of the related table. As for the regular `object_select_tag()`, the options of the drop-down list are the ones returned by the `__toString()` method of the related class.
  324. * For date columns (like the `created_at` field in the `comment` module), the filter is a pair of rich date tags (text fields filled by calendar widgets), allowing the selection of a time interval.
  325. * For Boolean columns, the filter is a drop-down list having `true`, `false`, and `true or false` options--the last value reinitializes the filter.
  326. Just like you use partial fields in lists, you can also use partial filters to create a filter that symfony doesn't handle on its own. For instance, imagine a `state` field that may contain only two values (`open` and `closed`), but for some reason you store those values directly in the field instead of using a table relation. A simple filter on this field (of type `string`) would be a text-based search, but what you want is probably a drop-down list of values. That's easy to achieve with a partial filter. See Listing 14-24 for an example implementation.
  327. Listing 14-24 - Using a Partial Filter
  328. [php]
  329. // Define the partial, in templates/_state.php
  330. <?php echo select_tag('filters[state]', options_for_select(array(
  331. '' => '',
  332. 'open' => 'open',
  333. 'closed' => 'closed',
  334. ), isset($filters['state']) ? $filters['state'] : '')) ?>
  335. // Add the partial filter in the filter list, in config/generator.yml
  336. list:
  337. filters: [date, _state]
  338. Notice that the partial has access to a `$filters` variable, which is useful to get the current value of the filter.
  339. There is one last option that can be very useful for looking for empty values. Imagine that you want to filter the list of comments to display only the ones that have no author. The problem is that if you leave the author filter empty, it will be ignored. The solution is to set the `filter_is_empty` field setting to true, as in Listing 14-25, and the filter will display an additional check box, which will allow you to look for empty values, as illustrated in Figure 14-17.
  340. Listing 14-25 - Adding Filtering of Empty Values on the `author` Field in the `list` View
  341. list:
  342. fields:
  343. author: { filter_is_empty: true }
  344. filters: [article_id, author, created_at]
  345. Figure 14-17 - Allowing the filtering of empty `author` values
  346. ![Allowing the filtering of empty author values](/images/book/F1417.png "Allowing the filtering of empty author values")
  347. #### Sorting the List
  348. In a `list` view, the table headers are hyperlinks that can be used to reorder the list, as shown in Figure 14-18. These headers are displayed both in the `tabular` and `stacked` layouts. Clicking these links reloads the page with a `sort` parameter that rearranges the list order accordingly.
  349. Figure 14-18 - Table headers of the `list` view are sort controls
  350. ![Table headers of the list view are sort controls](/images/book/F1418.png "Table headers of the list view are sort controls")
  351. You can reuse the syntax to point to a list directly sorted according to a column:
  352. [php]
  353. <?php echo link_to('Comment list by date', 'comment/list?sort=created_at&type=desc' ) ?>
  354. You can also define a default `sort` order for the `list` view directly in the `generator.yml` file. The syntax follows the example given in Listing 14-26.
  355. Listing 14-26 - Setting a Default Sort Field in the `list` View
  356. list:
  357. sort: created_at
  358. # Alternative syntax, to specify a sort order
  359. sort: [created_at, desc]
  360. >**NOTE**
  361. >Only the fields that correspond to an actual column are transformed into sort controls--not the custom or partial fields.
  362. #### Customizing the Pagination
  363. The generated administration effectively deals with even large tables, because the `list` view uses pagination by default. When the actual number of rows in a table exceeds the number of maximum rows per page, pagination controls appear at the bottom of the list. For instance, Figure 14-19 shows the list of comments with six test comments in the table but a limit of five comments displayed per page. Pagination ensures a good performance, because only the displayed rows are effectively retrieved from the database, and a good usability, because even tables with millions of rows can be managed by an administration module.
  364. Figure 14-19 - Pagination controls appear on long lists
  365. ![Pagination controls appear on long lists](/images/book/F1419.png "Pagination controls appear on long lists")
  366. You can customize the number of records to be displayed in each page with the `max_per_page` parameter:
  367. list:
  368. max_per_page: 5
  369. #### Using a Join to Speed Up Page Delivery
  370. By default, the administration generator uses a simple `doSelect()` to retrieve a list of records. But, if you use related objects in the list, the number of database queries required to display the list may rapidly increase. For instance, if you want to display the name of the article in a list of comments, an additional query is required for each post in the list to retrieve the related `Article` object. So you may want to force the pager to use a `doSelectJoinXXX()` method to optimize the number of queries. This can be specified with the `peer_method` parameter.
  371. list:
  372. peer_method: doSelectJoinArticle
  373. Chapter 18 explains the concept of Join more extensively.
  374. ### Edit View-Specific Customization
  375. In an `edit` view, the user can modify the value of each column for a given record. Symfony determines the type of input to display according to the data type of the column. It then generates an `object_*_tag()` helper, and passes that helper the object and the property to edit. For instance, if the article `edit` view configuration stipulates that the user can edit the `title` field:
  376. edit:
  377. display: [title, ...]
  378. then the `edit` page will display a regular text input tag to edit the `title` because this column is defined as a `varchar` type in the schema.
  379. [php]
  380. <?php echo object_input_tag($article, 'getTitle') ?>
  381. #### Changing the Input Type
  382. The default type-to-field conversion rules are as follows:
  383. * A column defined as `integer`, `float`, `char`, `varchar(size)` appears in the `edit` view as an `object_input_tag()`.
  384. * A column defined as `longvarchar` appears as an `object_textarea_tag()`.
  385. * A foreign key column appears as an `object_select_tag()`.
  386. * A column defined as `boolean` appears as an `object_checkbox_tag()`.
  387. * A column defined as a `timestamp` or `date` appears as an `object_input_date_tag()`.
  388. You may want to override these rules to specify a custom input type for a given field. To that extent, set the `type` parameter in the `fields` definition to a specific form helper name. As for the options of the generated `object_*_tag()`, you can change them with the params parameter. See an example in Listing 14-27.
  389. Listing 14-27 - Setting a Custom Input Type and Params for the `edit` View
  390. generator:
  391. class: sfPropelAdminGenerator
  392. param:
  393. model_class: Comment
  394. theme: default
  395. edit:
  396. fields:
  397. ## Drop the input, just display plain text
  398. id: { type: plain }
  399. ## The input is not editable
  400. author: { params: disabled=true }
  401. ## The input is a textarea (object_textarea_tag)
  402. content: { type: textarea_tag, params: rich=true css=user.css tinymce_options=width:330 }
  403. ## The input is a select (object_select_tag)
  404. article_id: { params: include_custom=Choose an article }
  405. ...
  406. The params parameters are passed as options to the generated `object_*_tag()`. For instance, the `params` definition for the preceding `article_id` will produce in the template the following:
  407. <?php echo object_select_tag($comment, 'getArticleId', 'related_class=Article', 'include_custom=Choose an article') ?>
  408. This means that all the options usually available in the form helpers can be customized in an `edit` view.
  409. #### Handling Partial Fields
  410. Partial fields can be used in `edit` views just like in `list` views. The difference is that you have to handle by hand, in the action, the update of the column according to the value of the request parameter sent by the partial field. Symfony knows how to handle the normal fields (corresponding to actual columns), but can't guess how to handle the inputs you may include in partial fields.
  411. For instance, imagine an administration module for a `User` class where the available fields are `id`, `nickname`, and `password`. The site administrator must be able to change the password of a user upon request, but the `edit` view must not display the value of the password field for security reasons. Instead, the form should display an empty password input that the site administrator can fill to change the value. The generator settings for such an `edit` view are then similar to Listing 14-28.
  412. Listing 14-28 - Including a Partial Field in the `edit` View
  413. edit:
  414. display: [id, nickname, _newpassword]
  415. fields:
  416. newpassword: { name: Password, help: Enter a password to change it, leave the field blank to keep the current one }
  417. The `templates/_newpassword.php` partial contains something like this:
  418. [php]
  419. <?php echo input_password_tag('newpassword', '') ?>
  420. Notice that this partial uses a simple form helper, not an object form helper, since it is not desirable to retrieve the password value from the current `User` object to populate the form input--which could disclose the user password.
  421. Now, in order to use the value from this control to update the object in the action, you need to extend the `updateUserFromRequest()` method in the action. To do that, create a method with the same name in the action class file with the custom behavior for the input of the partial field, as in Listing 14-29.
  422. Listing 14-29 - Handling a Partial Field in the Action, in `modules/user/actions/actions.class.php`
  423. [php]
  424. class userActions extends autouserActions
  425. {
  426. protected function updateUserFromRequest()
  427. {
  428. // Handle the input of the partial field
  429. $password = $this->getRequest()->getParameter('newpassword');
  430. if ($password)
  431. {
  432. $this->user->setPassword($password);
  433. }
  434. // Let symfony handle the other fields
  435. parent::updateUserFromRequest();
  436. }
  437. }
  438. >**NOTE**
  439. >In the real world, a `user/edit` view usually contains two password fields, the second having to match the first one to avoid typing mistakes. In practice, as you saw in Chapter 10, this is done via a validator. The administration-generated modules benefit from this mechanism just like regular modules.
  440. ### Dealing with Foreign Keys
  441. If your schema defines table relationships, the generated administration modules take advantage of it and offer even more automated controls, thus greatly simplifying the relationship management.
  442. #### One-to-Many Relationships
  443. The 1-n table relationships are taken care of by the administration generator. As is depicted by Figure 14-1 earlier, the `blog_comment` table is related to the `blog_article` table through the `article_id` field. If you initiate the module of the `Comment` class with the administration generator, the `comment/edit` action will automatically display the `article_id` as a drop-down list showing the IDs of the available records of the `blog_article` table (check again Figure 14-9 for an illustration).
  444. In addition, if you define a `__toString()` method in the `Article` class, the text of the drop-down options use it instead of the primary keys.
  445. If you need to display the list of comments related to an article in the `article` module (n-1 relationship), you will need to customize the module a little by way of a partial field.
  446. #### Many-to-Many Relationships
  447. Symfony also takes care of n-n table relationships, but since you can't define them in the schema, you need to add a few parameters to the `generator.yml` file.
  448. The implementation of many-to-many relationships requires an intermediate table. For instance, if there is an n-n relation between a `blog_article` and a `blog_author` table (an article can be written by more than one author and, obviously, an author can write more than one article), your database will always end up with a table called `blog_article_author` or similar, as in Figure 14-20.
  449. Figure 14-20 - Using a "through class" to implement many-to-many relationships
  450. ![Using a "through class" to implement many-to-many relationships](/images/book/F1420.png "Using a through class to implement many-to-many relationships")
  451. The model then has a class called `ArticleAuthor`, and this is the only thing that the administration generator needs--but you have to pass it as a `through_class` parameter of the field.
  452. For instance, in a generated module based on the `Article` class, you can add a field to create new n-n associations with the `Author` class if you write `generator.yml` as in Listing 14-30.
  453. Listing 14-30 - Handling Many-to-Many Relationships with a `through_class` Parameter
  454. edit:
  455. fields:
  456. article_author: { type: admin_double_list, params: through_class=ArticleAuthor }
  457. Such a field handles links between existing objects, so a regular drop-down list is not enough. You must use a special type of input for that. Symfony offers three widgets to help relate members of two lists (illustrated in Figure 14-21):
  458. * An `admin_double_list` is a set of two expanded select controls, together with buttons to switch elements from the first list (available elements) to the second (selected elements).
  459. * An `admin_select_list` is an expanded select control in which you can select many elements.
  460. * An `admin_check_list` is a list of check box tags.
  461. Figure 14-21 - Available controls for many-to-many relationships
  462. ![Available controls for many-to-many relationships](/images/book/F1421.png "Available controls for many-to-many relationships")
  463. ### Adding Interactions
  464. Administration modules allow users to perform the usual CRUD operations, but you can also add your own interactions or restrict the possible interactions for a view. For instance, the interaction definition shown in Listing 14-31 gives access to all the default CRUD actions on the `article` module.
  465. Listing 14-31 - Defining Interactions for Each View, in `backend/modules/article/config/generator.yml`
  466. list:
  467. title: List of Articles
  468. object_actions:
  469. _edit: ~
  470. _delete: ~
  471. batch_actions:
  472. _delete: ~
  473. actions:
  474. _create: ~
  475. edit:
  476. title: Body of article %%title%%
  477. actions:
  478. _list: ~
  479. _save: ~
  480. _save_and_add: ~
  481. _delete: ~
  482. In a `list` view, there are three action settings: the actions available for every object (`object_actions`), the actions available for a selection of objects (`batch_actions`), and actions available for the whole page (`actions`). The list interactions defined in Listing 14-31 render like in Figure 14-22. Each line shows one button to edit the record and one to delete it, plus one checkbox on each line to delete a selection of records. At the bottom of the list, a button allows the creation of a new record.
  483. Figure 14-22 - Interactions in the `list` view
  484. ![Interactions in the list view](/images/book/F1422.png "Interactions in the list view")
  485. In an `edit` view, as there is only one record edited at a time, there is only one set of actions to define (under `actions`). The `edit` interactions defined in Listing 14-31 render like in Figure 14-23. Both the `save` and the `save_and_add` actions save the current edits in the records, the difference being that the `save` action displays the `edit` view on the current record after saving, while the `save_and_add` action displays an empty `edit` view to add another record. The `save_and_add` action is a shortcut that you will find very useful when adding many records in rapid succession. As for the position of the `delete` action, it is separated from the other buttons so that users don't click it by mistake.
  486. The interaction names starting with an underscore (`_`) tell symfony to use the default icon and action corresponding to these interactions. The administration generator understands `_edit`, `_delete`, `_list`, `_save`, `_save_and_add`, and `_create`.
  487. Figure 14-23 - Interactions in the `edit` view
  488. ![Interactions in the edit view](/images/book/F1423.png "Interactions in the edit view")
  489. But you can also add a custom interaction, in which case you must specify a name starting with no underscore, and a target action in the current module, as in Listing 14-32.
  490. Listing 14-32 - Defining a Custom Interaction
  491. list:
  492. title: List of Articles
  493. object_actions:
  494. _edit: -
  495. _delete: -
  496. addcomment: { name: Add a comment, action: addComment, icon: backend/addcomment.png }
  497. Each article in the list will now show the `web/images/addcomment.png` button, as shown in Figure 14-24. Clicking it triggers a call to the `addComment` action in the current module. The primary key of the current object is automatically added to the request parameters.
  498. Figure 14-24 - Custom interaction in the `list` view
  499. ![Custom interaction in the list view](/images/book/F1424.png "Custom interaction in the list view")
  500. The `addComment` action can be implemented as in Listing 14-33.
  501. Listing 14-33 - Implementing the Custom Interaction Action, in `actions/actions.class.php`
  502. [php]
  503. public function executeAddComment($request)
  504. {
  505. $comment = new Comment();
  506. $comment->setArticleId($request->getParameter('id'));
  507. $comment->save();
  508. $this->redirect('comment/edit?id='.$comment->getId());
  509. }
  510. Batch actions receive an array of the primary keys of the selected records in the `sf_admin_batch_selection` request parameter.
  511. One last word about actions: If you want to suppress completely the actions for one category, use an empty list, as in Listing 14-34.
  512. Listing 14-34 - Removing All Actions in the `list` View
  513. list:
  514. title: List of Articles
  515. actions: {}
  516. ### Form Validation
  517. If you take a look at the generated `_edit_form.php` template in your project `cache/` directory, you will see that the form fields use a special naming convention. In a generated `edit` view, the input names result from the concatenation of the underscore-syntaxed model class name and the field name between angle brackets.
  518. For instance, if the `edit` view for the `Article` class has a `title` field, the template will look like Listing 14-35 and the field will be identified as `article[title]`.
  519. Listing 14-35 - Syntax of the Generated Input Names
  520. // generator.yml
  521. generator:
  522. class: sfPropelAdminGenerator
  523. param:
  524. model_class: Article
  525. theme: default
  526. edit:
  527. display: [title]
  528. // Resulting _edit_form.php template
  529. <?php echo object_input_tag($article, 'getTitle', array('control_name' => 'article[title]')) ?>
  530. // Resulting HTML
  531. <input type="text" name="article[title]" id="article_title" value="My Title" />
  532. This has plenty of advantages during the internal form-handling process. However, as explained in Chapter 10, it makes the form validation configuration a bit trickier, so you have to change square brackets, `[` `]`, to curly braces, `{` `}`, in the `fields` definition. Also, when using a field name as a parameter for a validator, you should use the name as it appears in the generated HTML code (that is, with the square brackets, but between quotes). Refer to Listing 14-36 for a detail of the special validator syntax for generated forms.
  533. Listing 14-36 - Validator File Syntax for Administration-Generated Forms
  534. ## Replace square brackets by curly brackets in the fields list
  535. fields:
  536. article{title}:
  537. required:
  538. msg: You must provide a title
  539. ## For validator parameters, use the original field name between quotes
  540. sfCompareValidator:
  541. check: "user[newpassword]"
  542. compare_error: The password confirmation does not match the password.
  543. ### Restricting User Actions Using Credentials
  544. For a given administration module, the available fields and interactions can vary according to the credentials of the logged user (refer to Chapter 6 for a description of symfony's security features).
  545. The fields in the generator can take a `credentials` parameter into account so as to appear only to users who have the proper credential. This works for the `list` view and the `edit` view. Additionally, the generator can also hide interactions according to credentials. Listing 14-37 demonstrates these features.
  546. Listing 14-37 - Using Credentials in `generator.yml`
  547. ## The id column is displayed only for users with the admin credential
  548. list:
  549. title: List of Articles
  550. layout: tabular
  551. display: [id, =title, content, nb_comments]
  552. fields:
  553. id: { credentials: [admin] }
  554. ## The addcomment interaction is restricted to the users with the admin credential
  555. list:
  556. title: List of Articles
  557. object_actions:
  558. _edit: -
  559. _delete: -
  560. addcomment: { credentials: [admin], name: Add a comment, action: addComment, icon: backend/addcomment.png }
  561. Modifying the Presentation of Generated Modules
  562. -----------------------------------------------
  563. You can modify the presentation of the generated modules so that it matches any existing graphical charter, not only by applying your own style sheet, but also by overriding the default templates.
  564. ### Using a Custom Style Sheet
  565. Since the generated HTML is structured content, you can do pretty much anything you like with the presentation.
  566. You can define an alternative CSS to be used for an administration module instead of a default one by adding a `css` parameter to the generator configuration, as in Listing 14-38.
  567. Listing 14-38 - Using a Custom Style Sheet Instead of the Default One
  568. generator:
  569. class: sfPropelAdminGenerator
  570. param:
  571. model_class: Comment
  572. theme: default
  573. css: mystylesheet
  574. Alternatively, you can also use the mechanisms provided by the module `view.yml` to override the styles on a per-view basis.
  575. ### Creating a Custom Header and Footer
  576. The `list` and `edit` views systematically include a header and footer partial. There is no such partial by default in the `templates/` directory of an administration module, but you just need to add one with one of the following names to have it included automatically:
  577. _list_header.php
  578. _list_footer.php
  579. _edit_header.php
  580. _edit_footer.php
  581. For instance, if you want to add a custom header to the `article/edit` view, create a file called `_edit_header.php` as in Listing 14-39. It will work with no further configuration.
  582. Listing 14-39 - Example `edit` Header Partial, in `modules/articles/templates/_edit_header.php`
  583. [php]
  584. <?php if ($article->getNbComments() > 0): ?>
  585. <h2>This article has <?php echo $article->getNbComments() ?> comments.</h2>
  586. <?php endif; ?>
  587. Notice that an edit partial always has access to the current object through a variable named after the class, and that a `list` partial always has access to the current pager through the `$pager` variable.
  588. >**SIDEBAR**
  589. >Calling the Administration Actions with Custom Parameters
  590. >
  591. >The administration module actions can receive custom parameters using the `query_string` argument in a `link_to()` helper. For example, to extend the previous `_edit_header` partial with a link to the comments for the article, write this:
  592. >
  593. > [php]
  594. > This article has <?php echo link_to($article->getNbComments().' comments', 'comment/list', array('query_string' => 'filter=filter&filters%5Barticle_id%5D='.$article->getId())) ?> comments.
  595. >
  596. >This query string parameter is an encoded version of the more legible
  597. >
  598. > 'filter=filter&filters[article_id]='.$article->getId()
  599. >
  600. >It filters the comments to display only the ones related to `$article`. Using the query_string argument, you can specify a sorting order and/or a filter to display a custom list view. This can also be useful for custom interactions.
  601. ### Customizing the Theme
  602. There are other partials inherited from the framework that can be overridden in the module `templates/` folder to match your custom requirements.
  603. The generator templates are cut into small parts that can be overridden independently, and the actions can also be changed one by one.
  604. However, if you want to override those for several modules in the same way, you should probably create a reusable theme. A theme is a complete set of templates and actions that can be used by an administration module if specified in the theme value at the beginning of `generator.yml`. With the default theme, symfony uses the files defined in `$sf_symfony_lib_dir/plugins/sfPropelPlugin/data/generator/sfPropelAdmin/default/`.
  605. The theme files must be located in a project tree structure, in a `data/generator/sfPropelAdmin/[theme_name]/template/` directory, and you can bootstrap a new theme by copying the files from the default theme (located in `$sf_symfony_lib_dir/plugins/sfPropelPlugin/data/generator/sfPropelAdmin/default/template/` directory). This way, you are sure that all the files required for a theme will be present in your custom theme:
  606. // Partials, in [theme_name]/template/templates/
  607. _edit_actions.php
  608. _edit_footer.php
  609. _edit_form.php
  610. _edit_header.php
  611. _edit_messages.php
  612. _filters.php
  613. _list.php
  614. _list_actions.php
  615. _list_footer.php
  616. _list_header.php
  617. _list_messages.php
  618. _list_td_actions.php
  619. _list_td_stacked.php
  620. _list_td_tabular.php
  621. _list_th_stacked.php
  622. _list_th_tabular.php
  623. // Actions, in [theme_name]/template/actions/actions.class.php
  624. processFilters() // Process the request filters
  625. addFiltersCriteria() // Adds a filter to the Criteria object
  626. processSort()
  627. addSortCriteria()
  628. Be aware that the template files are actually templates of templates, that is, PHP files that will be parsed by a special utility to generate templates based on generator settings (this is called the compilation phase). The generated templates must still contain PHP code to be executed during actual browsing, so the templates of templates use an alternative syntax to keep PHP code unexecuted for the first pass. Listing 14-40 shows an extract of a default template of template.
  629. Listing 14-40 - Syntax of Templates of Templates
  630. [php]
  631. <?php foreach ($this->getPrimaryKey() as $pk): ?>
  632. [?php echo object_input_hidden_tag($<?php echo $this->getSingularName() ?>,'get<?php echo $pk->getPhpName() ?>') ?]
  633. <?php endforeach; ?>
  634. In this listing, the PHP code introduced by `<?` is executed immediately (at compilation), the one introduced by `[?` is only executed at execution, but the templating engine finally transforms the `[?` tags into `<?` tags so that the resulting template looks like this:
  635. [php]
  636. <?php echo object_input_hidden_tag($article, 'getId') ?>
  637. Dealing with templates of templates is tricky, so the best advice if you want to create your own theme is to start from the `default` theme, modify it step by step, and test it extensively.
  638. >**TIP**
  639. >You can also package a generator theme in a plug-in, which makes it even more reusable and easy to deploy across multiple applications. Refer to Chapter 17 for more information.
  640. -
  641. >**SIDEBAR**
  642. >Building Your Own Generator
  643. >
  644. >The administration generator use a set of symfony internal components that automate the creation of generated actions and templates in the cache, the use of themes, and the parsing of templates of templates.
  645. >
  646. >This means that symfony provides all the tools to build your own generator, which can look like the existing ones or be completely different. The generation of a module is managed by the `generate()` method of the `sfGeneratorManager` class. For instance, to generate an administration, symfony calls the following internally:
  647. >
  648. > [php]
  649. > $generator_manager = new sfGeneratorManager();
  650. > $data = $generator_manager->generate('sfPropelAdminGenerator', $parameters);
  651. >
  652. >If you want to build your own generator, you should look at the API documentation of the `sfGeneratorManager` and the `sfGenerator` classes, and take as examples the `sfAdminGenerator` and `sfCRUDGenerator` classes.
  653. Summary
  654. -------
  655. To automatically generate your back-end applications, the basis is a well-defined schema and object model. The customization of the administration-generated modules are to be done mostly through configuration.
  656. The `generator.yml` file is the heart of the programming of generated back-ends. It allows for the complete customization of content, features, and the look and feel of the `list` and `edit` views. You can manage field labels, tooltips, filters, sort order, page size, input type, foreign relationships, custom interactions, and credentials directly in YAML, without a single line of PHP code.
  657. If the administration generator doesn't natively support the feature you need, the partial fields and the ability to override actions provide complete extensibility. Plus, you can reuse your adaptations to the administration generator mechanisms thanks to the theme mechanisms.