Пользователь
0,0
рейтинг
9 августа 2011 в 16:30

Разработка → Создание CRUD приложения на Symfony 2 из песочницы

Symfony 2.0


Недавно вышедшая версия фреймворка Symfony 2 включает в себя много интересных фич. В данной статье хочу рассказать про создание CRUD приложений — очень часто встречающейся задачи создания веб-интерфейса для создания, чтения, обновления и удаления записей в БД.

Про архитектуру и установку Symfony 2 уже было на хабре, поэтому считаем что Symfony 2 SE уже установлена и основные используемые понятия (бандлы, формы, шаблоны и т.д) вам знакомы.

Основные задачи, стоящие при разработке стандартного CRUD-приложения на Symfony 2


  1. Разработка модели данных
  2. Разработка контроллеров, форм и шаблонов позволяющих создавать, читать, обновлять и удалять сущности модели данных

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

Разработка модели данных


Создание описаний сущностей посредством ручного написания yml или xml достаточно утомительное занятие (хотя конечно можно воспользоваться плагином к Mysql Workbench или специализированным ПО). Для ускорения процесса в Symfony 2 есть удобное средство генерации описания модели данных путем реверс-инжиниринга существующей БД.

Для начала создаем бандл TestNewsBundle:

php app/console generate:bundle --namespace=Test/NewsBundle --format=annotation  --structure


Создаем схему БД в Mysql Workbench (или любом другом средстве проектирования БД):



SET FOREIGN_KEY_CHECKS=0;

CREATE  TABLE `news` (
  `id` INT NOT NULL AUTO_INCREMENT ,
  `news_category_id` INT NOT NULL ,
  `title` VARCHAR(255) NULL ,
  `announce` TEXT NULL ,
  `text` TEXT NULL ,
  `pub_date` DATE NULL ,
  PRIMARY KEY (`id`) ,
  INDEX `pub_date` (`pub_date` ASC) ,
  INDEX `fk_news_news_category` (`news_category_id` ASC) ,
  CONSTRAINT `fk_news_news_category`
    FOREIGN KEY (`news_category_id` )
    REFERENCES  `news_category` (`id` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;

CREATE  TABLE `news_category` (
  `id` INT NOT NULL AUTO_INCREMENT ,
  `name` VARCHAR(255) NULL ,
  PRIMARY KEY (`id`) )
ENGINE = InnoDB

CREATE  TABLE  `news_link` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `news_id` INT NOT NULL ,
  `url` varchar(255) DEFAULT NULL,
  `text` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`) ,
  INDEX `fk_news_link_news1` (`news_id` ASC) ,
  CONSTRAINT `fk_news_link_news1`
    FOREIGN KEY (`news_id` )
    REFERENCES  `news` (`id` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;

Создаем классы doctrine:

php app/console doctrine:mapping:import TestNewsBundle annotation


В результате выполнения этой команды в поддиректории Test/NewsBundle/Entity будут созданы по одному классу для каждой таблицы, при этом конфигурация маппинга объектов в реляционные таблицы для Doctrine ORM описаны в аннотациях классов, например класс News:

<?php
namespace Test\NewsBundle\Entity;
use Doctrine\ORM\Mapping as ORM;

/**
 * Test\NewsBundle\Entity\News
 *
 * @ORM\Table(name="news")
 * @ORM\Entity
 */
class News
{
    /**
     * @var integer $id
     *
     * @ORM\Column(name="id", type="integer", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @var string $title
     *
     * @ORM\Column(name="title", type="string", length=255, nullable=true)
     */
    private $title;

    /**
     * @var text $announce
     *
     * @ORM\Column(name="announce", type="text", nullable=true)
     */
    private $announce;

    /**
     * @var text $text
     *
     * @ORM\Column(name="text", type="text", nullable=true)
     */
    private $text;

  /**
     * @var date $pubDate
     *
     * @ORM\Column(name="pub_date", type="date", nullable=true)
     */
    private $pubDate;

    /**
     * @var NewsCategory
     *
     * @ORM\ManyToOne(targetEntity="NewsCategory")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="news_category_id", referencedColumnName="id")
     * })
     */
    private $newsCategory;
}

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

php app/console doctrine:generate:entities TestNewsBundle


Создание заготовки для CRUD — приложения


Cоздаем заготовку для работы с новостями с использованием команды doctrine:generate:crud. Формат роутинга — аннотации в файле контроллера (Test/NewsBundle/Controller/NewsController). Роутинг через аннотации в файле контроллера работает через бандл SensioFrameworkExtra (в поставке Symfony 2 SE он есть).

php app/console doctrine:generate:crud --entity=TestNewsBundle:News --route-prefix=news --with-write --format=annotation

Теперь при заходе по указанному при генерации пути (если Symfony 2 распакован в wwwroot — http://localhost/Symfony/web/app_dev.php/news/) показывается пустой список новостей, а при нажатии на ссылку «Create new entry» — открывается форма создания записи по умолчанию.

Создадим такую же заготовку для работы с категориями новостей и занесем несколько категорий:

php app/console doctrine:generate:crud --entity=TestNewsBundle:NewsCategory --route-prefix=newscategory --with-write --format=annotation


Для того чтобы у нас в дальнейшем была заготовка для формы добавления ссылки к новости аналогично сгенерируем заготовку для сущности NewsLink:

php app/console doctrine:generate:crud --entity=TestNewsBundle:NewsLink --route-prefix=newslink --with-write --format=annotation


Чтобы при выводе списка категорий в форме добавления новости система знала какое поле показывать в селекте, в класс Test/NewsBundle/Entity/NewsCategory нужно добавить метод:

function __toString()
{
  return $this->getName();
}


Модификация класса формы


Теперь подправим сгенерированную форму Test/NewsBundle/Form/NewsType. Добавим заголовки к полям (label), проставим нужные типы полей — text (отображается input type=text), textarea. В полях pubDate и newsCategory оставим тип поля null — в этом случае Symfony Form Component сам «угадывает» какой тип поля показать.

<?php
namespace Test\NewsBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;

class NewsType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('title', 'text', array('label' => 'Заголовок'))
                ->add('announce', 'textarea', array('label' => 'Анонс'))
                ->add('text', 'textarea', array('label' => 'Текст'))
                ->add('pubDate', null, array('label' => 'Дата новости'))
                ->add('newsCategory', null, array('label' => 'Категория'));
    }

    public function getName()
    {
        return 'news';
    }
}


Модификация шаблона формы


Подробно про формы и шаблонизацию в формах можно почитать разделы руководства Формы и Кастомизация форм. В создаваемой форме заменим отображение формы на табличное, для этого в шаблон нужно добавить указание использовать табличную верстку (верстку по умолчанию также можно изменить в настройках).

{% form_theme form 'form_table_layout.html.twig'  %}

Однако нам в дальнейшем нужно будет переопределить стандартное отображение поля, поэтому шаблон занесения новости Test/NewsBundle/Resources/views/News/new.html.twig выглядит так:

{% use 'form_table_layout.html.twig' %}
{% form_theme form _self %}

<h1>Занесение новости</h1>
 

<form action="{{ path('news_create') }}" method="post" {{ form_enctype(form) }}>
    {{ form_widget(form) }}
    <p>
        <button type="submit">Создать новость</button>
    </p>
</form>

<ul class="record_actions">
    <li>
        <a href="{{ path('news') }}">
            Назад к списку
        </a>
    </li>
</ul>


Форма отображается в табличной верстке:


Редактирование связанных записей


Теперь займемся самым интересным — редактированием ссылок к новости. При реверс-инжиниринге в классы сущностей была автоматически добавлена только связь «Ссылка -> Новость». Для того чтобы добавить связь «Новость -> Cсылки» нужно в классе Test\NewsBundle\Entity\News добавить:

/**
 * @ORM\OneToMany(targetEntity="NewsLink", mappedBy="news", cascade={"all"})
*/
protected $newsLinks;
 
function __construct()
{
 $this->newsLinks = new \Doctrine\Common\Collections\ArrayCollection();
}

После чего выполнить команду (будут сгенерированы геттер и сеттер для атрибута $newsLinks):

php app/console doctrine:generate:entities TestNewsBundle


Теперь в класс Test/NewsBundle/Form/NewsType добавляем поле типа «Collection», позволяющее редактировать набор связанных сущностей:

$builder->add('title', 'text', array('label' => 'Заголовок'))
                ....
              ->add('newsLinks', 'collection', array(
                                               'label' => 'Ссылки к новости',
                                               'type' => new NewsLinkType(),
                                               'allow_add' => true,
                                               'allow_delete' => true,
                                               'prototype' => true
                                              ));

В классе Test\NewsBundle\Form\NewsLinkType обязательно нужно указать название класса редактируемой сущности (опция data_class):

<?php
namespace Test\NewsBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;

class NewsLinkType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('url')
                ->add('text');
    }

    public function getName()
    {
        return 'newsLinkType';
    }


    public function getDefaultOptions(array $options)
    {
        return array(
            'data_class' => 'Test\NewsBundle\Entity\NewsLink',
        );
    }
}


Однако если сейчас посмотреть форму — то будет отображен только заголовок поля «Ссылки к новости». Чтобы форма заработала нужно еще модифицировать шаблон. Заодно и вынесем определение формы в отдельный шаблон. Для этого создаем файл Test/NewsBundle/Resources/views/News/form.html.twig:
{% use 'form_table_layout.html.twig' %}
{% form_theme form _self %}

<script language="JavaScript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>

<form action="{{ entity.id  ? path('news_update', { 'id': entity.id }) : path('news_create') }}" method="post" {{ form_enctype(form) }}>

{{ form_errors(form) }}
<table>
  {{ form_row (form.title) }}
  {{ form_row (form.announce) }}
  {{ form_row (form.text) }}
  {{ form_row (form.pubDate) }}
  {{ form_row (form.newsCategory) }}
  <tr>
   <td valign="top">Ссылки к новости</td>
   <td>

   <!-- Шаблон вывода строки с полем занесения/редактирования ссылки к новости -->
   {% macro linkRow(link) %}
   <tr>
    <td>{{ form_widget(link.url) }}</td>
    <td>{{ form_widget(link.text) }}</td>
    <td><a href="#" class="deleteRowLink">X</a></td>
   </tr>
  {% endmacro %}

  <!-- В этом контейнере находится шаблон строки занесения/редактирования ссылки к новости -->
  <!-- При нажатии на кнопку #addLink он добавляется к таблице -->
   <script type="text/html" id="nl">{{ _self.linkRow (form.newsLinks.get('prototype')) }}  </script>

  <!-- Таблица в которой будет выводиться список занесенных ссылок -->
  <table id="linksTable">
  <tr><td>Url</td><td>Название ссылки</td></tr>
  {% for key, link in form.newsLinks %}
     {{ _self.linkRow(link) }}
  {% endfor %}
   </table>


   <input type="button" id="addLink" value="Добавить ссылку">

   <script>
   $(function() {
      $("#addLink" ).click(function() {
            $('#linksTable tbody').append($('#nl').html().replace(/\$\$name\$\$/g, $('#linksTable tbody tr').length));  });
      $("form a.deleteRowLink").live('click', function() {
            $(this).closest('tr').remove(); });
    });
   </script>

   </td>
  </tr>
</table>

{{ form_rest(form) }}

 <p><button type="submit">Сохранить</button></p>
</form>


Шаблон занесения записи Test/NewsBundle/Resources/views/News/new.html.twig:

<h1>Занесение новости</h1>
{% include 'TestNewsBundle:News:form.html.twig' %}
<ul class="record_actions">
    <li>
        <a href="{{ path('news') }}">
            Назад к списку
        </a>
    </li>
</ul>


Шаблон редактирования записи Test/NewsBundle/Resources/views/News/edit.html.twig:

<h1>Редактирование новости</h1>
{% include 'TestNewsBundle:News:form.html.twig' with { 'form' : edit_form } %}
<ul class="record_actions">
    <li>
        <a href="{{ path('news') }}">
            Back to the list
        </a>
    </li>
    <li>
        <form action="{{ path('news_delete', { 'id': entity.id }) }}" method="post">
            {{ form_widget(delete_form) }}
            <button type="submit">Delete</button>
        </form>
    </li>
</ul>


Можно конечно оставить один шаблон для занесения и редактирования записей, но в данном примере оставим структуру шаблонов и методов контроллера, которую сгенерировал CRUD-генератор, без изменений.

Далее нужно модифицировать класс котроллера Test/NewsBundle/Controller/NewsController. Хотя мы и указали в параметрах связи «Новость — Ссылка» опцию cascade = all (при сохранении сущности сохраняются связанные сущности), однако все равно требуется определить привязку объектов NewsLink к родительскому объекту News:

public function createAction()
{
    $entity = new News();
    $request = $this->getRequest();
    $form = $this->createForm(new NewsType(), $entity);
    $form->bindRequest($request);

    if ($form->isValid()) {
        $em = $this->getDoctrine()->getEntityManager();
       
        // Нужно указать родительский объект
        foreach ($entity->getNewsLinks() as $link)
        {
          $link->setNews($entity);
        }
        $em->persist($entity);
        $em->flush();

        return $this->redirect($this->generateUrl('news_show', array('id' => $entity->getId())));
    }

    return array(
       'entity' => $entity,
       'form' => $form->createView());
}


Для метода updateAction нужно еще предусмотреть возможно удаления связанных записей. В html-форме есть удаление строк таблицы с использованием JQuery, однако этого недостаточно для удаления записей — это нужно явно реализовать в контроллере:

    public function updateAction($id)
    {
        $em = $this->getDoctrine()->getEntityManager();

        $entity = $em->getRepository('TestNewsBundle:News')->find($id);

        if (!$entity) {
            throw $this->createNotFoundException('Unable to find News entity.');
        }


        $beforeSaveLinks = $currentLinkIds = array();
        foreach ($entity->getNewsLinks() as $link)
            $beforeSaveLinks [$link->getId()] = $link;


        $editForm = $this->createForm(new NewsType(), $entity);
        $deleteForm = $this->createDeleteForm($id);

        $request = $this->getRequest();
        $editForm->bindRequest($request);

        if ($editForm->isValid()) {

            foreach ($entity->getNewsLinks() as $link)
            {
                $link->setNews($entity);
                //Если ссылка - не только что занесенная (у нее есть id)
                if ($link->getId()) $currentLinkIds[] = $link->getId();
            }

            $em->persist($entity);

            //Если ссылка которая была до сохранения отсутствует в текущем наборе - удаляем ее
           foreach ($beforeSaveLinks as $linkId => $link)
                 if (!in_array( $linkId, $currentLinkIds)) $em->remove($link);

            $em->flush();

            return $this->redirect($this->generateUrl('news_edit', array('id' => $id)));
        }

        return array(
            'entity' => $entity,
            'edit_form' => $editForm->createView(),
            'delete_form' => $deleteForm->createView(),
        );
    }


Форма с возможностью добавления связанных записей:

Форма с возможностью добавления связанных записей

Теперь наше приложение позволяет заносить, просматривать, обновлять и удалять новости. При этом работа со связанными ссылками к новости происходит без перезагрузки страницы. Не сказать, чтобы совсем без кодирования, но с достаточно небольшими усилиями был разработан данный функционал. При этом внешний вид формы можно настраивать по своему усмотрению. Используя механизм тем в формах можно переопределить стандартные шаблоны отображения строк и полей формы.
Виктор @vitiko
карма
8,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

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

  • 0
    Перезайлете картинки, пожалуйста. За топик спасибо, после Zend с интересом смотрю на Symfony.
    • 0
      залил на habrastorage.org
      • 0
        благодарю!
  • –2
    В yii как-то попроще это, через gii
    • –3
      Что проще то? А о, например, генерации связей в формах (насчет моделей не знаю) вообще мечтать не приходиться.
      • +3
        Модели связываются автоматом, причём генерятся даже связи много-ко-многим, покрайней мере я даже не помню что-бы я писал руками many_to_many связь (а они у меня есть).
        На уровне форм — да, этого нету. Но генератор кода там такой, что он предназначен для того, что бы вы расширяли его своими шаблонами для кодогенерации. Сообщество уже целые пачки наборов шаблонов для него понаписало: пользуйся — нехочу.
        • –1
          Да, но если название модели не совпадает с названием таблицы, связи генерируются не правильно, а метод tableName вообще не учитывается.
          • +2
            Баг-репорт сделайте :)
      • +4
        В Yii (посредством модуля Gii) автоматически генерятся модели, имеющие связи.
        На примере топика: у нас новость имеет ссылки (через отношение «много ссылок — к одной новости»). При подгрузке данной новости есть возможность сделать т.н. «eager loading», с целью минимизировать количество запросов к БД (в отличие от ручной загрузки связанных с новостью ссылок). Автоматизация — налицо.
        Далее. В форме, описанной для новости, очень легко и тривиально получить список связанных с новостью ссылок посредством конструкции
        $model->links // Тут возвращается массив объектов Links для данной новости (если есть)

        Дальше — дело техники: проходимся по массиву, строим поля формы, в контроллере описываем логику на обновление/удаление набора данных, относящихся к текущему объекту — вуаля.
        Обновление данных (ссылок, в нашем случае) через AJAX — без проблем.

        Ни в коем случае не умаляю достоинств S2 и Doctrine. Это такой же инструмент, как и другие вменяемые CRUD-генераторы. :)
        • 0
          Эх как зацепило про Yii. Я тоже в общем то не говорил, что Yii хуже, просто опровергаю неверное, по-моему мнению, высказывание.
          С Yii я работал, один проект даже дополз до гитхаба :)
  • 0
    Создаем схему БД:

    А как жe YML и миграции?
    • 0
      Ну видно человеку просто так больше нравится.
  • 0
    Начиналось все очень бодренько и интересно.

    А потом как-то странно получилось, что при сохранении объекта приходится руками сохранять все связанные объекты, которые в принципе как-то «понятно» существую в форме.
    // Нужно указать родительский объект
    foreach ($entity->getNewsLinks() as $link)
    {
    $link->setNews($entity);
    }
    $em->persist($entity);


    • 0
      Я тоже думал будут сохраняться автоматом. После изучения форумов и гитхаба (на сайте документации по этой части пока нет) оказалось что нужно явно указывать родительский объект. Надеюсь в 2.1 или раньше будет улучшено.
  • +11
    Когда-то, на заре института, попалась мне в руки книга по винапи.

    Глава начиналась:

    Давайте создадим нашу первую программу. (вроде было окошко с кнопочкой)

    После этого шли три страницы мешанины букв.

    Завершалось вся это фразой в духе: посмотрите, как «легко и просто» (с) с помощью винапи можно создавать замечательные программы.

    «Посмотрите, как с помощью %foo% можно легко и удобно %something%» и wallofcode — это шаблон, напрочь отбивающий всякое потенциальное желание в этом дальше разбираться.

    Обычно очень интересны аспекты, позволяющие что-то сделать именно _легко_ и _удобно_, как-то облегчить жизнь. Вот чего почитать бы о той же симфонии.

    PS: Это я не критикую (на самом деле материал довольно полезный в качестве вводного), и не холиварю (хотя для CRUD-а телодвижений, конечно, многовато), просто накипело.
    • +1
      большая часть кода генерится автоматом, но движений конечно многовато
    • 0
      кто то предлагал такую штуку)
      echo $site->gen('tz.doc')->render();
  • 0
    Как-то слишком объемно, но материал полезный!
  • +2
    Создание описаний сущностей посредством ручного написания yml или xml достаточно утомительное занятие… Для ускорения процесса в Symfony 2 есть удобное средство генерации описания модели данных путем реверс-инжиниринга существующей БД.

    Вы, по-моему, неверно понимаете суть используемых в sf2 абстракций. Модель — это модель (не знаю как ещё по другому сказать) предметной области. Она может быть с БД вообще не связана, а храниться только в памяти. То есть БД это способ хранения модели, а не модель — способ представления БД в sf2 приложении. А если в ТЗ будет что хранилищем должна выступать MongoDB — вы будете создавать фейковый документ, чтобы по нему модель сгенерировалась? (Аравда не уверн есть ли в этом бандле для доктрины генерация вообще)

    Генерировать модель по схеме специально для этого созданной, это, имхо, как проектировать автомобиль начиная с колёс. Я пользовался реверс-инженирингом, но именно когда передо мной стояла задача написать новый фронт-енд к существующей БД. То есть моей задачей было заставить именно эти колеса катиться. И то, буквально первая моя строчка была расширением класса модели новым свойством без его связи с БД.

    Кому-то это покажется придиркой, или спором остроконечников с тупоконечниками, но поверьте разница есть не только в том, что по чему генерируется.
    • 0
      В данном примере рассматривается, как мне кажется, достаточно удобный способ автоматизации генерации заготовки модели по схеме БД, которую уже можно дальше расширять. При этом схему БД можно создать различными визуальными утилитами, для генерации описания модели для ORM-систем знаю только платный ORM Designer.
  • 0
    а где же тесты?
  • 0
    ещё для CRUD можно использовать SonataAdminBundle github.com/sonata-project/SonataAdminBundle
    тогда, например, весь CRUD для страниц будет выглядеть вот так github.com/stfalcon/fwdays/blob/master/src/Stfalcon/Bundle/PageBundle/Admin/PageAdmin.php
    а в стандартном контроллере у меня пока только вывод страниц github.com/stfalcon/fwdays/blob/master/src/Stfalcon/Bundle/PageBundle/Controller/PageController.php
    • +1
      Метод showAction можно еще упростить

      use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
      use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
      use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
      ...
      /**
      * Finds and displays a Page entity.
      *
      * @Route("/{slug}", name="page_show")
      * @ParamConverter("page", class="StfalconPageBundle:Page")
      * @Template()
      */
      public function showAction(Page $page)
      {
      return array(
      'page' => $page,
      );
      }
  • 0
    Когда я вижу, как результат кодогенератора допиливается руками (а иначе в 90% никак), мне всегда страшно становится от того, во что превратится код, если в модель надо будет внести изменения и перезапустить кодогенератор
  • 0
    $builder->add('title', 'text', array('label' => 'Заголовок'))

    ->add('newsLinks', 'collection', array(
    'label' => 'Ссылки к новости',
    'type' => new NewsLinkType(),
    'allow_add' => true,
    'allow_delete' => true,
    'prototype' => true
    ));
    здесь можно добавить 'by_reference' => false в список параметров и определить метод setNewsLinks в модели, в которой и присваивать родительскую сущность как-то так:
    public function setFeatures(\Doctrine\Common\Collections\ArrayCollection $features){
    $this->features = array();
    foreach($features as $feature){
    $feature->setProductTemplate($this);
    $this->addFeature($feature);
    }
    } (здесь другие имена, но суть должна быть понятна
  • 0
    Коллеги, только-только начал изучать Symfony и возникла маленькая проблемка при обновлении существующего entity:

    public function saveAction($id, Request $request) {
    $em = $this->getDoctrine()->getEntityManager();
    $guest = $em->getRepository(«THEEventsBundle:Guest»)->find($id);

    if (!$guest) {
    throw $this->createNotFoundException('No guest found for id '.$id);
    }

    $form = $this->createFormBuilder($guest)
    ->add(«fio», «text», array(«label» => «ФИО»))
    ->add(«event», null, array(«label» => «Событие»))
    ->getForm();

    if ($request->getMethod() == «POST») {
    $form->bindRequest($request);

    if ($form->isValid()) {
    $em->flush();

    return $this->redirect($this->generateUrl(«guests_list»));
    }
    }

    return array(«form» => $form->createView());
    }

    По этому коду, вместо обновления записи, создается новая запись в БД
    Подскажите, в чем может быть причина такого поведения?

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