Install Dependencies
composer require aliocza/sortable-ui-bundle
composer require gedmo/doctrine-extensions stof/doctrine-extensions-bundle
Enable Bundles in app/AppKernel.php:
new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),
new Aliocza\SortableUiBundle\AlioczaSortableUiBundle(),
Configure Doctrine Extensions in config.yml:
stof_doctrine_extensions:
orm:
default:
sortable: true
Add Position Field to your entity (e.g., Client):
use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class Client
{
/**
* @Gedmo\SortablePosition
* @ORM\Column(type="integer")
*/
private $position;
}
Register the Sortable Listener in services.yml:
services:
gedmo.listener.sortable:
class: Gedmo\Sortable\SortableListener
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [ setAnnotationReader, [ "@annotation_reader" ] ]
Configure Bundle in config.yml:
aliocza_sortable_ui:
db_driver: orm
position_field:
default: position
entities:
AppBundle\Entity\Client: position
Integrate into Sonata Admin (ClientAdmin.php):
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->add('_action', 'actions', [
'actions' => [
'drag' => [
'template' => 'AlioczaSortableUiBundle:Default:drag.html.twig'
]
]
]);
}
Override Base Template in ClientAdmin.php:
public function configure()
{
$this->setTemplate('list', 'AlioczaSortableUiBundle:CRUD:base_list.html.twig');
}
Add Route in ClientAdmin.php:
protected function configureRoutes(RouteCollection $collection)
{
$collection->add('drag', 'drag');
}
Inject Position Service in services.yml:
app.admin.client:
class: AppBundle\Admin\ClientAdmin
arguments: [...]
calls:
- [ setPositionService, ['@aliocza_sortable_ui.position'] ]
Client will include a drag handle (🖱️ icon) in each row.position column updates).Entity Setup
@Gedmo\SortablePosition to the field you want to sort by (e.g., position, order).integer type.Admin Configuration
configureListFields() to include the drag action:
$listMapper->add('_action', 'actions', ['actions' => ['drag' => [...]]]);
AlioczaSortableUiBundle:CRUD:base_list.html.twig.Routing
drag route in configureRoutes() to handle AJAX updates:
$collection->add('drag', 'drag');
Service Injection
PositionHandler into your admin class to customize sorting logic:
public function setPositionService(PositionHandler $positionHandler) {
$this->positionService = $positionHandler;
}
Customization
drag.html.twig) to modify the drag handle UI.PositionHandler to add business logic (e.g., validation before saving).position_field.entities in config.yml to map different fields per entity.datagridValues in your admin to default-sort by the position field:
protected $datagridValues = [
'_sort_by' => 'position',
'_sort_order' => 'ASC',
];
PositionHandler to update positions in bulk (e.g., after a mass action).preUpdate/postUpdate events to trigger side effects (e.g., recalculating dependent fields).Missing Doctrine Extensions
stof_doctrine_extensions or register the SortableListener will cause silent failures.stof_doctrine_extensions.orm.default.sortable: true and the listener service is defined.Incorrect Position Field Type
integer. Using string or other types will break sorting.@ORM\Column(type="integer")
Route Conflicts
drag route may conflict with existing routes (e.g., sonata_admin routes)._sonata_admin prefix or customize the route path:
$collection->add('drag', '_sonata_admin/drag', ['FOSRest' => true]);
Caching Issues
sonata.cache.clear command:
php bin/console cache:clear
JavaScript Errors
jquery-ui is included in your admin template:
{{ sonata_admin_include_jquery_ui('sortable') }}
Check Database Updates
position field updates in the database after dragging:
SELECT id, position FROM clients ORDER BY position;
Enable Debug Mode
APP_DEBUG=true in .env to see Symfony errors during drag operations.Log AJAX Requests
/drag endpoint.PositionHandler is properly injected and the route is accessible.Validate Entity Changes
preUpdate in your entity to log position changes:
public function preUpdate()
{
\Log::info('Position updated to: ' . $this->position);
}
Custom Position Logic
Aliocza\SortableUiBundle\Services\PositionHandler to add validation or custom save logic:
class CustomPositionHandler extends PositionHandler
{
public function updatePosition($entity, $position)
{
if ($position < 0) {
throw new \RuntimeException('Position cannot be negative');
}
parent::updatePosition($entity, $position);
}
}
services.yml:
services:
aliocza_sortable_ui.position:
class: AppBundle\Services\CustomPositionHandler
Multi-Level Sorting
Sortable fields in your entity (e.g., parent_id + position).Performance Optimization
// In PositionHandler
public function updatePositions(array $updates)
{
$em = $this->getEntityManager();
foreach ($updates as $entity) {
$em->persist($entity);
}
$em->flush(); // Single flush for all updates
}
Alternative Drivers
orm driver. For MongoDB, you’d need to:
mongodb driver logic.PositionHandler to use DoctrineMongoDBBundle instead.How can I help you explore Laravel packages today?