Постановка задачи


Имеется набор некоторых объектов, например, входящих писем, связанных связью один-к-одному (для целей данной статьи вид связи значения не имеет) с объектами из другого набора, например, ответами на письма. Для управления сущностями используется SonataAdminBundle (т.е. для каждой сущности определен Admin-класс). Необходимо создавать новые ответы непосредственно из списка (List View) писем.

Сущности (entity) соответственно письма и ответа могут выглядеть следующим образом:

Сущность входящего письма
    namespace AppBundle\Entity;

    use Doctrine\ORM\Mapping as ORM;

    /**
    * @ORM\Entity
    * @ORM\Table(name="incoming", options={"comment":"Входящее письмо"})
    */
    class Incoming
    {
        /**
        * @ORM\Column(type="integer")
        * @ORM\Id
        * @ORM\GeneratedValue(strategy="AUTO")
        */
        protected $id;

        protected $incomingTitle;

        /**
        * @ORM\OneToOne(targetEntity="Response", mappedBy="incoming")
        */
        protected $response;

        // другие поля

        /**
        * Добавление ответа на входящее письмо
        *
        * @param \AppBundle\Entity\Response $response
        *
        * @return \AppBundle\Entity\Incoming
        */
        public function setResponse( \AppBundle\Entity\Response $response)
        {
            $this->response = $response;

            return $this;
        }
    }

    


Сущность ответа на входящее письмо
    namespace AppBundle\Entity;

    use Doctrine\ORM\Mapping as ORM;

    /**
    * @ORM\Entity
    * @ORM\Table(name="response", options={"comment":"Ответ на входящее письмо"})
    */
    class Response
    {
        /**
        * @ORM\Column(type="integer")
        * @ORM\Id
        * @ORM\GeneratedValue(strategy="AUTO")
        */
        protected $id;

        /**
        * @ORM\Column(type="text", options={"comment":"Заголовок ответа"})
        */
        protected $responseTitle;

        /**
        * @ORM\OneToOne(targetEntity="Incoming", inversedBy="response")
        * @ORM\JoinColumn(name="incoming_id", referencedColumnName="id")
        */
        protected $incoming;

        /**
         * @ORM\Column(type="text", options={"comment":"Текст ответа"})
         */
        protected $text;

        /**
        * Добавление входящего письма
        *
        * @param \AppBundle\Entity\Incoming $incoming
        *
        * @return \AppBundle\Entity\Response
        */
        public function setResponse( \AppBundle\Entity\Incoming $incoming)
        {
            $this->incoming = $incoming;

            return $this;
        }

        //Другие действия
    }


Варианты решения


Решение задачи на первый взгляд аналогично созданию Custom Admin Action в SonataAdminBundle, процесс которого описан в [1]. Следуя данному руководству, мы могли бы реализовать действие, которое создает и сохраняет объект ответа, прикрепляет его к текущему объекту письма и перенаправляет пользователя на форму редактирования сохраненного ответа для ввода его заголовка и текста.

При этом нам было бы необходимо:

  • Создать действие по созданию ответа (например, createResponseAction) в CRUD контроллере, унаследовавшись от Sonata\AdminBundle\Controller\CRUDController
  • Скопировать содержимое createAction из Sonata\AdminBundle\Controller\CRUDController в наше действие, внеся в него изменения, выполняющее функции, которые описаны выше

Такой подход привлекателен тем, что для его реализации достаточно строго следовать руководству по созданию Custom Admin Action, однако ведет к дублированию кода и чреват непредвиденными ошибками при доработке createAction до createResponseAction. Избежать недостатков подхода можно, напрямую используя существующие, а также переопределяя предназначенные для этого действия Sonata\AdminBundle\Controller\CRUDController.

Для этого будем решать задачу поэтапно:

  • обеспечим переход на форму создания нового ответа по нажатию элемента управления (например, кнопки) в строке List View писем;
  • реализуем автоматическую связь между создаваемым ответом и письмом, в строке которого был нажат элемент управления, до отображения формы создания.

Переход на форму создания нового ответа


Процесс создания элемента управления в строке ListView подробно описан в [1]. Остановимся на особенностях, касающихся решения нашей задачи, а именно — генерации url для перехода на форму создания нового объекта. Предлагаемый в [1] вариант

{# src/AppBundle/Resources/views/CRUD/list__action_create_other_admin.html.twig #}
<a class="btn btn-sm" href="{{ admin.generateObjectUrl('create', object) }}">Создать ответ</a>

не подходит, поскольку функция admin.generateObjectUrl генерирует url для создания объекта текущего Admin-класса; в нашем случае это письмо (Incoming), а нужно, чтобы был ответ (Response). Поэтому используем следующий вариант, украсив кнопку иконкой:

{# src/AppBundle/Resources/views/CRUD/list__action_create_other_admin.html.twig #}
<a href="{{ admin.getRouteGenerator.generateUrl(template_variables.otherAdmin, 'create'}" class="btn btn-sm btn-default edit_link" title="Создать ответ">
    <i class="fa fa-plus"></i>
    Создать ответ
</a>

Ключевым моментом здесь является использование функции admin.getRouteGenerator.generateUrl, принимающей в качестве аргумента Admin-сервис, для создания объекта которого необходимо сгенерировать url. Теперь задача состоит в том, чтобы передать нужный Admin-сервис в шаблон. Это можно сделать, обратившись к контейнеру Symfony2 прямо из list__action_create_other_admin.html.twig, что лишит подход универсальности, поэтому мы использовали переменную template_variables.otherAdmin, которая передается в шаблон описанным ниже способом.

Шаблоны, соответствующие кнопкам _actions в строке ListView, отображаются посредством twig-функции include шаблона SonataAdminBundle CRUD\list__action.html.twig, а именно:

{% include actions.template %}

где actions.template — переменная, которая определеяется в Admin-классе в секции configureListFields.

    protected function configureListFields(ListMapper $listMapper)
    {
        $listMapper

        // other fields...

            ->add('_action', 'actions', array(
                'actions' => array(

                // ...

                'createOtherAdmin' => array(

                    // ВОТ ЭТА ПЕРЕМЕННАЯ
                    'template' => 'AppBundle:CRUD:list__action_create_other_admin.html.twig'
                )
            )
        ))
        ;
    }

Таким образом, нам нужно добавить в CRUD\list__action.html.twig ключевое слово with, чтобы обеспечить передачу переменной в дочерние шаблоны. Поскольку не все из них будут использовать данную переменную, следует сделать проверку на ее наличие:

    {% include actions.template with {template_variables : (actions.template_variables is defined ? actions.template_variables : null)} %}

Теперь можно определить переменную template_variables.otherAdmin в Admin-классе, присвоив ей нужный Admin-сервис (в нашем случае это sonata.admin.response) и она станет доступна в шаблоне list__action_create_other_admin.html.twig.

    protected function configureListFields(ListMapper $listMapper)
    {
        $listMapper

        // other fields...

            ->add('_action', 'actions', array(
                'actions' => array(

                // ...

                'createOtherAdmin' => array(

                    'template' => 'AppBundle:CRUD:list__action_create_other_admin.html.twig',

                    // Передаем Admin-сервис в качестве аргумента в шаблон
                    'template_variables' => array('otherAdmin'=> $this->getConfigurationPool()->getContainer()->get('sonata.admin.response');)
                )
            )
        ))
        ;
    }

Теперь при нажатии на кнопку в строке List View писем открывается форма для создания ответа на письмо.

Во второй части статьи будет рассмотрена реализация автоматического установления связи между письмом и ответом на него, а также вопросы отображения кнопки в зависимости от наличия у пользователя прав на создание ответа.


Ссылки на используемые ресурсы


  1. CREATING A CUSTOM ADMIN ACTION

Комментарии (0)