SonataDoctrineORMAdminBundle
repository·4.x·Indexed 19 days ago
https://github.com/sonata-project/sonatadoctrineormadminbundleAn integration layer between Doctrine ORM and SonataAdminBundle that allows developers to manage Doctrine entities through a web-based administration interface. It enables the creation of automated CRUD interfaces for database-backed entities and provides features for auditing via EntityAuditBundle, custom datagrid filters, and template overrides for list and show views.
What's inside SonataDoctrineORMAdminBundle
- SonataDoctrineORMAdminBundle is a bridge that integrates Doctrine ORM into the SonataAdminBundle. It allows developers to use Doctrine entities as the data source for Sonata's administration interface, enabling the creation of automated CRUD (Create, Read, Update, Delete) interfaces for database-backed entities.
Configure child associations in an Admin class
4.xWhen an Admin class manages a child entity (an entity that belongs to a parent), you can use the
$parentAssociationMappingproperty to define the relationship.In the
configureFormFieldsmethod, you can check$this->isChild()to determine if the current context is a child view. This allows you to conditionally add fields, such as a selector for the parent entity, only when creating a new child record rather than editing an existing one.final class CommentAdmin extends AbstractAdmin { protected $parentAssociationMapping = 'post'; protected function configureFormFields(FormMapper $formMapper) { if (!$this->isChild()) { $formMapper->add('post', ModelType::class, [], ['edit' => 'list']); } $formMapper ->add('name') ->add('message'); } }Configure One-to-Many relation fields (Collections)
4.xTo manage a
One-to-Manyrelationship (e.g., aGallerycontaining multipleMediaitems), use theSonata\Form\Type\CollectionType.You can customize the collection behavior using these options in the field definition:
edit: Set toinlineorstandard.inlinemode allows adding new rows directly.inline: Set totableorstandard.tabledisplays fields in a table format.sortable: Set to the name of the field (e.g.,'position') to enable drag-and-drop sorting.limit: An integer that limits the number of elements that can be added. Once the limit is reached, the "Add new" button is hidden.
Important: You must implement a
setMediasmethod in your model and ensure cascading persistence is configured for the relationship.namespace Sonata\MediaBundle\Admin; use Sonata\AdminBundle\Admin\AbstractAdmin; use Sonata\AdminBundle\Form\FormMapper; use Sonata\Form\Type\CollectionType; final class GalleryAdmin extends AbstractAdmin { protected function configureFormFields(FormMapper $form): void { $form ->add('code') ->add('enabled') ->add('name') ->add('defaultFormat') ->add('galleryHasMedias', CollectionType::class, [ 'by_reference' => false, ], [ 'edit' => 'inline', 'inline' => 'table', 'sortable' => 'position', 'limit' => 3, ]); } }Filter by sub-entity properties using dot notation
4.xYou can filter base entities by the properties of related entities using dot-separated notation (e.g.,
relation.property).Note: This only works when the path consists of entities. It does not work when traversing collections.
protected function configureDatagridFilters(DatagridMapper $filter): void { $filter ->add('address.street') ->add('address.ZIPCode') ->add('address.town'); }How auditing works with EntityAuditBundle
4.xAuditing works by creating a mirroring table for every audited entity table, suffixed with
_audit. These mirror tables contain all columns from the original entity plus two metadata fields:rev: The global revision number from a centralrevisionstable.revtype: The type of operation that triggered the log entry ('INS'for insert,'UPD'for update, or'DEL'for delete).
The global revision table tracks the
id,timestamp,username, and achange comment. This mechanism allows you to version your application and view associations at specific points in time. The extension automatically hooks into the DoctrineSchemaToolgeneration process to create the necessary DDL statements for these audit tables.Understand the required routes for an Admin class
4.xWhen creating an Admin class for a Doctrine entity in SonataAdmin, the bundle expects 6 specific routes to be defined to handle the standard CRUD lifecycle. These routes are:
list: Displays the list of entities.create: The form for creating a new entity.batch: Handles batch operations on multiple entities.update: The logic for updating an existing entity.edit: The form for editing an existing entity.delete: The action for deleting an entity.
Note: In standard configurations, the route information is automatically generated for you by the bundle, allowing you to proceed without manual routing configuration.
Use ProxyQuery to extend Doctrine Query functionality
4.xThe
ProxyQueryobject wraps a standard DoctrineQueryBuilderto provide additional features and convenience methods required by Sonata Admin.Key enhancements include:
- Direct Execution: Use the
execute()method directly instead of callinggetQuery()->getResult(). - Sorting: Provides simplified
setSortBy()and sort order options. - Optimized Joins: Automatically handles preselecting IDs on left join queries so that
setMaxResults()(limit) is applied to the primary entity rather than the entire result set, simulating Doctrine 1 behavior. - Duplicate Prevention: By default, Sonata uses the
DISTINCTSQL keyword when fetching identifiers for listings to prevent duplicates caused by joins. If you are certain no duplicates will occur and want to improve performance, you can disable this usingsetDistinct(false).
use Sonata\AdminBundle\Datagrid\ORM\ProxyQuery; $queryBuilder = $this->em->createQueryBuilder(); $queryBuilder->from('Post', 'p'); $proxyQuery = new ProxyQuery($queryBuilder); $proxyQuery->leftJoin('p.tags', 't'); $proxyQuery->setSortBy('name'); $proxyQuery->setMaxResults(10); $results = $proxyQuery->execute();- Direct Execution: Use the
Handle object-based IDs (e.g., UUIDs) in SonataAdmin
4.xIf your entity uses an object for its ID (such as a
Ramsey\\Uuid\" object) instead of a primitive type, the bundle will automatically resolve the string representation for URLs and rendering by calling$entity->getId()->__toString(). To support this, ensure your ID object implements the__toString()` method.use Doctrine oDB\\Types\\ use Doctrine\ORM\Mapping as ORM; use Ramsey\Uuid\Doctrine\UuidOrderedTimeGenerator; use Ramsey\Uuid\UuidInterface; class Comment { #[ORM\Id] #[ORM\Column(type: Types::INTEGER)] #[ORM\GeneratedValue(strategy: 'CUSTOM')] #[ORM\CustomIdGenerator(class: UuidOrderedTimeGenerator::class)] private ?UuidInterface $id = null; // ... }Configure Many-to-One relation fields
4.xWhen a model has a
Many-to-Onerelationship (e.g., manyPostentities linked to oneUser), you can choose between two primary Sonata types to handle the relation:Sonata\AdminBundle\Form\Type\ModelType: Displays the list in a select widget with anAddbutton to create a new related object.Sonata\AdminBundle\Form\Type\ModelListType: Displays the list in a searchable model where you can select and delete related objects.
ModelListType Options:
btn_add: Custom label for the add button.btn_list: Translation key for the list button.btn_delete: Set tofalseto hide the delete button.btn_edit: Custom label for the edit button (shows when a value is set).btn_catalogue: Custom translation domain for the buttons.
namespace Sonata\NewsBundle\Admin; use Sonata\AdminBundle\Admin\AbstractAdmin; use Sonata\AdminBundle\Form\FormMapper; use Sonata\AdminBundle\Form\Type\ModelListType; use Sonata\AdminBundle\Form\Type\ModelType; final class PostAdmin extends AbstractAdmin { protected function configureFormFields(FormMapper $form): void { $form ->with('General') ->add('enabled', null, ['required' => false]) ->add('author', ModelListType::class, [ 'btn_add' => 'Add author', 'btn_list' => 'button.list', 'btn_delete' => false, 'btn_edit' => 'Edit', 'btn_catalogue' => 'SonataNewsBundle', ], [ 'placeholder' => 'No author selected', ]) ->add('title') ->add('abstract') ->add('content') ->end() ->with('Tags') ->add('tags', ModelType::class, ['expanded' => true]) ->end() ->with('Options', ['collapsed' => true]) ->add('commentsCloseAt') ->add('commentsEnabled', null, ['required' => false]) ->add('commentsDefaultStatus', 'choice', [ 'choices' => Comment::getStatusList() ]) ->end(); } }Migrate ModelAutocompleteFilter to ModelFilter in version 4.1+
4.xIn version 4.1 and later,
Sonata\DoctrineORMAdminBundle\Filter\ModelAutocompleteFilteris deprecated. To maintain compatibility and follow the new pattern, replace the direct use ofModelAutocompleteFilterwithModelFilterand specify theModelAutocompleteTypevia thefield_typeoption.Old way (Deprecated):
->add('foo', ModelAutocompleteFilter::class)New way:
->add('foo', ModelFilter::class, [ 'field_type' => ModelAutocompleteType::class, ])Define Doctrine entities for SonataAdmin
4.xTheSonataDoctrineORMAdminBundleinteracts directly with your Doctrine entities. You can use any Doctrine metadata driver (Attributes, XML, YAML, etc.) to define your models. When defining entities, ensure you implement__toString()if you want a readable representation in the admin interface, or ensure the ID object implements__toString()if you are using object-based identifiers (like UUIDs).Define a custom CRUD controller
4.xA CRUD controller in Sonata is a class that extends
Sonata\AdminBundle\Controller\CRUDController. By default, you can use an empty class if you only need the standard CRUD actions. However, defining a custom controller allows you to add new actions or overwrite default behavior to suit your application's specific needs.If you do not declare a controller in your Admin configuration, the
AdminBundlewill automatically use the defaultCRUDController.// src/Tutorial/BlogBundle/Controller/CommentAdminController.php namespace Tutorial//BlogBundle/Controller; use Sonata\AdminBundle\Controller\CRUDController; final class CommentAdminController extends CRUDController { // You can add new actions or overwrite default ones here }