Skip to content
  • About
    • What is Symfony?
    • Community
    • News
    • Contributing
    • Support
  • Documentation
    • Symfony Docs
    • Symfony Book
    • Screencasts
    • Symfony Bundles
    • Symfony Cloud
    • Training
  • Services
    • Platform.sh for Symfony Best platform to deploy Symfony apps
    • SymfonyInsight Automatic quality checks for your apps
    • Symfony Certification Prove your knowledge and boost your career
    • SensioLabs Professional services to help you with Symfony
    • Blackfire Profile and monitor performance of your apps
  • Other
  • Blog
  • Download
sponsored by
  1. Home
  2. Documentation
  3. Bundles
  4. SonataAdminBundle
  5. Batch actions

Batch actions

Edit this page

Batch actions are actions triggered on a set of selected objects. By default, Admins have a delete action which allows you to remove several entries at once.

Defining new actions

To create a new custom batch action which appears in the list view follow these steps:

Override configureBatchActions() in your Admin class to define the new batch actions by adding them to the $actions array. Each key represent a batch action and could contain these settings:

  • label: The name to use when offering this option to users, should be passed through the translator (default: the label is generated via the labelTranslatorStrategy)
  • translation_domain: The domain which will be used to translate the key. (default: the translation domain of the admin)
  • ask_confirmation: defaults to true and means that the user will be asked for confirmation before the batch action is processed
  • template: Override ask_confirmation template for this specific action. This allows you to specify different templates for each batch action that requires confirmation.

For example, lets define a new merge action which takes a number of source items and merges them onto a single target item. It should only be available when two conditions are met:

  • the EDIT and DELETE routes exist for this Admin (have not been disabled)
  • the logged in administrator has EDIT and DELETE permissions:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    protected function configureBatchActions(array $actions): array
    {
        if (
          $this->hasRoute('edit') && $this->hasAccess('edit') &&
          $this->hasRoute('delete') && $this->hasAccess('delete')
        ) {
            $actions['merge'] = [
                'ask_confirmation' => true,
                'controller' => 'app.controller.merge::batchMergeAction',
                // Or 'App/Controller/MergeController::batchMergeAction' base on how you declare your controller service.
            ];
        }
    
        return $actions;
    }

Define the core action logic

Define a regular Symfony controller like you normally would (without a route). Make sure you configure your controller as a service and tag it with controller.service_arguments. The parameter will be automatically injected. The AdminInterface is done via a param converter already available in SonataAdminBundle. The $query is unique to the context of this request. There is no requirement on the base class or any other logic, this is just an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// src/Controller/MergeController.php

namespace App\Controller;

use Sonata\AdminBundle\Admin\AdminInterface;
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;

class MergeController extends AbstractController
{
    public function batchMergeAction(ProxyQueryInterface $query, AdminInterface $admin): RedirectResponse
    {
        $admin->checkAccess('edit');
        $admin->checkAccess('delete');

        $modelManager = $admin->getModelManager();

        $target = $modelManager->find($admin->getClass(), $request->get('targetId'));

        if ($target === null) {
            $this->addFlash('sonata_flash_info', 'flash_batch_merge_no_target');

            return new RedirectResponse(
                $admin->generateUrl('list', [
                    'filter' => $admin->getFilterParameters()
                ])
            );
        }

        $selectedModels = $query->execute();

        // do the merge work here

        try {
            foreach ($selectedModels as $selectedModel) {
                $modelManager->delete($selectedModel);
            }

            $this->addFlash('sonata_flash_success', 'flash_batch_merge_success');
        } catch (\Exception $e) {
            $this->addFlash('sonata_flash_error', 'flash_batch_merge_error');
        } finally {
            return new RedirectResponse(
                $admin->generateUrl('list', [
                    'filter' => $admin->getFilterParameters()
                ])
            );
        }
    }

    // ...
}

(Deprecated) Define the core action logic

Deprecated: This is the old way to do this. Will be removed in version 5.x.

The method batchAction<MyAction> will be executed to process your batch in your CRUDController class. The selected objects are passed to this method through a query argument which can be used to retrieve them. If for some reason it makes sense to perform your batch action without the default selection method (for example you defined another way, at template level, to select model at a lower granularity), the passed query is null:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// src/Controller/CRUDController.php

namespace App\Controller;

use Sonata\AdminBundle\Controller\CRUDController as BaseController;
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

class CRUDController extends BaseController
{
    /**
     * @param ProxyQueryInterface $selectedModelQuery
     * @param Request             $request
     *
     * @return RedirectResponse
     */
    public function batchActionMerge(ProxyQueryInterface $selectedModelQuery, Request $request)
    {
        $this->admin->checkAccess('edit');
        $this->admin->checkAccess('delete');

        $modelManager = $this->admin->getModelManager();

        $target = $modelManager->find($this->admin->getClass(), $request->get('targetId'));

        if ($target === null){
            $this->addFlash('sonata_flash_info', 'flash_batch_merge_no_target');

            return new RedirectResponse(
                $this->admin->generateUrl('list', [
                    'filter' => $this->admin->getFilterParameters()
                ])
            );
        }

        $selectedModels = $selectedModelQuery->execute();

        // do the merge work here

        try {
            foreach ($selectedModels as $selectedModel) {
                $modelManager->delete($selectedModel);
            }

            $modelManager->update($selectedModel);
        } catch (\Exception $e) {
            $this->addFlash('sonata_flash_error', 'flash_batch_merge_error');

            return new RedirectResponse(
                $this->admin->generateUrl('list', [
                    'filter' => $this->admin->getFilterParameters()
                ])
            );
        }

        $this->addFlash('sonata_flash_success', 'flash_batch_merge_success');

        return new RedirectResponse(
            $this->admin->generateUrl('list', [
                'filter' => $this->admin->getFilterParameters()
            ])
        );
    }

    // ...
}

Note

You can check how to declare your own CRUDController class in the Architecture section.

(Optional) Overriding the batch selection template

A merge action requires two kinds of selection: a set of source objects to merge from and a target object to merge into. By default, batch_actions only let you select one set of objects to manipulate. We can override this behavior by changing our list template (list__batch.html.twig) and adding a radio button to choose the target object.

1
2
3
4
5
6
7
8
9
10
11
12
{# templates/bundles/SonataAdminBundle/CRUD/list__batch.html.twig #}

{# see @SonataAdmin/CRUD/list__batch.html.twig for the current default template #}

{% extends get_admin_template('base_list_field', admin.code) %}

{% block field %}
    <input type="checkbox" name="idx[]" value="{{ admin.id(object) }}"/>

    {# the new radio button #}
    <input type="radio" name="targetId" value="{{ admin.id(object) }}"/>
{% endblock %}

(Optional|Deprecated) Overriding the default relevancy check function

Deprecated: Make this check in your controller directly. This will be remove in version 5.x

By default, batch actions are not executed if no object was selected, and the user is notified of this lack of selection. If your custom batch action needs more complex logic to determine if an action can be performed or not, define a batchAction<MyAction>IsRelevant method (e.g. batchActionMergeIsRelevant) in your CRUDController class. This check is performed before the user is asked for confirmation, to make sure there is actually something to confirm.

This method may return three different values:

  • true: The batch action is relevant and can be applied.
  • false: Same as above, with the default "action aborted, no model selected" notification message.
  • string: The batch action is not relevant given the current request parameters (for example the target is missing for a merge action). The returned string is a message displayed to the user:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    // src/Controller/CRUDController.php
    
    namespace App\Controller;
    
    use Sonata\AdminBundle\Controller\CRUDController as BaseController;
    use Symfony\Component\HttpFoundation\Request;
    
    class CRUDController extends BaseController
    {
        public function batchActionMergeIsRelevant(array $selectedIds, $allEntitiesSelected, Request $request)
        {
            // here you have access to all POST parameters, if you use some custom ones
            // POST parameters are kept even after the confirmation page.
            $parameterBag = $request->request;
    
            // check that a target has been chosen
            if (!$parameterBag->has('targetId')) {
                return 'flash_batch_merge_no_target';
            }
    
            $targetId = $parameterBag->get('targetId');
    
            // if all entities are selected, a merge can be done
            if ($allEntitiesSelected) {
                return true;
            }
    
            // filter out the target from the selected models
            $selectedIds = array_filter($selectedIds,
                function($selectedId) use($targetId){
                    return $selectedId !== $targetId;
                }
            );
    
            // if at least one but not the target model is selected, a merge can be done.
            return count($selectedIds) > 0;
        }
    }

(Optional) Executing a pre batch hook

In your admin class you can create a preBatchAction method to execute something before doing the batch action. The main purpose of this method is to alter the query or the list of selected IDs:

1
2
3
4
5
6
7
8
9
10
public function preBatchAction($actionName, ProxyQueryInterface $query, array &$idx, bool $allElements): void
{
    // altering the query or the idx array
    $foo = $query->getParameter('foo')->getValue();

    // Doing something with the foo object
    // ...

    $query->setParameter('foo', $bar);
}
This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
TOC
    Version
    Online exam, become Symfony certified today

    Online exam, become Symfony certified today

    Make sure your project is risk free

    Make sure your project is risk free

    Version:

    Table of Contents

    • Defining new actions
    • Define the core action logic
    • (Deprecated) Define the core action logic
    • (Optional) Overriding the batch selection template
    • (Optional|Deprecated) Overriding the default relevancy check function
    • (Optional) Executing a pre batch hook

    Symfony footer

    Avatar of Peter Gribanov, a Symfony contributor

    Thanks Peter Gribanov for being a Symfony contributor

    1 commit • 2 lines changed

    View all contributors that help us make Symfony

    Become a Symfony contributor

    Be an active part of the community and contribute ideas, code and bug fixes. Both experts and newcomers are welcome.

    Learn how to contribute

    Symfony™ is a trademark of Symfony SAS. All rights reserved.

    • What is Symfony?

      • What is Symfony?
      • Symfony at a Glance
      • Symfony Components
      • Symfony Releases
      • Security Policy
      • Logo & Screenshots
      • Trademark & Licenses
      • symfony1 Legacy
    • Learn Symfony

      • Symfony Docs
      • Symfony Book
      • Reference
      • Bundles
      • Best Practices
      • Training
      • eLearning Platform
      • Certification
    • Screencasts

      • Learn Symfony
      • Learn PHP
      • Learn JavaScript
      • Learn Drupal
      • Learn RESTful APIs
    • Community

      • Symfony Community
      • SymfonyConnect
      • Events & Meetups
      • Projects using Symfony
      • Contributors
      • Symfony Jobs
      • Backers
      • Code of Conduct
      • Downloads Stats
      • Support
    • Blog

      • All Blog Posts
      • A Week of Symfony
      • Case Studies
      • Cloud
      • Community
      • Conferences
      • Diversity
      • Living on the edge
      • Releases
      • Security Advisories
      • Symfony Insight
      • Twig
      • SensioLabs Blog
    • Services

      • SensioLabs services
      • Train developers
      • Manage your project quality
      • Improve your project performance
      • Host Symfony projects

      Powered by

    Follow Symfony