Creating a Custom Admin Action
This is a full working example of creating a custom list action for SonataAdmin.
The example is based on an existing CarAdmin
class in a App
namespace.
It is assumed you already have an admin service up and running.
The recipe
SonataAdmin provides a very straight-forward way of adding your own custom actions.
To do this we need to:
- extend the
SonataAdmin:CRUD
Controller and tell our admin class to use it - create the custom action in our Controller
- create a template to show the action in the list view
- add the route and the new action in the Admin class
Extending the Admin Controller
First you need to create your own Controller extending the one from SonataAdmin:
1 2 3 4 5 6 7 8 9 10
// src/Controller/CarAdminController.php
namespace App\Controller;
use Sonata\AdminBundle\Controller\CRUDController;
class CarAdminController extends CRUDController
{
// ...
}
Admin classes by default use the SonataAdmin:CRUD
controller, this is the third parameter
of an admin service definition, you need to change it to your own.
Register the Admin as a Service
Either by using XML:
1 2 3 4 5
<!-- config/services.xml -->
<service id="app.admin.car" class="App\Admin\CarAdmin">
<tag name="sonata.admin" model_class="App\Entity\Car" controller="App\Controller\CarAdminController" manager_type="orm" group="Demo" label="Car"/>
</service>
or by adding it to your services.yaml
:
1 2 3 4 5 6 7
# config/services.yaml
services:
app.admin.car:
class: App\Admin\CarAdmin
tags:
- { name: sonata.admin, model_class: App\Entity\Car, controller: App\Controller\CarAdminController, manager_type: orm, group: Demo, label: Car }
For more information about service configuration please refer to Step 3 of Creating an Admin
Create the custom action in your Controller
Now it is time to actually create your custom action here, for this example I chose
to implement a clone
action:
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
// src/Controller/CarAdminController.php
namespace App\Controller;
use Sonata\AdminBundle\Controller\CRUDController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class CarAdminController extends CRUDController
{
/**
* @param $id
*/
public function cloneAction($id): Response
{
$object = $this->admin->getSubject();
if (!$object) {
throw new NotFoundHttpException(sprintf('unable to find the object with id: %s', $id));
}
// Be careful, you may need to overload the __clone method of your object
// to set its id to null !
$clonedObject = clone $object;
$clonedObject->setName($object->getName().' (Clone)');
$this->admin->create($clonedObject);
$this->addFlash('sonata_flash_success', 'Cloned successfully');
return new RedirectResponse($this->admin->generateUrl('list'));
}
}
If you want to add the current filter parameters to the redirect url you can add them to the generateUrl()
method:
1 2 3
return new RedirectResponse(
$this->admin->generateUrl('list', ['filter' => $this->admin->getFilterParameters()])
);
Here we first get the object, see if it exists then clone it and insert the clone as a new object. Finally we set a flash message indicating success and redirect to the list view.
Tip
If you want to render something here you can create new template anywhere, extend sonata layout
and use sonata_admin_content
block.
1 2 3 4 5
{% extends '@SonataAdmin/standard_layout.html.twig' %}
{% block sonata_admin_content %}
Your content here
{% endblock %}
Create a template for the new action
You need to tell SonataAdmin how to render your new action. You do that by
creating a list__action_clone.html.twig
in the namespace of your custom
Admin Controller.
1 2 3
{# templates/CRUD/list__action_clone.html.twig #}
<a class="btn btn-sm" href="{{ admin.generateObjectUrl('clone', object) }}">clone</a>
Right now clone
is not a known route, we define it in the next step.
Bringing it all together
What is left now is actually adding your custom action to the admin class.
You have to add the new route in configureRoutes
:
1 2 3 4 5 6 7
use Sonata\AdminBundle\Route\RouteCollectionInterface;
protected function configureRoutes(RouteCollectionInterface $collection): void
{
$collection
->add('clone', $this->getRouterIdParameter().'/clone');
}
This gives us a route like ../admin/app/car/1/clone
.
You could also write $collection->add('clone');
to get a route like ../admin/app/car/clone?id=1
Next we have to add the action in configureListFields
specifying the template we created:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
protected function configureListFields(ListMapper $list): void
{
$list
->add(ListMapper::NAME_ACTIONS, null, [
'actions' => [
// ...
'clone' => [
'template' => '@App/CRUD/list__action_clone.html.twig',
],
],
]);
}
The full CarAdmin.php
example looks like this:
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
// src/Admin/CarAdmin.php
namespace App\Admin;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Route\RouteCollection;
final class CarAdmin extends AbstractAdmin
{
protected function configureRoutes(RouteCollectionInterface $collection): void
{
$collection
->add('clone', $this->getRouterIdParameter().'/clone');
}
protected function configureListFields(ListMapper $list): void
{
$list
->addIdentifier('name')
->add('engine')
->add('rescueEngine')
->add('createdAt')
->add(ListMapper::NAME_ACTIONS, null, [
'actions' => [
'show' => [],
'edit' => [],
'delete' => [],
'clone' => [
'template' => '@App/CRUD/list__action_clone.html.twig',
]
]
]);
}
}
Note
If you want to render a custom controller action in a template by using the
render function in twig you need to add _sonata_admin
as an attribute. For
example; {{ render(controller('App
. This has to be done because the
moment the rendering should happen the routing, which usually sets the value of
this parameter, is not involved at all, and then you will get an error "There is
no _sonata_admin defined for the controller
AppControllerXxxxCRUDController and the current route ' '."
Custom Action without Entity
Creating an action that is not connected to an Entity is also possible. Let's imagine we have an import action. We register our route:
1 2 3 4 5 6
use Sonata\AdminBundle\Route\RouteCollectionInterface;
protected function configureRoutes(RouteCollectionInterface $collection): void
{
$collection->add('import');
}
and the controller action:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// src/Controller/CarAdminController.php
namespace App\Controller;
use Sonata\AdminBundle\Controller\CRUDController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
final class CarAdminController extends CRUDController
{
public function importAction(Request $request): Response
{
// do your import logic
}
Now, instead of adding the action to the form mapper, we can add it next to
the add button. In your admin class, overwrite the configureActionButtons
method:
1 2 3 4 5 6
protected function configureActionButtons(array $buttonList, string $action, ?object $object = null): array
{
$buttonList['import'] = ['template' => 'import_button.html.twig'];
return $buttonList;
}
Create a template for that button:
1 2 3 4 5
<li>
<a class="sonata-action-element" href="{{ admin.generateUrl('import') }}">
<i class="fas fa-level-up-alt"></i> {{ 'import_action'|trans({}, 'SonataAdminBundle') }}
</a>
</li>
You can also add this action to your dashboard actions, you have to overwrite
the getDashboardActions
method in your admin class and there are two
ways you can add action:
1 2 3 4 5 6
protected function configureDashboardActions(array $actions): array
{
$actions['import'] = ['template' => 'import_dashboard_button.html.twig'];
return $actions;
}
Create a template for that button:
1 2 3
<a class="btn btn-link btn-flat" href="{{ admin.generateUrl('import') }}">
<i class="fas fa-level-up-alt"></i> {{ 'import_action'|trans({}, 'SonataAdminBundle') }}
</a>
Or you can pass values as array:
1 2 3 4 5 6 7 8 9 10 11
protected function configureDashboardActions(array $actions): array
{
$actions['import'] = [
'label' => 'import_action',
'translation_domain' => 'SonataAdminBundle',
'url' => $this->generateUrl('import'),
'icon' => 'level-up-alt',
];
return $actions;
}