Формы Symfony2: Как мне сохранить сущность с обнуляемой ассоциацией? - PullRequest
7 голосов
/ 13 октября 2011

При сохранении данных отправки формы у меня возникают проблемы с сохранением нового экземпляра сущности, в котором сущность имеет нулевую связь с другой сущностью, и я пытаюсь установить для него значение null.После создания нового экземпляра сущности для формы, привязки отправленного запроса к форме и сохранения и сброса экземпляра сущности, в зависимости от того, как я заполняю свойство для связанной сущности, я получаю

  1. UnexpectedTypeException: Expected argument of type "object or array", "NULL" given (если установлено значение null) или
  2. InvalidArgumentException: A new entity was found through the relationship 'AccessLog#document' that was not configured to cascade persist operations for entity (если задано новое, пустое экземпляр связанной сущности, который я не хочу сохранять).

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

Обратите внимание, что поведение независимо от того, установлена ​​ли связь как однонаправленная или двунаправленная.

Подробности:

У меня есть сущность, имеющая связь «многие к одному» с другой сущностью (сокращенно):

/** @Entity */
class AccessLog
{
    /** @Id @Column(type="integer") */
    private $access_log_id;

    /** @Column(type="integer", nullable=true) */
    private $document_id;

    /**
     * @ManyToOne(targetEntity="Document", inversedBy="access_logs", cascade={"persist"})
     * @JoinColumn(name="document_id", referencedColumnName="document_id")
     */
    private $document;

    // plus other fields
    // plus getters and setters for all of the above...
}

Связанная сущность ничего необычного:

/** @Entity */
class Document
{
    /** @Id @Column(type="integer") */
    private $document_id;

    /** @Column(length=255) */
    private $name;

    /** @OneToMany(targetEntity="AccessLog", mappedBy="document") */
    private $access_logs;

    // plus other fields
    // plus getters and setters for all of the above...
}

У меня есть Symfonyформа для ввода данных для новых записей AccessLog:

class AccessLogFormType extends AbstractType
{
    public function getName()
    {
        return 'access_log_form';
    }

    public function getDefaultOptions(array $options)
    {
        return array('data_class' => 'AccessLog');
    }

    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('access_log_id', 'hidden');
        $builder->add('document_id', 'hidden', array(
            'required' => false
        ));
        $builder->add('document', new DocumentType(), array(
            'label' => 'Document',
            'required' => false
        ));
        //...
    }
}

Со следующим определением вспомогательного типа:

class DocumentType extends AbstractType
{
    public function getName()
    {
        return 'document';
    }

    public function getDefaultOptions(array $options)
    {
        return array('data_class' => 'Document');
    }

    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('name', 'text', array(
            'required' => false
        ));
    }
}

Мой контроллер включает в себя следующее:

public function save_access_log_action()
{
    $request = $this->get('request');
    $em = $this->get('doctrine.orm')->getEntityManager();

    $access_log = null;

    if ($request->getMethod() === 'POST') {
        $data = $request->request->get('access_log_form');

        if (is_numeric($data['access_log_id'])) {
            $access_log = $em->find('AccessLog', $data['access_log_id']);
        } else {
            $access_log = new AccessLog();
        }

        if (is_numeric($data['document_id'])) {
            $document = $em->find('Document', $data['document_id']);
            $access_log->set_document($document);

        } else {
            // Not calling set_document() since there shouldn't be a
            // related Document entity.
        }

        $form = $this->get('form.factory')
            ->createBuilder(new AccessLogFormType(), $access_log)
            ->getForm();

        $form->bindRequest($request);

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

    } else {
        // ... (handle get request)
    }

    return $this->render('access_log_form.tpl', array(
        'form' => $form->createView()
    ));
}

Приведенный выше код работает нормально при обновлении существующей записи в журнале доступа или создании новой, и документ выбирается в форме, но не, если документ не выбран.

Если предположить, что модель данных не может быть изменена, как я могупродолжить сохранение новой сущности AccessLog без сохранения новой сущности Document?

Ответы [ 5 ]

3 голосов
/ 13 октября 2011

Похоже, что решение было в том, чтобы переместить вызов set_document (null) вправо перед операцией persist, поскольку привязка запроса к форме привела к присоединению пустого объекта Document к свойству $ document объекта AccessLog.

Это решение также работает после удаления каскада и сохранения однонаправленной ассоциации.

Спасибо @ greg0ire за помощь.

[update] Также стало необходимым добавить @ChangeTrackingPolicy("DEFERRED_EXPLICIT") к определению сущности Document, поскольку он продолжал пытаться обновить запись Document, связанную с записью AccessLog, например, при удалении существующей ассоциации (которая вызвала свойство $ name быть установленным равным нулю в Документе и затем в БД). Действительно разочаровывает, что поведение по умолчанию заключается в удалении / изменении данных при удалении ассоциации, даже если каскадное сохранение не задано.

2 голосов
/ 29 февраля 2012

Я думаю, что ваша проблема может быть в том, как вы определили свой установщик документов в AccessLog.

, если вы сделаете это так:1007 *, поскольку ноль не является экземпляром Document.Для этого

setDocument($document) or setDocument(Document $docuement = null)
1 голос
/ 23 мая 2012

Это было исправлено в мастере Symfony2.Пустые формы больше не приводят к пустым объектам, а вместо этого возвращают ноль.

0 голосов
/ 19 апреля 2012

В вашем AccessLogFormType вы должны изменить

$builder->add('document', new DocumentType(), array(
            'label' => 'Document',
            'required' => false
        ));

на:

$builder->add('document', new DocumentType(), array(
            'label' => 'Document',
            'required' => false,
            'empty_value' => '', 
            'empty_data' => null
        ));

Это позволит вам сохранить вашу сущность без настройки документа.

0 голосов
/ 13 октября 2011

Читали ли вы страницу документации Doctrine2 о транзитивной персистентности / каскадных операциях ? Я думаю, что если документ существует, сначала вам нужно remove, а если нет, то вам просто не следует звонить setDocument(). Чтобы избежать второй ошибки, в § объясняется, как можно настроить каскад (см. Последний фрагмент кода).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...