7. Advanced configuration
7.1. 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
):
7.1.1. With a tag attribute (less verbose)
# 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'
7.1.2. With a method call (more verbose)
# 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:
# 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
7.2. Creating a custom RouteBuilder
To create your own RouteBuilder create the PHP class and register it as a service:
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');
}
}
# config/services.yaml
services:
app.admin.entity_route_builder:
class: App\Route\EntityRouterBuilder
arguments:
- '@sonata.admin.audit.manager'
7.3. 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
:
# 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:
// 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');
}
}
7.6. 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.
{# templates/standard_layout.html.twig #}
{% block html_attributes %}class="no-js no-stretch"{% endblock %}
7.7. 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:
// src/Admin/CustomAdmin.php
final class CustomAdmin extends AbstractAdmin
{
protected function getAccessMapping(): array
{
return [
'myCustomFoo' => 'EDIT',
'myCustomBar' => ['EDIT', 'LIST'],
];
}
}
// 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:
// src/Security/Handler/CustomSecurityHandler.php
use Sonata\AdminBundle\Security\Handler\SecurityHandlerInterface;
final class CustomSecurityHandler implements 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:
# 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:
// src/Security/Handler/CustomSecurityHandler.php
use Sonata\AdminBundle\Security\Handler\SecurityHandlerInterface;
final class CustomSecurityHandler implements 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);
}
// ...
}
# 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:
# 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.
7.8. 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:
# config/packages/sonata_admin.yaml
sonata_admin:
default_controller: App\Controller\DefaultCRUDController