Extending back office grids and forms
Symfony back office pages are built from two bricks: a grid with a list and a form. A module can extend someone else's grid or form — add a column to the customers list, a field to the customer form. For that every grid and form has a set of action hooks whose names follow a pattern.
Grid hooks
| Hook | What it lets you change |
|---|---|
action<Name>GridDefinitionModifier | The set of columns, filters and actions |
action<Name>GridQueryBuilderModifier | The SQL query that selects the rows |
action<Name>GridDataModifier | The rows already selected |
<Name> is the grid id: Customer, Order, Category, Address. For the customers list the hooks are called actionCustomerGridDefinitionModifier and actionCustomerGridQueryBuilderModifier.
A column in the customers list
Let us add a "Reviews" column to the customers list. First the column description:
// modules/acmehello/acmehello.php
use PrestaShop\PrestaShop\Core\Grid\Column\Type\DataColumn;
public function hookActionCustomerGridDefinitionModifier(array $params): void
{
$column = new DataColumn('reviews_count');
$column->setName($this->trans('Reviews', [], 'Modules.Acmehello.Admin'));
$column->setOptions(['field' => 'reviews_count']);
$params['definition']->getColumns()->addAfter('email', $column);
}The use line goes at the top of the file, before the class declaration. $params['definition'] holds the grid description. addAfter('email', ...) inserts the column after the email column, and the field option says which field of a row holds the value.
The value for the column
The query has no reviews_count field yet. The second hook adds it:
// modules/acmehello/acmehello.php
public function hookActionCustomerGridQueryBuilderModifier(array $params): void
{
$params['search_query_builder']->addSelect(
'(SELECT COUNT(*) FROM `' . _DB_PREFIX_ . 'acmehello_review` r
WHERE r.id_customer = c.id_customer) AS reviews_count'
);
}search_query_builder is the Doctrine query builder the grid uses to select customers; the customers table has the alias c in it. The subquery counts each customer's reviews by the id_customer column added in the lesson on tables.
How to see the result
Register both hooks in install() and reset the module:
&& $this->registerHook('actionCustomerGridDefinitionModifier')
&& $this->registerHook('actionCustomerGridQueryBuilderModifier')php bin/console prestashop:module reset acmehelloOpen "Customers → Customers": after the email column there is a "Reviews" column with a number for every customer.
Form hooks
| Hook | When it fires |
|---|---|
action<Name>FormBuilderModifier | The form is being built — a field can be added |
actionAfterCreate<Name>FormHandler | A new record is saved |
actionAfterUpdate<Name>FormHandler | An existing record is saved |
A field in the customer form
// modules/acmehello/acmehello.php
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
public function hookActionCustomerFormBuilderModifier(array $params): void
{
$params['form_builder']->add('acme_trusted', CheckboxType::class, [
'label' => $this->trans('Trusted review author', [], 'Modules.Acmehello.Admin'),
'required' => false,
]);
$params['data']['acme_trusted'] = (bool) Configuration::get('ACMEHELLO_TRUSTED_' . (int) $params['id']);
$params['form_builder']->setData($params['data']);
}form_builder is the Symfony form builder. The add() method adds a checkbox, and setData() fills in its current value. $params['id'] holds the customer id; it is empty when a new customer is being created.
Saving the field
// modules/acmehello/acmehello.php
public function hookActionAfterUpdateCustomerFormHandler(array $params): void
{
Configuration::updateValue(
'ACMEHELLO_TRUSTED_' . (int) $params['id'],
(int) !empty($params['form_data']['acme_trusted'])
);
}form_data holds the values of all form fields, including the added one. Here the checkbox is kept in Configuration for simplicity; a real module would add a column to its own table for that. After registering the three hooks and resetting the module, the customer form shows the checkbox and keeps its state.
Common mistake
A column is added to the grid definition, but the query is not changed. The list page fails with an unknown field error or shows an empty column. The GridDefinitionModifier and GridQueryBuilderModifier hooks almost always come as a pair: the first declares the column, the second supplies its data.