Клонирование объекта в Symfony2 сохраняет изменения в оригинальной записи и клонированной записи при сохранении через Doctrine

У меня есть форма, которая позволяет мне сохранять запись или дублировать ее. Форма сохраняет запись как объект $view, который, случается, имеет несколько связанных объектов, например. $viewVersion, которые управляются компоновщиком форм в formType с вложенными объектами (это, вероятно, не имеет значения).

Если я вношу изменения и передаю форму для "дублирования", код клонирует объект $view с функцией на моей сущности, которая отключает $view->id и другие ассоциации. Это заставляет Doctrine создавать новую запись, когда она сохраняет запись в базе данных. Это прекрасно работает. Ура!

НО, изменения, внесенные в запись, также сохраняются в исходном объекте, который был клонирован (и, следовательно, сохранен в базе данных). Таким образом, он сохраняет эти изменения в ДВА записей базы данных. Мне нравится эта функциональность, но мне нужно понять, ПОЧЕМУ она делает это, чтобы она не прерывалась позже. Вот краткие сводки кода:

// File: CmsBundle/Controller/AdminEditController.php

// Get the Entity Manager
$em = $this->getDoctrine()->getManager();

// Get the View based on the requested ID
// Is there some magic that happens here to make the entity manager track this $view entity?
$view = $em->getRepository("GutensiteCmsBundle:View\View")->find($request->query->get('id'));

// Various bits of code to do whatever I want before a save
// ...

if ($request->isMethod( 'POST' )) {
    $form->handleRequest($request);
    if( $form->isValid() ) {
        // Duplicate the view entity if the view button is pushed
        if(
            $form->has('duplicate') 
            && $form->get('duplicate')->isClicked()
        ) {
            $view = clone $view;
        }

        // Persist the cloned view
        $em->persist($view);
        $em->flush();
    }
}

Объект View имеет специальную функцию клонирования, которая запускается на клоне, которая сбрасывает идентификаторы клонированных версий:

// File: CmsBundle/Entity/View.php

public function __clone() {
    if($this->id) {
    $this->setId(null);
    $this->setLockVersion(1);
    $this->setPublished(null);

    // Clone associated entities and reassociate with THIS version (even though there is no id yet, there will be when it persists)
    // clone the current version (which also has a clone function like this)
    $version = clone $this->getVersion();
    // reset the viewid with a custom function
    $version->resetView();
    // Add this cloned verion to the version history
    $this->addVersion($version);
}

Я много читал о клонировании, и мне постоянно говорят, что вам не нужно detach оригинал $view от диспетчера объектов. Кроме того, я пробовал, и это не принесло пользы. Изменения в представлении $view, которые были отправлены формой и обработаны до $view до клонирования, по-прежнему сохраняются в исходном идентификаторе записи $view (например, 33), а также в новой клонированной записи (например, 62), Таким образом, сохраняются два состояния, хотя один объект сохраняется только для одного объекта.

Что происходит?

Update

Мне сказали, что если вы загружаете объект с менеджером сущностей, он отслеживается менеджером сущности. Поэтому, если вы вызовете flush() в любое время, любые изменения будут сохранены, даже если вы не вызывали persist($view) в сущности. Поэтому, когда я клонирую сущность, менеджер сущности эффективно управляет двумя объектами: оригиналом и клоном.

Я попытался отделить представление от менеджера сущности до клонирования двумя способами:

// detach method 1
$em->detach($view); 
$em->flush();

// detach method 2
$em->refresh($view); 
$em->flush();

// clone the view after detaching the first entity.
$view = clone $view;

Но менеджер объектов по-прежнему сохраняет изменения в исходной записи $view.

Я также попытался добавить unset($this->_entityPersister, $this->_identifier); в свой пользовательский метод __clone(). Но это также не отделяло исходный объект или клонированную версию от менеджера сущности. Изменения были сохранены как в старой записи, так и в новой записи.

Ничто не может заставить диспетчера объектов игнорировать исходный объект.

Полезные ссылки

Ответ 1

Persist нужен только тогда, когда вы прикрепляете что-то к своему Entity Manager. Но в вашем случае оригинал "$view записывает id (например, 33)" уже в нем. Итак, в основном, что происходит:

$view1 = new View();
$view1->text = '1';
$em->persist($view1);
$em->flush();

Теперь у вас есть одна запись с текстом == '1'. Тогда:

$view1->text = 'one'; //important!

$view2 = new View();
$view2->text = 'two';

$view3 = new View();
$view3->text = 'three';

$em->persist($view2);
$em->flush();

Вызов flush() обновлений вставки вставки <<27 > и игнорирует ваш $view3, поскольку последний не сохранялся. В результате у вас есть две записи: "одна" и "две".

Можно вызвать flush() для выбранных объектов. Поэтому вызов $em->flush($view2) будет только вставлять $view2 и оставить $view1 нетронутым.

В вашем простом примере это сработает.

Но убедитесь, что $em->flush() больше не будет.

В противном случае, чтобы ваш $view1 остался без изменений, попробуйте $em->refresh($view1) его.