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 an ``AppBundle``. 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 .. code-block:: php AppBundle\Entity\Car AppBundle:CRUD or by adding it to your ``admin.yml``: .. code-block:: yaml # src/AppBundle/Resources/config/admin.yml services: app.admin.car: class: AppBundle\Admin\CarAdmin tags: - { name: sonata.admin, manager_type: orm, group: Demo, label: Car } arguments: - null - AppBundle\Entity\Car - AppBundle:CRUD public: true For more information about service configuration please refer to Step 3 of :doc:`../reference/getting_started` 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. .. code-block:: php 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 have a filtered list and want to keep your filters after the redirect // return new RedirectResponse($this->admin->generateUrl('list', $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. If you want to add the current filter parameters to the redirect url you can add them to the `generateUrl` method: .. code-block:: php return new RedirectResponse($this->admin->generateUrl('list', array('filter' => $this->admin->getFilterParameters()))); Using template in new controller ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you want to render something here you can create new template anywhere, extend sonata layout and use `sonata_admin_content` block. .. code-block:: html+jinja {% extends 'SonataAdminBundle::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. .. code-block:: html+jinja {# src/AppBundle/Resources/views/CRUD/list__action_clone.html.twig #} clone 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``: .. code-block:: php // ... use Sonata\AdminBundle\Route\RouteCollection; protected function configureRoutes(RouteCollection $collection) { $collection->add('clone', $this->getRouterIdParameter().'/clone'); } This gives us a route like ``../admin/app/car/1/clone``. You could also just 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. .. code-block:: php protected function configureListFields(ListMapper $listMapper) { $listMapper // other fields... ->add('_action', null, array( 'actions' => array( // ... 'clone' => array( 'template' => 'AppBundle:CRUD:list__action_clone.html.twig' ) ) )) ; } The full ``CarAdmin.php`` example looks like this: .. code-block:: php add('clone', $this->getRouterIdParameter().'/clone'); } protected function configureDatagridFilters(DatagridMapper $datagridMapper) { // ... } protected function configureFormFields(FormMapper $formMapper) { // ... } protected function configureListFields(ListMapper $listMapper) { $listMapper ->addIdentifier('name') ->add('engine') ->add('rescueEngine') ->add('createdAt') ->add('_action', null, array( 'actions' => array( 'show' => array(), 'edit' => array(), 'delete' => array(), 'clone' => array( 'template' => 'AppBundle:CRUD:list__action_clone.html.twig' ) ) )); } protected function configureShowFields(ShowMapper $showMapper) { // ... } } .. 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('AppBundle:XxxxCRUD:comment', {'_sonata_admin': 'sonata.admin.xxxx' })) }}``. 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 AppBundle\Controller\XxxxCRUDController and the current route ' '."