Постановка задачи
Имеется набор некоторых объектов, например, входящих писем, связанных связью один-к-одному (для целей данной статьи вид связи значения не имеет) с объектами из другого набора, например, ответами на письма. Для управления сущностями используется 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 писем открывается форма для создания ответа на письмо.
Во второй части статьи будет рассмотрена реализация установления автоматической связи между письмом и ответом на него, а также вопросы отображения кнопки в зависимости от наличия у пользователя прав на создание ответа.
Ссылки на используемые ресурсы
ссылка на оригинал статьи https://habrahabr.ru/post/278987/
Добавить комментарий