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, and keep it close to you when you read the following examples of this chapter.
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 by creating a route and launching the propel:generate-admin
task:
comment:
class: sfPropelRouteCollection
options:
model: Comment
module: comment
with_wildcard_routes: true
> php symfony propel:init-admin backend comment Comment
14.3.1. 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 (ArticleForm
). 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.
14.3.1.1. 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
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
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
andedit
inherits fromform
which inherits fromfields
list
inherits fromfields
filter
inherits fromfields
14.3.1.2. 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
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.
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
, andedit
views: Use an associative array to group fields with the group name as a key, orNONE
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.
14.3.1.3. 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 Article
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/Article.php
public function getNbComments()
{
return $this->countComments();
}
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
config:
list:
display: [id, title, nb_comments, created_at]
The resulting list
view of the article
module is shown in Figure 14-10.
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
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
config:
list:
display: [id, article_link, content]
14.3.1.4. 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
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 echo link_to($comment->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
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
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.
14.3.2. 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.
14.3.2.1. 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
config:
list:
title: List of Articles
new:
title: New Article
edit:
title: Edit Article %%title%% (%%id%%)
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 %%
.
14.3.2.2. 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
config:
edit:
fields:
article_id: { help: The current comment relates to this article }
In the list
view, tooltips are displayed in the column header; in the new
, edit
, and filter
views, they appear under the field tag.
14.3.2.3. 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
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.
14.3.3. 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.
14.3.3.1. 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
config:
list:
display: [article_link, =content]
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
config:
list:
layout: stacked
params: |
%%=content%%<br />
(sent by %%author%% on %%created_at%% about %%article_link%%)
display: [created_at, author, content]
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.
14.3.3.2. Filtering the Results ###
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 filter
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.
Listing 14-23 - Setting the Filters in the list
View, in modules/comment/config/generator.yml
config:
list:
layout: stacked
params: |
%%=content%% <br />
(sent by %%author%% on %%created_at%% about %%article_link%%)
display: [created_at, author, content]
filter:
display: [article_id, author, created_at]
The filters displayed by symfony depend on the column type defined in the schema, and can be customized in the filter form class:
- For text columns (like the
author
field in thecomment
module), the filter is a text input allowing text-based search (wildcards are automatically added). - For foreign keys (like the
article_id
field in thecomment
module), the filter is a drop-down list of the records of the related table. By default, the options of the drop-down list are the ones returned by the__toString()
method of the related class. - For date columns (like the
created_at
field in thecomment
module), the filter is a pair of rich date tags, allowing the selection of a time interval. - For Boolean columns, the filter is a drop-down list having
true
,false
, andtrue or false
options — the last value reinitializes the filter.
Just like the new
and edit
views are tied to a form class, the filters use the default filter form class associated with the model (ArticleFormFilter
for the Article
model for example). By defining a custom class for the filter form, you can customize the filter fields by leveraging the power of the form framework and by using all the available filter widgets. It is as easy as defining a class
under the filter
entry as shown in Listing 14-24.
Listing 14-24 - Customizing the Form Class used for Filtering
config:
filter:
class: BackendArticleFormFilter
Tip To disable filters altogether, you can just specify false
as the class
to use for the filters.
You can also use partial filters to modify a filter that symfony. Each partial receives the form
and the HTML attributes
to use when rendering the form element. Listing 14-24 shows an example implementation to mimics the default behavior but with a partial.
Listing 14-24 - Using a Partial Filter
// Define the partial, in templates/_state.php
<?php echo $form[$name]->render($attributes->getRawValue()) ?>
// Add the partial filter in the filter list, in config/generator.yml
config:
filter: [date, _state]
14.3.3.3. Sorting the List ###
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.
You can reuse the syntax to point to a list directly sorted according to a column:
<?php echo link_to('Comment list by date', '@comments?sort=created_at&sort_type=desc' ) ?>
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.
Listing 14-26 - Setting a Default Sort Field in the list
View
config:
list:
sort: created_at
# Alternative syntax, to specify a sort order
sort: [created_at, desc]
Note Only the fields that correspond to an actual column are transformed into sort controls — not the custom or partial fields.
14.3.3.4. Customizing the Pagination ###
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.
You can customize the number of records to be displayed in each page with the max_per_page
parameter:
config:
list:
max_per_page: 5
14.3.3.5. Using a Join to Speed Up Page Delivery ###
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.
config:
list:
peer_method: doSelectJoinArticle
Chapter 18 explains the concept of Join more extensively.
14.3.4. New and Edit View-Specific Customization
In a new
or edit
view, the user can modify the value of each column for a new record or a given record. By default, the form used by the admin generator is the form associated with the model: BlogArticleForm
for the BlogArticle
model. You can customize the class to use by defining the class
under the form
entry as shown in Listing 14-27.
Listing 14-27 - Customizing the Form Class used for the new
and edit
views
config:
form:
class: BackendBlogArticleForm
Using a custom form class allows the customization of all the widgets and validators used for the admin generator. The default form class can then be used and customized specifically for the frontend application.
You can also customize the labels, help messages, and the layout of the form directly in the generator.yml
configuration file as show in Listing 14-28.
Listing 14-28 - Customizing the Form Display
config:
form:
display:
NONE: [article_id]
Editable: [author, content, created_at]
fields:
content: { label: body, help: "The content can be in the Markdown format" }
14.3.4.1. Handling Partial Fields
Partial fields can be used in the new
and edit
views just like in list
views.
14.3.5. Dealing with Foreign Keys
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.
14.3.5.1. One-to-Many Relationships
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 BlogComment
class with the administration generator, the edit
view 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).
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.
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.
14.3.5.2. Many-to-Many Relationships
Symfony also takes care of n-n table relationships as shown in Figure 14-20.
By customizing the widget used to render the relationship, you can tweak the rendering of the field (illustrated in Figure 14-21):
14.3.6. Adding Interactions
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.
Listing 14-31 - Defining Interactions for Each View, in backend/modules/article/config/generator.yml
config:
list:
title: List of Articles
object_actions:
_edit: ~
_delete: ~
batch_actions:
_delete: ~
actions:
_new: ~
edit:
title: Body of article %%title%%
actions:
_delete: ~
_list: ~
_save: ~
_save_and_add: ~
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.
In a new
and edit
views, 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 a new
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.
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
, _new
, _list
, _save
, _save_and_add
, and _create
.
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.
Listing 14-32 - Defining a Custom Interaction
list:
title: List of Articles
object_actions:
_edit: -
_delete: -
addcomment: { label: Add a comment, action: addComment }
Each article in the list will now show the Add a comment
link, 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.
The addComment
action can be implemented as in Listing 14-33.
Listing 14-33 - Implementing the Custom Interaction Action, in actions/actions.class.php
public function executeAddComment($request)
{
$comment = new Comment();
$comment->setArticleId($request->getParameter('id'));
$comment->save();
$this->redirect('comments_edit', $comment);
}
Batch actions receive an array of the primary keys of the selected records in the sf_admin_batch_selection
request parameter.
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.
Listing 14-34 - Removing All Actions in the list
View
config:
list:
title: List of Articles
actions: {}
14.3.7. Form Validation
The validation is taken care of by the form used by the new
and edit
views automatically. You can customize it by editing the corresponding form classes.
14.3.8. Restricting User Actions Using Credentials
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).
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
entry. Additionally, the generator can also hide interactions according to credentials. Listing 14-37 demonstrates these features.
Listing 14-37 - Using Credentials in generator.yml
config:
# The id column is displayed only for users with the admin credential
list:
title: List of Articles
display: [id, =title, content, nb_comments]
fields:
id: { credentials: [admin] }
# The addcomment interaction is restricted to the users with the admin credential
actions:
addcomment: { credentials: [admin] }