Advanced configuration

Edit this page

Advanced configuration

Service Configuration

When you create a new Admin service you can configure its dependencies, the services which are injected by default are:

Dependencies Service ID
model_manager sonata.admin.manager.%manager-type%
data_source sonata.admin.data_source.%manager-type%
form_contractor sonata.admin.builder.%manager-type%_form
show_builder sonata.admin.builder.%manager-type%_show
list_builder sonata.admin.builder.%manager-type%_list
datagrid_builder sonata.admin.builder.%manager-type%_datagrid
translator translator
configuration_pool sonata.admin.pool
router router
validator validator
security_handler sonata.admin.security.handler
menu_factory knp_menu.factory
route_builder sonata.admin.route.path_info | sonata.admin.route.path_info_slashes
label_translator_strategy sonata.admin.label.strategy.form_component

Note

%manager-type% is to be replaced by the manager type (orm, doctrine_mongodb...), and the default route_builder depends on it.

You have 2 ways of defining the dependencies inside your services config file (services.xml or services.yaml):

With a tag attribute (less verbose)

  • YAML
  • XML
1
2
3
4
5
6
7
8
9
10
11
12
13
# config/services.yaml

app.admin.project:
    class: App\Admin\ProjectAdmin
    tags:
        -
            name: sonata.admin
            model_class: App\Entity\Project
            manager_type: orm
            group: 'Project'
            label: 'Project'
            label_translator_strategy: 'sonata.admin.label.strategy.native'
            route_builder: 'sonata.admin.route.path_info'

With a method call (more verbose)

  • YAML
  • XML
1
2
3
4
5
6
7
8
9
# config/services.yaml

app.admin.project:
    class: App\Admin\ProjectAdmin
    calls:
        - [setLabelTranslatorStrategy, ['@sonata.admin.label.strategy.native']]
        - [setRouteBuilder, ['@sonata.admin.route.path_info']]
    tags:
        - { name: sonata.admin, model_class: App\Entity\Project, manager_type: orm, group: 'Project', label: 'Project' }

If you want to modify the service that is going to be injected, add the following code to your application's config file:

  • YAML
1
2
3
4
5
6
7
# config/packages/sonata_admin.yaml

admins:
    sonata_admin:
        sonata.order.admin.order:   # id of the admin service this setting is for
            model_manager:          # dependency name, from the table above
                sonata.order.admin.order.manager  # customised service id

Creating a custom RouteBuilder

To create your own RouteBuilder create the PHP class and register it as a service:

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
namespace App\Route;

use Sonata\AdminBundle\Builder\RouteBuilderInterface;
use Sonata\AdminBundle\Admin\AdminInterface;
use Sonata\AdminBundle\Route\PathInfoBuilder;
use Sonata\AdminBundle\Route\RouteCollectionInterface;

final class EntityRouterBuilder implements RouteBuilderInterface
{
    private PathInfoBuilder $pathInfoBuilder;

    public function __construct(PathInfoBuilder $pathInfoBuilder)
    {
        $this->pathInfoBuilder = $pathInfoBuilder;
    }

    public function build(AdminInterface $admin, RouteCollectionInterface $collection)
    {
        $this->pathInfoBuilder->build($admin, $collection);

        $collection->add('yourSubAction');

        // The create button will disappear, delete functionality will be disabled as well
        // No more changes needed!
        $collection->remove('create');
        $collection->remove('delete');
    }
}
  • YAML
  • XML
1
2
3
4
5
6
7
# config/services.yaml

services:
    app.admin.entity_route_builder:
        class: App\Route\EntityRouterBuilder
        arguments:
            - '@sonata.admin.audit.manager'

Inherited classes

You can manage inherited classes by injecting subclasses using the service configuration.

Lets consider a base class named Person and its subclasses Student and Teacher:

  • YAML
  • XML
1
2
3
4
5
6
7
8
9
10
11
12
# config/services.yaml

app.admin.person:
    class: App\Admin\PersonAdmin
    calls:
        -
            - setSubClasses
            -
                student: App\Entity\Student
                teacher: App\Entity\Teacher
    tags:
        - { name: sonata.admin, model_class: App\Entity\Person, manager_type: orm, group: "admin", label: "Person" }

You will need to change the way forms are configured in order to take into account these new subclasses:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/Admin/PersonAdmin.php

protected function configureFormFields(FormMapper $form): void
{
    $subject = $this->getSubject();

    $form
        ->add('name')
    ;

    if ($subject instanceof Teacher) {
        $form->add('course', 'text');
    }
    elseif ($subject instanceof Student) {
        $form->add('year', 'integer');
    }
}

Tab Menu

ACL

Though the route linked by a menu may be protected the Tab Menu will not automatically check the ACl for you. The link will still appear unless you manually check it using the hasAccess() method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected function configureTabMenu(MenuItemInterface $menu, string $action, ?AdminInterface $childAdmin = null): void
{
    // Link will always appear even if it is protected by ACL
    $menu->addChild($this->trans('Show'), [
        'uri' => $admin->generateUrl('show', [$admin->getIdParameter() => $id])
    ]);

    // Link will only appear if access to ACL protected URL is granted
    if ($this->hasAccess('edit')) {
        $menu->addChild($this->trans('Edit'), [
            'uri' => $admin->generateUrl('edit', [$admin->getIdParameter() => $id])
        ]);
    }
}

You can use dropdowns inside the Tab Menu by default. This can be achieved by using the `'dropdown' => true` attribute:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/Admin/PersonAdmin.php

protected function configureTabMenu(MenuItemInterface $menu, string $action, ?AdminInterface $childAdmin = null): void
{
    // other tab menu stuff ...

    $menu->addChild('comments', ['attributes' => ['dropdown' => true]]);

    $menu['comments']->addChild('list', [
        'uri' => $admin->generateUrl('listComment', [$admin->getIdParameter() => $id])
    ]);
    $menu['comments']->addChild('create', [
        'uri' => $admin->generateUrl('addComment', [$admin->getIdParameter() => $id])
    ]);
}

If you want to use the Tab Menu in a different way, you can replace the Menu Template:

  • YAML
1
2
3
4
5
# config/packages/sonata_admin.yaml

sonata_admin:
    templates:
        tab_menu_template:  "@App/Admin/own_tab_menu_template.html.twig"

Translations

The label translation parameters and domain can be customised by using the label_translation_parameters and translation_domain keys of the extra array of data associated with the item, respectively:

1
2
3
4
$menuItem->setExtras([
    'label_translation_parameters' => ['myparam' => 'myvalue'],
    'translation_domain' => 'My domain',
]);

You can also set the translation domain on the menu root, and children will inherit it:

1
$menu->setExtra('translation_domain', 'My domain');

Filter parameters

You can add or override filter parameters to the Tab Menu:

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
use Knp\Menu\ItemInterface as MenuItemInterface;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Admin\AdminInterface;
use Sonata\Form\Type\EqualType;

final class DeliveryAdmin extends AbstractAdmin
{
    protected function configureTabMenu(MenuItemInterface $menu, string $action, ?AdminInterface $childAdmin = null): void
    {
        if (!$childAdmin && !in_array($action, ['edit', 'show', 'list'])) {
            return;
        }

        if ($action == 'list') {
            // Get current filter parameters
            $filterParameters = $this->getFilterParameters();

            // Add or override filter parameters
            $filterParameters['status'] = [
                'type'  => EqualType::TYPE_IS_EQUAL, // => 1
                'value' => Delivery::STATUS_OPEN,
            ];

            // Add filters to uri of tab
            $menu->addChild('List open deliveries', [
                'uri' => $this->generateUrl('list', ['filter' => $filterParameters])
            ]);

            return;
        }
    }
}

Actions Menu

You can add custom items to the actions menu for a specific action by overriding the following method:

1
2
3
4
5
6
7
8
9
10
11
12
13
public function configureActionButtons(AdminInterface $admin, array $list, string $action, ?object $object = null): array
{
    if (in_array($action, ['show', 'edit', 'acl']) && $object) {
        $buttonList['custom'] = [
            'template' => '@App/Button/custom_button.html.twig',
        ];
    }

    // Remove history action
    unset($buttonList['history']);

    return $buttonList;
}

Your custom twig file

1
2
3
4
5
6
7
8
{# @App/Button/custom_button.html.twig #}

<li>
    <a href="{{ admin.generateObjectUrl('custom', object) }}">
        <i class="fa fa-cogs" aria-hidden="true"></i>
        Custom
    </a>
</li>
Custom action buttons

Disable content stretching

You can disable html, body and sidebar elements stretching. These containers are forced to be full height by default. If you use a custom layout or don't need such behavior, add the no-stretch class to the <html> tag.

1
2
3
{# templates/standard_layout.html.twig #}

{% block html_attributes %}class="no-js no-stretch"{% endblock %}

Custom Action Access Management

You can customize the access system inside the CRUDController by override `getAccessMapping` method in your Admin class and return array with additional entries:

1
2
3
4
5
6
7
8
9
10
11
12
// src/Admin/CustomAdmin.php

final class CustomAdmin extends AbstractAdmin
{
    protected function getAccessMapping(): array
    {
        return [
            'myCustomFoo' => 'EDIT',
            'myCustomBar' => ['EDIT', 'LIST'],
        ];
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/Controller/CustomCRUDController.php

final class CustomCRUDController extends CRUDController
{
    public function myCustomFooAction(): Response
    {
        $this->admin->checkAccess('myCustomFoo');
        // If you can't access to EDIT role for the linked admin, an AccessDeniedException will be thrown

        // ...
    }

    public function myCustomBarAction($object): Response
    {
        $this->admin->checkAccess('myCustomBar', $object);
        // If you can't access to EDIT AND LIST roles for the linked admin, an AccessDeniedException will be thrown

        // ...
    }
}

You can also fully customize how you want to handle your access management by creating custom SecurityHandler service for specific Admin class:

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
// src/Security/Handler/CustomSecurityHandler.php

use Sonata\AdminBundle\Security\Handler\SecurityHandlerInterface;

final class CustomSecurityHandler extends SecurityHandlerInterface
{
    public function isGranted(AdminInterface $admin, $attributes, ?object $object = null): bool
    {
        return $this->customAccessLogic();
    }

    public function getBaseRole(AdminInterface $admin): string
    {
        return '';
    }

    public function buildSecurityInformation(AdminInterface $admin): array
    {
        return [];
    }

    public function createObjectSecurity(AdminInterface $admin, object $object): void
    {
    }

    public function deleteObjectSecurity(AdminInterface $admin, object $object): void
    {
    }
}

Use `security_handler` tag to point to your custom SecurityHandler service for specific Admin class:

  • YAML
1
2
3
4
5
6
7
8
# config/services.yaml

services:
    # ...
    admin.custom:
        class: App\Admin\CustomAdmin
        tags:
            - { name: sonata.admin, model_class: App\Entity\Custom, manager_type: orm, label: Category, security_handler: App\Security\Handler\CustomSecurityHandler }

You can also use the default SecurityHandler (defined in global configuration) in your custom SecurityHandler:

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
// src/Security/Handler/CustomSecurityHandler.php

use Sonata\AdminBundle\Security\Handler\SecurityHandlerInterface;

final class CustomSecurityHandler extends SecurityHandlerInterface
{
    private SecurityHandlerInterface $defaultSecurityHandler;

    public function __construct(SecurityHandlerInterface $defaultSecurityHandler)
    {
        $this->defaultSecurityHandler = $defaultSecurityHandler;
    }

    public function isGranted(AdminInterface $admin, $attributes, ?object $object = null): bool
    {
        // Custom access logic
        if (...) {
            return false;
        }

        // Default access logic
        return $this->defaultSecurityHandler->isGranted($admin, $attributes, $object);
    }

    // ...
}
  • YAML
1
2
3
4
5
6
7
# config/services.yaml

services:
    # ...
    App\Security\Handler\CustomSecurityHandler:
        arguments:
            - '@sonata.admin.security.handler'

If you have a lot of SecurityHandler services that use the default SecurityHandler service, you can define a service alias:

  • YAML
1
2
3
4
5
# config/services.yaml

services:
    # ...
    Sonata\AdminBundle\Security\Handler\SecurityHandlerInterface: '@sonata.admin.security.handler'

This way, you do not need to define each custom SecurityHandler service to specify the default SecurityHandler service as an argument.

Use your own custom controller as default

By default, CRUDController is the controller used when no controller has been specified. You can modify this by adding the following in the configuration:

  • YAML
1
2
3
4
# config/packages/sonata_admin.yaml

sonata_admin:
    default_controller: App\Controller\DefaultCRUDController
This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.