Symfony / Sonata: Añadir una función de clonación en un CRUD

Para agregar una función de duplicación de objetos a una lista CRUD, necesitas modificar la lista añadiendo el botón, configurar una ruta, ejecutar el código de duplicación de objeto y, finalmente, referenciar el controlador que alojará nuestra función en nuestra interfaz, a través de su servicio.

Añadiendo el botón:
En tu controlador, añade el botón haciendo referencia a la plantilla del botón.
Aquí establecemos la ruta 'Admin/list__action_clone.html.twig'. El sistema buscará el archivo en: /templates/Admin/list__action_clone.html.twig

   protected function configureListFields(ListMapper $listMapper)
    {

        $listMapper->addIdentifier('id', null, ['label' => 'id']);
        $listMapper->addIdentifier('label', null, ['label' => 'type']);  
        $listMapper->addIdentifier('description', null, ['label' => 'Label']);
        $listMapper->add('actif', null, ['editable' => true]);
        $listMapper->add('_action', null, [
				'actions' => [
					'clone' => [
						'template' => 'Admin/list__action_clone.html.twig'
					]
				]
			]);
        
    }

El contenido de nuestro archivo twig es muy simple. Es solo la visualización de un ícono.

<a  class="btn btn-sm btn-default" 
    href="{{ admin.generateObjectUrl('clone', object) }}" 
    title="{{ 'Clone'|trans({}, 'default') }}" 
    alt="{{ 'Clone'|trans({}, 'default') }}">
	<i class="fa fa-clone"></i>
</a>

El siguiente paso es crear la ruta en nuestro controlador:

    protected function configureRoutes(RouteCollection $collection)
    {
        $collection->add('clone', $this->getRouterIdParameter().'/clone');
    }

Y el último paso, clonar nuestro objeto junto con todas sus configuraciones. Para hacer esto, crearemos un controlador específicamente dedicado a administrar nuestras funciones adicionales y lo añadiremos a nuestro servicio para que se vuelva disponible.

En App/Admin vamos a crear un archivo llamado CustomAction.php que contendrá nuestra función cloneAction().
Esta debe extenderse de CRUDController y no de AbstractAdmin como con nuestras interfaces. Es por eso que ponemos nuestra función en un archivo nuevo. Pero también nos permite reutilizar esta función si es necesario.


En src/Admin/CustomAction.php:

<?php
namespace App\Admin;

use Sonata\AdminBundle\Controller\CRUDController;
use Symfony\Component\HttpFoundation\RedirectResponse;

class CustomAction extends CRUDController
{
    
    
    /**
     * @param $id
     */
    public function cloneAction($id)
    {
        $object = $this->admin->getObject($id);
        $objectConf=$object->getWtypeWconf()->toArray();
    
    
        if (!$object) {
            throw new NotFoundHttpException(sprintf('unable to find the object with id: %s', $id));
        }
    
        $clonedObject = clone $object;
        $clonedObject->unsetId();
        $clonedObject->setLabel($object->getLabel().' clone('.uniqid().')');
        $this->admin->create($clonedObject);
        if(!empty($objectConf)){
            foreach($objectConf as $c){
                $clonedConf=clone $c;
                $clonedConf->setWconfTypcont($clonedObject);
                $this->admin->create($clonedConf);
            }
        }
        $this->addFlash('sonata_flash_success', 'L\'élément a correctement été dupliqué.');
        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', ['filter' => $this->admin->getFilterParameters()]));
    }
}

Como puedes ver, nuestra función de clonación es responsable de clonar nuestro objeto así como todas sus relaciones OneToMany.
La línea $clonedConf->setWconfTypcont($clonedObject); se usa para poner el id de la relación padre en el campo de la relación hijo. De lo contrario, todos los objetos mantendrían sus relaciones con el objeto inicial.

Último paso, necesitamos referenciar nuestro nuevo controlador en el servicio de nuestra interfaz.

Antes:

admin.wtype:
    class: App\Admin\WtypeAdmin
    arguments: [~, App\Entity\Wtype, ~]
    calls:
        - [addChild, ["@admin.wconf"]] 
    tags:
        - { name: sonata.admin, manager_type: orm, label: "Types de contrats" }
    public: true

Después:

    admin.wtype:
        class: App\Admin\WtypeAdmin
        arguments: [~, App\Entity\Wtype, App\Admin\CustomAction]
        calls:
            - [addChild, ["@admin.wconf"]] 
        tags:
            - { name: sonata.admin, manager_type: orm, label: "Types de contrats" }
        public: true