Изменение порядка записей мышкой (drag’n’drop) в Sonata Admin

Очень часто бывает нужно изменять порядок записей из панели администрирования. Конечно, это можно сделать, введя параметр для сортировки, и меняя его, но это не совсем удобно. Сегодня будем делать сортировку записей из админки мышкой.

Перед применением рецепта, изложенного в этой статье, нужно установить и настроить проект на Symfony и Sonata Admin.

В нашем случае, есть админка для работы с каруселью на фронтэнде. Нужно добавить в нее изменение порядка следования баннеров.

Вот то, что мы хотим получить:

Для начала устанавливаем пакеты:

composer require gedmo/doctrine-extensions
composer require pixassociates/sortable-behavior-bundle
composer require stof/doctrine-extensions-bundle

После установки в bundles.php должны появиться две строки:

[
...
Pix\SortableBehaviorBundle\PixSortableBehaviorBundle::class => ['all' => true],
Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle::class => ['all' => true],
]

Включаем сортировку в конфигах:

# config/packages/stof_doctrine_extensions.yaml
stof_doctrine_extensions:
    default_locale: ru_RU
    orm:
        default:
            sortable: true
# config/services.yaml
# Добавлям Gedmo сервис
gedmo.listener.sortable:
    class: Gedmo\Sortable\SortableListener
    calls:
        - [setAnnotationReader, ['@annotation_reader']]
    tags:
        - { name: doctrine.event_subscriber, connection: default }
# Прописываем контроллер «PixSortableBehaviorBundle:SortableAdmin» в классе администрирования
admin.banner:
    class: App\Admin\BannerAdmin
    arguments:
        - ~
        - App\Entity\Banner
        - PixSortableBehaviorBundle:SortableAdmin
    tags:
        - { name: sonata.admin, manager_type: orm, label: Баннеры, group: CMS }

Прописываем порядок следования в классе администрирования:

# src/Admin/BannerAdmin.php
...
use Sonata\AdminBundle\Route\RouteCollection;
...
final class BannerAdmin extends AbstractAdmin
{
    protected $datagridValues = [
        '_page' => 1,
        '_sort_order' => 'ASC',
        '_sort_by' => 'position',
    ];
    protected function configureListFields(ListMapper $listMapper)
    {
        $listMapper
            ...
            ->add('_action'null, [
                'label' => false,
                'actions' => [
                    'move' => [
                        'template' => '@PixSortableBehavior/Default/_sort_drag_drop.html.twig',
                        'enable_top_bottom_buttons' => false,
                    ],
                ]
            ])
        ;
    }
    protected function configureRoutes(RouteCollection $collection)
    {
        $collection->add('move'$this->getRouterIdParameter().'/move/{position}');
    }
}

Изменения в сущности Banner:

# src/Entity/Banner.php
...
use Gedmo\Mapping\Annotation as Gedmo;
...
class Banner
{
    /**
     * @Gedmo\SortablePosition
     * @ORM\Column(type="integer")
     */
    private $position;
...
    public function getPosition(): ?int
    {
        return $this->position;
    }
    public function setPosition(int $position): self
    {
        $this->position = $position;
        return $this;
    }
}

В шаблон страницы админки нужно добавить js бандла «pixsortablebehavior»:

{# templates/admin/layout.html.twig #}
{% block javascripts %}
     {{ parent() }}
     {{ encore_entry_script_tags('admin') }}
+    {{ encore_entry_script_tags('jquery-ui') }}
+    {{ encore_entry_script_tags('sortable') }}
 {% endblock %}
# webpack.config.js
Encore
...
+    .addEntry('jquery-ui''./public/bundles/pixsortablebehavior/js/jquery-ui.min.js')
+    .addEntry('sortable''./public/bundles/pixsortablebehavior/js/init.js')
...

Вот и всё! Теперь у нас есть сортировка. Осталось только на фронтэнде, при получении записей из базы, не забыть отсортировать по полю position.