Chapter 14 - Admin Generator
============================
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.
Code Generation Based on the Model
----------------------------------
In a web application, data access operations can be categorized as one of the following:
* Creation of a record
* Retrieval of records
* Update of a record (and modification of its columns)
* Deletion of a record
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.
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.
### Example Data Model
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 `BlogArticle` and `BlogComment` classes. Listing 14-1 shows its schema, illustrated in Figure 14-1.
Listing 14-1 - `schema.yml` of the Example Weblog Application
[yml]
propel:
blog_article:
id: ~
title: varchar(255)
content: longvarchar
author_id: ~
category_id: ~
is_published: integer(1)
created_at: ~
blog_comment:
id: ~
blog_article_id: ~
author: varchar(255)
content: longvarchar
created_at: ~
Figure 14-1 - Example data model
![Example data model](/images/book/F1401.png "Example data model")
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.
>**TIP**
>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.
Administration
--------------
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:
> php symfony generate:app backend
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, 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.
>**NOTE**
>Even if most common requirements are covered by the `generator.yml` configuration file, you can also configure an administration module via a configuration class as we will see later in this chapter.
### Initiating an Administration Module
With symfony, you build an administration on a per-model basis. A module is generated based on a Propel object using the `propel:generate-admin` task:
> php symfony propel:generate-admin backend BlogArticle --module=article
This call is enough to create an `article` module in the `backend` application based on the `BlogArticle` class definition, and is accessible by the following:
http://localhost/backend.php/article
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.
>**NOTE**
>The administration modules are based on a REST architecture. The `propel:generate-admin` task automatically adds such a route to the `routing.yml` configuration file:
>
> [yml]
> # apps/backend/config/routing.yml
> article:
> class: sfPropelRouteCollection
> options:
> model: BlogArticle
> module: article
> with_wildcard_routes: true
>
>You can also create your own route and pass the name as an argument to the task instead of the model class name:
>
> > php symfony propel:generate-admin backend BlogArticle --module=article
Figure 14-5 - `list` view of the `article` module in the `backend` application
![list view of the article module in the backend application](/images/book/F1405.png "list view of the article module in the backend application")
Figure 14-6 - `edit` view of the `article` module in the `backend` application
![edit view of the article module in the backend application](/images/book/F1406.png "edit view of the article module in the backend application")
>**TIP**
>If you don't see the expected look and feel (no stylesheet and no image), this is because you need to install the assets in your project by running the `plugin:publish-assets` task:
>
> $ php symfony plugin:publish-assets
### A Look at the Generated Code
The code of the article administration module, in the `apps/backend/modules/article/` directory, looks 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.
Listing 14-4 - Generated Administration Elements, in `cache/backend/ENV/modules/article/`
// Actions in actions/actions.class.php
index // Displays the list of the records of the table
filter // Updates the filters used by the list
new // Displays the form to create a new record
create // Creates a new record
edit // Displays a form to modify the fields of a record
update // Updates an existing record
delete // Deletes a record
batch // Executes an action on a list of selected records
// In templates/
_assets.php
_filters.php
_filters_field.php
_flashes.php
_form.php
_form_actions.php
_form_field.php
_form_fieldset.php
_form_footer.php
_form_header.php
_list.php
_list_actions.php
_list_batch_actions.php
_list_field_boolean.php
_list_footer.php
_list_header.php
_list_td_actions.php
_list_td_batch_actions.php
_list_td_stacked.php
_list_td_tabular.php
_list_th_stacked.php
_list_th_tabular.php
_pagination.php
editSuccess.php
indexSuccess.php
newSuccess.php
This shows that a generated administration module is composed mainly of three views, `list`, `new`, and `edit`. If you have a look at the code, you will find it to be very modular, readable, and extensible.
### Introducing the generator.yml Configuration File
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.
Listing 14-5 - Default Generator Configuration, in `backend/modules/article/config/generator.yml`
[yml]
generator:
class: sfPropelGenerator
param:
model_class: BlogArticle
theme: admin
non_verbose_templates: true
with_show: false
singular: ~
plural: ~
route_prefix: article
with_propel_route: 1
config:
actions: ~
list: ~
filter: ~
form: ~
edit: ~
new: ~
This configuration is enough to generate the basic administration. Any customization is added under the `config` key. Listing 14-6 shows a typical customized `generator.yml`.
Listing 14-6 - Typical Complete Generator Configuration
[yml]
generator:
class: sfPropelGenerator
param:
model_class: BlogArticle
theme: admin
non_verbose_templates: true
with_show: false
singular: ~
plural: ~
route_prefix: article
with_propel_route: 1
config:
actions:
_new: { label: "Create a new article", credentials: editor }
fields:
author_id: { label: Article author }
published_on: { credentials: editor }
list:
title: Articles
display: [title, author_id, category_id]
fields:
published_on: { date_format: dd/MM/yy }
layout: stacked
params: |
%%is_published%%%%=title%%
by %%author%%
in %%category%% (%%published_on%%)
%%content_summary%%
max_per_page: 2 sort: [title, asc] filter: display: [title, category_id, author_id, is_published] form: display: "Post": [title, category_id, content] "Workflow": [author_id, is_published, created_at] fields: published_at: { help: "Date of publication" } title: { attributes: { style: "width: 350px" } } new: title: New article edit: title: Editing article "%%title%%" In this configuration, there are six sections. Four of them represent views (`list`, `filter`, `new`, and `edit`) and two of them are "virtuals" (`fields` and `form`) and only exists for configuration purpose. The following sections explain in detail all the parameters that can be used in this configuration file. Generator Configuration ----------------------- 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 examples of this section will tweak the `article` administration module, as well as the `comment` administration module, based on the `BlogComment` class definition. Create the latter by launching the `propel:generate-admin` task: > php symfony propel:generate-admin backend BlogComment --module=comment Figure 14-7 - The administration generator cheat sheet ![The administration generator cheat sheet](/images/book/F1407.png "The administration generator cheat sheet") ### Fields By default, the columns of the `list` view are the columns defined in `schema.yml`. The fields of the `new` and `edit` views are the one defined in the form associated with the model (`BlogArticleForm`). 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. #### Field Settings 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. Listing 14-7 - Setting a Custom Label for a Column [yml] config: fields: title: label: Article Title attributes: { class: foo } content: { label: Body, help: Fill in the article body } In addition to this default definition for all the views, you can override the field settings for a given view (`list`, `filter`, `form`, `new`, and `edit`), as demonstrated in Listing 14-8. Listing 14-8 - Overriding Global Settings View per View [yml] config: fields: title: { label: Article Title } content: { label: Body } list: fields: title: { label: Title } form: fields: content: { label: Body of the article } This is a general principle: Any settings that are set for the whole module under the `fields` key can be overridden by view-specific areas. The overriding rules are the following: * `new` and `edit` inherits from `form` which inherits from `fields` * `list` inherits from `fields` * `filter` inherits from `fields` #### Adding Fields to the Display 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. Listing 14-9 - Choosing the Fields to Display, in `modules/comment/config/generator.yml` [yml] config: fields: article_id: { label: Article } created_at: { label: Published on } content: { label: Body } list: display: [id, article_id, content] form: display: NONE: [article_id] Editable: [author, content, created_at] The `list` will then display three columns, as in Figure 14-8, and the `new` and `edit` form will display four fields, assembled in two groups, as in Figure 14-9. Figure 14-8 - Custom column setting in the `list` view of the `comment` module ![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") Figure 14-9 - Grouping fields in the `edit` view of the `comment` module ![Grouping fields in the edit view of the comment module](/images/book/F1409.png "Grouping fields in the edit view of the comment module") So you can use the `display` setting in two ways: * For the `list` view: Put the fields in a simple array to select the columns to display and the order in which they appear. * For the `form`, `new`, and `edit` views: Use an associative array to group fields 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. Be careful to list all the required fields referenced in your form class or you may have some unexpected validation errors. #### Custom Fields 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 `BlogArticle` model with a `getNbComments()` method similar to the one in Listing 14-10. Listing 14-10 - Adding a Custom Getter in the Model, in `lib/model/BlogArticle.php` [php] public function getNbComments() { return $this->countBlogComments(); } 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. Listing 14-11 - Custom Getters Provide Additional Columns for Administration Modules, in `backend/modules/article/config/generator.yml` [yml] config: list: display: [id, title, nb_comments, created_at] The resulting `list` view of the `article` module is shown in Figure 14-10. Figure 14-10 - Custom field in the `list` view of the `article` module ![Custom field in the list view of the article module](/images/book/F1410.png "Custom field in the list view of the article module") Custom fields can even return HTML code to display more than raw data. For instance, you can extend the `BlogComment` class with a `getArticleLink()` method as in Listing 14-12. Listing 14-12 - Adding a Custom Getter Returning HTML, in `lib/model/BlogComment.php` [php] public function getArticleLink() { return link_to($this->getBlogArticle()->getTitle(), 'article_edit', $this->getBlogArticle()); } 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. Listing 14-13 - Custom Getters Returning HTML Can Also Be Used As Additional Columns, in `modules/comment/config/generator.yml` [yml] config: list: display: [id, article_link, content] Figure 14-11 - Custom field in the `list` view of the `comment` module ![Custom field in the list view of the comment module](/images/book/F1411.png "Custom field in the list view of the comment module") #### Partial Fields 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. Listing 14-14 - Partials Can Be Used As Additional Columns--Use the `_` Prefix [yml] config: list: display: [id, _article_link, created_at] For this to work, an `_article_link.php` partial must be created in the `modules/comment/templates/` directory, as in Listing 14-15. Listing 14-15 - Example Partial for the `list` View, in `modules/comment/templates/_article_link.php` [php] getBlogArticle()->getTitle(), '@article_edit', $comment->getBlogArticle()) ?> 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. 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. 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. Listing 14-16 - Partial Field Properties Can Be Customized Under the `fields` Key [yml] config: fields: article_link: { label: Article } 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. Listing 14-17 - Components Can Be Used As Additional Columns--Use the `~` Prefix [yml] config: list: display: [id, ~article_link, created_at] In the generated template, this will result by a call to the `articleLink` component of the current module. >**NOTE** >Custom and partial fields can be used in the `list`, `new`, `edit` and `filter` views. If you use the same partial for several views, the context (`list`, `new`, `edit`, or `filter`) is stored in the `$type` variable. ### View Customization To change the `new`, `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. #### Changing the View Title In addition to a custom set of fields, the `list`, `new`, 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. Listing 14-18 - Setting a Custom Title for Each View, in `backend/modules/article/config/generator.yml` [yml] config: list: title: List of Articles new: title: New Article edit: title: Edit Article %%title%% (%%id%%) Figure 14-12 - Custom title in the `edit` view of the `article` module ![Custom title in the edit view of the article module](/images/book/F1412.png "Custom title in the edit view of the article module") As the default titles use the class name, they are often good enough--provided that your model uses explicit class names. >**TIP** >In the string values of `generator.yml`, the value of a field can be accessed via the name of the field surrounded by `%%`. #### Adding Tooltips In the `list`, `new`, `edit`, and `filter` 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. Listing 14-19 - Setting a Tooltip in the `edit` View, in `modules/comment/config/generator.yml` [yml] config: edit: fields: article_id: { help: The current comment relates to this article } Figure 14-13 - Tooltip in the `edit` view of the `comment` module ![Tooltip in the edit view of the comment module](/images/book/F1413.png "Tooltip in the edit view of the comment module") In the `list` view, tooltips are displayed in the column header; in the `new`, `edit`, and `filter` views, they appear under the field tag. #### Modifying the Date Format Dates can be displayed using a custom format as soon as you use the `date_format` option, as demonstrated in Listing 14-20. Listing 14-20 - Formatting a Date in the `list` View [yml] config: list: fields: created_at: { label: Published, date_format: dd/MM } It takes the same format parameter as the `format_date()` helper described in the previous chapter. >**SIDEBAR** >Administration templates are i18N ready > >The admin generated modules are made of interface strings (default action names, pagination help messages, ...) and custom strings (titles, column labels, help messages, error messages, ...). > >Translations of the interface strings are bundled with symfony for a lot of languages. But you can also add your own or override existing ones by creating a custom XLIFF file in your `i18n` directory for the `sf_admin` catalogue (`apps/frontend/i18n/sf_admin.XX.xml` where `XX` is the ISO code of the language). > >All the custom strings found in the generated templates are also 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. > >You can change the default catalogue used for the custom strings by specifying an `i18n_catalogue` parameter: > > [yml] > generator: > class: sfPropelGenerator > param: > i18n_catalogue: admin ### List View-Specific Customization 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. #### Changing the Layout 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. 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. Listing 14-21 - Moving the Hyperlink for the `edit` View in the `list` View, in `modules/comment/config/generator.yml` [yml] config: list: display: [article_link, =content] Figure 14-14 - Moving the link to the `edit` view on another column, in the `list` view of the `comment` module ![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") 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. Listing 14-22 - Using a `stacked` Layout in the `list` View, in `modules/comment/config/generator.yml` [yml] config: list: layout: stacked params: | %%=content%%