Административный интерфейс на Symfony 2 и SonataAdminBundle

В базовой поставке Symfony 2 предусмотрен только минимальный функционал создания CRUD интерфейса. Для реализации административного интерфейса разработан ряд бандлов, в частности SonataAdminBundle.

Для чего это нужно?

С помощью SonataAdminBundle можно быстро создать конфигурируемый интерфейс редактирования сущностей ORM-модели (также выделены бандлы для работы с MongoDb и PHPCr, но они пока находятся на раннем этапе развития). При этом любую часть интерфейса можно доработать под себя. В конце октября 2011 оформление было переведено на фреймворк Twitter Boostrap, поэтому внешний вид административного интерфейса получается довольно современным.

Установка и базовая конфигурация

Для установки зависимостей в Symfony 2.1 используется Composer, в отличие от версии 2.0, в которой использовался файл deps. Чтобы загрузить бандл Sonata Admin (в частности Sonata Orm Admin), в файл composer.json нужно добавить

   "require":{
        "php":">=5.3.3",
         ...
        "sonata-project/doctrine-orm-admin-bundle":"dev-master",

И затем запустить

php composer.phar update

В отличие от загрузки зависимостей посредством deps, файл app/autoload.php править не нужно, namespace для загруженных бандлов будут прописаны автоматически. Нужно только добавить инициализацию бандлов в app/AppKernel.php

<?php
// app/AppKernel.php
public function registerBundles()
{
    return array(
        // ...
        new Sonata\AdminBundle\SonataAdminBundle(),
        new Sonata\DoctrineORMAdminBundle\SonataDoctrineORMAdminBundle(),
        new Sonata\BlockBundle\SonataBlockBundle(),
        new Knp\Bundle\MenuBundle\KnpMenuBundle(),
        new Sonata\jQueryBundle\SonatajQueryBundle(),
        // ...
    );
}

В файл app/config/routing.yml нужно добавить роутинг для административного интерфейса:

# app/config/routing.yml
admin:
    resource: '@SonataAdminBundle/Resources/config/routing/sonata_admin.xml'
    prefix: /admin
 
_sonata_admin:
    resource: .
    type: sonata_admin
    prefix: /admin

И записать в директорию web css,js и пр. от установленных бандлов

php app/console assets:install web

Чтобы добавить пароль на адмистистративный интерфейс можно воспользоваться либо штатной авторизацией Symfony 2, либо поставить дополнительный бандл FOSUserBundle

В файле app/config/config.yml нужно прописать блоки, используемые SonataAdmig, а также можно задать заголовок и логотип , а также переопределить шаблоны административного интерфейса. Для начала добавим заголовок административного интерфейса:

sonata_admin:
    title:      Сайт.Ру
 
sonata_block:
    default_contexts: [cms]
    blocks:
        sonata.admin.block.admin_list:
            contexts:   [admin]
 
        sonata.block.service.text:
        sonata.block.service.rss:

Для того чтобы включить сервис translator нужно модифицировать app/config/config.yml:

 framework:
     translator:      { fallback: %locale% }

После установки, при обращении по адресу http://localhost/admin/dashboard (предполагаем что Symfony 2 установлена на сайт с именем http://localhost) выводится пустой административный интерфейс, для которого пока не прописаны сервисы администрирования сущностей.

Замечание: Translator и IE

Чтобы компонент translator определял русский accept-language нужно в настройках IE добавить в разделе Свойства обозревателя/ Общие / Языки русский язык с кодом ru-Ru

Пример использования

В качеcтве примера сделаем административный интерфейс для редактирования новостей, сущности которых описаны в статье Создание CRUD приложения на Symfony 2. Исходники используемых сущностей можно посмотреть на Github.

SonataAdminBundle использует архитектуру, в которой описание административного интерфейса производится посредством специального класса Admin, в котором производится конфигурация формы редактирования, списка записей, формы поиска записей, страницы отображения записи. Этот принцип был заимствован из проекта Django.

Классы {Имя сущности}Admin

Для редактирования новостей, ссылок к новостям и категорий новостей нужно создать 3 класса в директории Test/NewsBundle/Admin: NewsAdmin, NewsLinkAdmin и NewsCategoryAdmin:

<?php
namespace Test\NewsBundle\Admin;
 
use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Show\ShowMapper;
 
use Knp\Menu\ItemInterface as MenuItemInterface;
 
class NewsAdmin extends Admin
{
    /**
     * Конфигурация отображения записи
     *
     * @param \Sonata\AdminBundle\Show\ShowMapper $showMapper
     * @return void
     */
    protected function configureShowField(ShowMapper $showMapper)
    {
        $showMapper
                ->add('id', null, array('label' => 'Идентификатор'))
                ->add('title', null, array('label' => 'Заголовок'))
                ->add('announce', null, array('label' => 'Анонс'))
                ->add('text', null, array('label' => 'Текст'))
                ->add('pubDate', null, array('label' => 'Дата публикации'))
                ->add('newsLinks', null, array('label' => 'Ссылки к новости'))
                ->add('newsCategory', null, array('label' => 'Идентификатор'));
    }
 
    /**
     * Конфигурация формы редактирования записи
     * @param \Sonata\AdminBundle\Form\FormMapper $formMapper
     * @return void
     */
    protected function configureFormFields(FormMapper $formMapper)
    {
        $formMapper
                ->add('title', null, array('label' => 'Заголовок'))
                ->add('announce', null, array('label' => 'Анонс'))
                ->add('text', null, array('label' => 'Текст'))
                ->add('pubDate', null, array('label' => 'Дата публикации'))
 
        //by_reference используется для того чтобы при трансформации данных запроса в объект сущности
        //которую выполняет Symfony Form Framework, использовался setter сущности News::setNewsLinks
                ->add('newsLinks', 'sonata_type_collection',
                      array('label' => 'Ссылки', 'by_reference' => false),
                      array(
                           'edit' => 'inline',
                           //В сущности NewsLink есть поле pos, отражающее положение ссылки в списке
                          //указание опции sortable позволяет менять положение ссылок в списке перетаскиваением
                           'sortable' => 'pos',
                           'inline' => 'table',
                      ))
                ->add('newsCategory', null, array('label' => 'Категория'))
                ->setHelps(array(
                                'title' => 'Подсказка по заголовку',
                                'pubDate' => 'Дата публикации новости на сайте'
                           ));
    }
 
    /**
     * Конфигурация списка записей
     *
     * @param \Sonata\AdminBundle\Datagrid\ListMapper $listMapper
     * @return void
     */
    protected function configureListFields(ListMapper $listMapper)
    {
        $listMapper
                ->addIdentifier('id')
                ->addIdentifier('title', null, array('label' => 'Заголовок'))
                ->add('pubDate', null, array('label' => 'Дата публикации'))
                ->add('newsCategory', null, array('label' => 'Категория'));
    }
 
    /**
     * Поля, по которым производится поиск в списке записей
     *
     * @param \Sonata\AdminBundle\Datagrid\DatagridMapper $datagridMapper
     * @return void
     */
    protected function configureDatagridFilters(DatagridMapper $datagridMapper)
    {
        $datagridMapper
                ->add('title', null, array('label' => 'Заголовок'));
    }
 
    /**
     * Конфигурация левого меню при отображении и редатировании записи
     *
     * @param \Knp\Menu\ItemInterface $menu
     * @param $action
     * @param null|\Sonata\AdminBundle\Admin\Admin $childAdmin
     *
     * @return void
     */
    protected function configureSideMenu(MenuItemInterface $menu, $action, Admin $childAdmin = null)
    {
        $menu->addChild(
            $action == 'edit' ? 'Просмотр новости' : 'Редактирование новости',
            array('uri' => $this->generateUrl(
                $action == 'edit' ? 'show' : 'edit', array('id' => $this->getRequest()->get('id'))))
        );
    }
}

Административный класс для ссылок новостей содержит только метод configureFormFields, т.к. ссылки новостей редактируются вместе с новостью:

<?php
namespace Test\NewsBundle\Admin;
 
use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Form\FormMapper;
 
class NewsLinkAdmin extends Admin
{
    /**
     * @param \Sonata\AdminBundle\Form\FormMapper $formMapper
     * @return void
     */
    protected function configureFormFields(FormMapper $formMapper)
    {
        $formMapper
                ->add('url', null, array('label' => 'URL', 'required' => true))
                ->add('text', null, array('label' => 'Описание'))
                ->add ('pos','hidden');
    }
}

NewsCategoryAdmin создается по аналогии с NewsAdmin. Исходники административных классов можно посмотреть на Github.

Регистрация админстративных сервисов

Административный класс нужно зарегистировать как сервис, для чего его нужно прописать в Test/NewsBundle/Resources/config/services.xml. Для сервисов административного интерфейса указывается тэг «sonata.admin», позволяющий отличать их от других сервисов. Также указывается название группы пунктов меню (атрибут «group») и название пункта меню (атрибут «label») — эти данные используются для построения меню административного интерфейса. В нашем случае пункт меню для редактирования ссылок к новости в главном меню показывать не нужно, т.к. они заносятся на странице редактирования новости. Поэтому для сервиса c id=»test.news.admin.newsLink» ставим атрибут show_in_dashboard=»false».

В приведенном примере сервисы используют стандартный контроллер SonataAdminBundle:CRUD, однако при необходимости можно создавать свои контроллеры.

<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
    <services>
        <service id="test.news.admin.news" class="Test\NewsBundle\Admin\NewsAdmin">
            <tag name="sonata.admin" manager_type="orm" group="Новости" label="Новости"/>
            <argument/>
            <argument>Test\NewsBundle\Entity\News</argument>
            <argument>SonataAdminBundle:CRUD</argument>
        </service>
        <service id="test.news.admin.newsLink" class="Test\NewsBundle\Admin\NewsLinkAdmin">
            <tag name="sonata.admin" manager_type="orm" show_in_dashboard="false" />
            <argument/>
            <argument>Test\NewsBundle\Entity\NewsLink</argument>
            <argument>SonataAdminBundle:CRUD</argument>
        </service>
        <service id="test.news.admin.newsCategory" class="Test\NewsBundle\Admin\NewsCategoryAdmin">
            <tag name="sonata.admin" manager_type="orm" group="Новости" label="Категории новостей"/>
            <argument/>
            <argument>Test\NewsBundle\Entity\NewsCategory</argument>
            <argument>SonataAdminBundle:CRUD</argument>
        </service>
    </services>
</container>

Что получилось

После перезагрузки административный интерфейс выглядит так:


При нажатии на ссылку «Новости / Список» выводится список новостей с возможностью фильтрации записей:

Страница редактирования новоcти выглядит так:

Изменение позиции привязанных сущностей

Привязанные к новости ссылки добавляются без перезагрузки страницы. В сущность «NewsLink» добавлено поле pos, по которому ведется сортировка при запрашивании ссылок к новости. Указании опции ‘sortable’ => ‘pos’ для типа поля sonata_type_collection добавляет в интерфейс возможность изменения порядка новостей, путем перетаскивания строк таблицы:

Навигация

В базовой поставке SonataAdminBundle есть русская локализация стандартных названий кнопок, заголовков и и т.п. Чтобы локализация была полной, для созданных разделов административного интерфейса нужно создать переводы заголовков, которые автоматически создаются на основе названий сущностей, например, News List, News Create. Для этого в директории Test/NewsBundle/Resources/translations требуется создать файл messages.ru.xliff (про сервис трансляции можно почитать здесь)

<?xml version="1.0"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
    <file source-language="ru" datatype="plaintext" original="" >
        <body>
           <trans-unit id="News List">
                <source>News List</sourсe>
                <target>Список новостей</target>
            </trans-unit>
           <trans-unit id="News Create">
                <source>News Create</sourсe>
                <target>Создание новости</target>
           </trans-unit>
           <trans-unit id="News Edit">
                <source>News Edit</sourсe>
                <target>Редактирование новости</target>
           </trans-unit>
           <trans-unit id="News Category List">
                <source>News Category List< sourсe>
                <target>Список категорий новостей</target>
            </trans-unit>
           <trans-unit id="News Category Create">
                <source>News Category Create</sourсe>
                <target>Создание категории новости</target>
           </trans-unit>
           <trans-unit id="News Category Edit">
                <source>News Category Edit</sourсe>
                <target>Редактирование категории новостей</target>
           </trans-unit>
        </body>
    </file>
</xliff>

Заключение

В итоге получился функциональный, расширяемый интерфейс редактирования записей. Все шаблоны и контроллеры, используемые SonataAdmin можно переопределить в конфигурации приложения. Разработчики на базе SonataAdmin сделали несколько полезных для разработки веб-приложений бандлов, реализующих ряд базовых функций: SonataUserBundle (управление пользователями) , SonataNewsBundle (блог) , SonataMediaBundle (управление медиа-ресурсами) и SonataPageBundle (прототип CMS). Большой проблемой является плохая документированность, особенно SonataPageBundle, хотя на первый взляд интересный продукт.

Запись опубликована в рубрике Без рубрики. Добавьте в закладки постоянную ссылку.

10 комментариев: Административный интерфейс на Symfony 2 и SonataAdminBundle

  1. Марсель говорит:

    Спасибо за отличную статью — очень помогла! Прошу помочь в одном вопросе — на картинке видно что вверху в панели «истории» стоит «0/1/2″ вместо необходимых ссылок. Почему так происходит и как это поправить.
    Спасибо.

    http://i49.tinypic.com/wclh78.png

  2. Николай говорит:

    в функции
    public function registerBundles()
    вы забыли
    new Sonata\BlockBundle\SonataBlockBundle()

  3. Антон говорит:

    А как все это завернуть в отдельную авторизацию? вот я прикрутил sonata user bundle. Создал сущность admin_user , которые хранятся в отдельной таблице admi_user. через фикстуры залил туда тестового админа. при логине, если ввести правильные данные, он не ругается, но и не авторизирует, а обратно редиректит. Но если данные левые вводить. то пишет что bad credentials, т.е. он лезет в эту таблицу нормально, но сама авторизация в сессию не устанавливается. Не подскажете, как побороть такое?

  4. Andrei говорит:

    Реализовал теги так как на скриншоте http://uploads.ru/UexJ6.png
    Использовал связь много ко многим.
    Можно ли как-то реализовать добавление тегов, вводя их через запятую в строку. Потому что большое количество тегов очень сильно затрудняет их поиск в общем списке

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">