Escenario
Necesito añadir un enlace a una página personalizada en el menú lateral de Sonata Admin. En concreto uno que, en vez de dirigir a un listado de entidades, abra una página específica. La entidad en cuestión es Acme\Entity\CashOffice. Antes que nada tenemos que crear un CashOfficeAdmin en el cual usaremos el método configureRoutes para añadir la nueva ruta a la acción del controlador que devuelve nuestra página, y suprimir la ruta list:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/** * * @param RouteCollection $collection */ public function configureRoutes(RouteCollection $collection) { $collection ->add('list', 'index', array( '_controller' => 'Acme\Controller\CashOfficeAdminController:index', 'methods' => array('GET')) ); $collection->remove('list'); } |
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 |
<?php // src/Controller/CashOfficeAdminController namespace Acme\Controller; use Acme\Manager\CashOfficeManager; use Sonata\AdminBundle\Controller\CRUDController; use Symfony\Component\HttpFoundation\Response; /** * * @author Marcos */ class CashOfficeAdminController extends CRUDController { public function index(): Response { $cashOfficeManager = $this->admin->getModelManager(); return $this->render('CashOfficeAdmin/index.html.twig', array( 'action' => 'index', 'cashOffice' => $cashOfficeManager->getParkingMainCashOffice() )); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
{% extends base_template %} {%- block actions -%} {% include '@SonataAdmin/CRUD/action_buttons.html.twig' %} {%- endblock -%} {%- block tab_menu -%} {{ knp_menu_render(admin.sidemenu(action), { 'currentClass': 'active', 'template': get_global_template('tab_menu_template') }, 'twig') }} {%- endblock -%} {% block title %} {{ "cashOffice"|trans }} {% endblock %} {% block navbar_title %} {{ block('title') }} {% endblock %} {% block stylesheets %} {{ parent() }} {% endblock stylesheets %} {% block sonata_admin_content %} <h2> {{ "cash_balance"|trans }}: <strong class="text-{{ (cashOffice.getBalance() < 0) ? 'danger' : 'success' }}">{{ cashOffice.getBalance() }} € </strong> </h2> {% endblock sonata_admin_content %} |
1 2 3 4 5 6 7 8 |
acme.admin.cash_office: class: Acme\Admin\CashOfficeAdmin arguments: [~, Acme\Entity\CashOffice, Acme\Controller\CashOfficeAdminController] tags: - { name: sonata.admin, manager_type: orm, label: cash_office, label_catalogue: Acme, label_translator_strategy: sonata.admin.label.strategy.noop } calls: - [ setModelManager, [ '@acme.manager.cash_office' ]] public: true |
1 2 3 4 5 6 7 8 9 10 11 |
dashboard: groups: cash_office: label: cash_office label_catalogue: messages icon: '<i class="fa fa-money"></i>' items: - admin: acme.admin.cash_office route: admin_acme_cashoffice_index label: 'cash_register' roles: [ROLE_SONATA_ADMIN] |
Problema
Después de actualizar a la última versión de Sonata Admin (3.78.1), despareció el menú de CashOffice. Conseguí restaurarlo dando de alta un Listener que capture el evento del menú y modifique el árbol que lo compone. La clase debe contener un método público para construir los elementos:
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 |
namespace Acme\EventListener; use Sonata\AdminBundle\Event\ConfigureMenuEvent; /** * Description of MenuBuilderListener * * @author marcos */ class MenuBuilderListener { /** * @param ConfigureMenuEvent $event */ public function adminMenuItems(ConfigureMenuEvent $event) { $event->getMenu() ->addChild('cash_office') ->setExtras( [ 'roles' => ['ROLE_SONATA_ADMIN'], 'icon' => '<i class="fa fa-money"></i>' ] ) ->addChild( 'cash_register', [ 'route' => 'admin_acme_cashoffice_index', ] ) ->setLabel('cash_register') ->setExtras( [ 'roles' => ['ROLE_SONATA_ADMIN'], ] ); } } |
1 2 3 4 |
acme.event_listener.app.admin_menu: class: Acme\EventListener\MenuBuilderListener tags: - { name: kernel.event_listener, event: sonata.admin.event.configure.menu.sidebar, method: adminMenuItems } |
Solución
La solución del Listener no me convence, a parte de que no queda activo el enlace cuando se encuentra en la página que le corresponde, me digo que tiene que haber una manera más sencilla de conseguirlo. Tras investigar el código fuente de Sonata Admin doy con el motivo por el que ya no se muestra el enlace. En vendor/sonata-project/admin-bundle/src/Menu/Provider/GroupMenuProvider.php hay un método que decide si se puede generar el elemento del menú:
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 |
private function canGenerateMenuItem(array $item, array $group): bool { if (isset($item['admin']) && !empty($item['admin'])) { $admin = $this->pool->getInstance($item['admin']); // skip menu item if no `list` url is available or user doesn't have the LIST access rights return $admin->hasRoute('list') && $admin->hasAccess('list'); } //NEXT_MAJOR: Remove if statement of null checker. if (null === $this->checker) { return true; } // Making the checker behave affirmatively even if it's globally unanimous // Still must be granted unanimously to group and item $isItemGranted = true; if (!empty($item['roles'])) { $isItemGranted = false; foreach ($item['roles'] as $role) { if ($this->checker->isGranted($role)) { $isItemGranted = true; break; } } } $isGroupGranted = true; if (!empty($group['roles'])) { $isGroupGranted = false; foreach ($group['roles'] as $role) { if ($this->checker->isGranted($role)) { $isGroupGranted = true; break; } } } return $isItemGranted && $isGroupGranted; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/** * * @param RouteCollection $collection */ public function configureRoutes(RouteCollection $collection) { $collection->remove('list'); $collection ->add('list', 'index', array( '_controller' => 'Acme\Controller\CashOfficeAdminController:index', 'methods' => array('GET')) ); } |
1 2 3 4 5 6 7 |
cash_office: label: cash_office label_catalogue: messages icon: '<i class="fa fa-money"></i>' items: - acme.admin.cash_office roles: [ROLE_SONATA_ADMIN] |
Nota: Estoy usando la versión de Symfony 4.4 y la 3.78.1 de SonataAdmin.