Как правильно обновить управляемый объект, используя данные из неуправляемого объекта?

Моя цель — выполнить обновление управляемого объекта, используя данные из объекта того же класса, но не управляемого Doctrine.

Это было бы здорово, если бы можно было выполнить «простое обновление» при замене атрибутов, но на самом деле, если я уберу ArrayCollectionстарые данные, кажется, не удаляются (даже если я очищаю все ссылки на скрипке от элементов ArrayCollection или если orphanRemoval установлено в true).

Но давайте введем конкретный пример. я имею эта сущность с множеством отношений OneToOne / OneToMany для представления играть на скрипке. Я могу импортировать образцы скрипки (ранее экспортированные как json из другой среды) с помощью команды Symfony2.

Если образец уже существует, как я могу его правильно обновить?

Плохая идея: делать DELETE + INSERT

Я строю свою сущность, используя следующий код (сокращенно):

$fiddle = new Fiddle();
$fiddle->setHash($this->get($json, 'hash'));
$fiddle->setRevision($this->get($json, 'revision'));

$context = $fiddle->getContext();
$context->setFormat($this->get($json, 'context', 'format'));
$context->setContent($this->get($json, 'context', 'content'));

$fiddle->clearTemplates();
$jsonTemplates = $this->get($json, 'templates') ? : array ();
foreach ($jsonTemplates as $jsonTemplate)
{
$template = new FiddleTemplate();
$template->setFilename($this->get($jsonTemplate, 'filename'));
$template->setContent($this->get($jsonTemplate, 'content'));
$template->setIsMain($this->get($jsonTemplate, 'is-main'));
$fiddle->addTemplate($template);
}

// ...

Теперь я могу сохранить свою сущность после удаления, если она уже существует:

    $check = $this
->getContainer()
->get('doctrine')
->getRepository('FuzAppBundle:Fiddle')
->getFiddle($fiddle->getHash(), $fiddle->getRevision());

if (!is_null($check->getId()))
{
$em->remove($check);
$em->flush();
}

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

Но это создаст DELETE + INSERT вместо UPDATE, если образец уже существует. Это странно, потому что пользователи могут ставить закладки на скрипты, а связь устанавливается по id.

Уродливая идея: сделать ОБНОВЛЕНИЕ для основных сущностей и отношений OneToOne и DELETE + INSERT для отношений OneToMany

Сначала я получаю свою скрипку, и если она уже существует, я очищаю ее и заполняю ее новыми данными … Код работает хорошо, но действительно ужасно, Вы можете проверить это Вот.

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

// remove the old tags
foreach ($fiddle->getTags() as $tag)
{
if (\Doctrine\ORM\UnitOfWork::STATE_MANAGED === $em->getUnitOfWork()->getEntityState($tag))
{
$em->remove($tag);
$em->flush();
}
}

// set the new tags
$tags = new ArrayCollection();
$jsonTags = $this->getFromArray($json, 'tags');
foreach ($jsonTags as $jsonTag)
{
$tag = new FiddleTag();
$tag->setTag($jsonTag);
$tags->add($tag);
}
$fiddle->setTags($tags);

Поскольку на теги ссылаются с помощью идентификатора fiddle, я могу использовать ->remove даже если это безобразно Это нормально, но если идентификаторы были сгенерированы автоматически, то должны быть лучшие решения.

Я также попытался просто установить идентификатор старой скрипки на новый и объединить, но это привело к следующему исключению:

[Symfony\Component\Debug\Exception\ContextErrorException]
Notice: Undefined index: 00000000125168f2000000014b64e87f

Больше, чем простая «функция импорта», я хочу использовать этот стиль обновления для привязки форм к неуправляемым объектам и обновления существующих объектов только при необходимости. Поэтому моя цель — сделать что-то общее, применимое ко всем видам сущностей.

Но я, конечно, не ожидаю весь код. Хорошая практика для борьбы с ArrayCollectionобновлений и некоторых подсказок / предупреждений о том, что я должен рассмотреть, прежде чем кодировать эту функцию, должно быть достаточно.

6

Решение

Управление тем, что Доктрина сохраняется

Обновлять существующие объекты только при необходимости.

Это может быть достигнуто довольно просто с Doctrine:

То, что вы ищете, это политика отслеживания изменений Отложенный Явный.

Доктрина по умолчанию будет использовать Политику отслеживания изменений Отложенный неявный. Это означает, что когда вы звоните $em->flush()Учение пройдет все его управляемых объектов для расчета наборов изменений. Тогда все изменения сохраняются.

При использовании Политики отслеживания изменений Отложенный Явный и позвонить $em->flush()Учение будет только просмотрите сущности, которые вы явно назвали $em->persist() на. Другими словами: вы можете иметь тысячи управляемых объектов, называемых $em->persist() на 2 из них, и Doctrine рассчитает только наборы изменений этих 2 (и сохранит изменения при необходимости).

Политика отслеживания изменений может быть установлена ​​на уровне класса объекта. Так что, если вы хотите использовать определенный класс сущностей Отложенный Явный, просто добавьте аннотацию к блоку doc класса:

/**
* @Entity
* @ChangeTrackingPolicy("DEFERRED_EXPLICIT")
*/
class Fiddle
{

Тогда это просто вопрос вызова $em->persist($fiddle) когда тебе действительно нужно.

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

PS: есть также третья политика отслеживания изменений, названная Поставить в известность, это немного больше работы для настройки, но дает вам еще более детальный контроль над тем, что сохраняется при вызове $em->flush(), Но я не думаю, что вам нужно заходить так далеко.

Обновление скрипки

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

Сначала перенесите ответственность за управление ассоциациями обратно на предприятие:

/**
* @Entity
* @ChangeTrackingPolicy("DEFERRED_EXPLICIT")
*/
class Fiddle
{
// ...

/**
* @return FiddleTag[]
*/
public function getTags()
{
return $this->tags->toArray();
}

/**
* @param FiddleTag $tag
*/
public function addTag(FiddleTag $tag)
{
if (!$this->tags->contains($tag)) {
$this->tags->add($tag);
$tag->setFiddle($this);
}
}

/**
* @param FiddleTag $tag
*/
public function removeTag(FiddleTag $tag)
{
if ($this->tags->contains($tag)) {
$this->tags->removeElement($tag);
$tag->setFiddle(null);
}
}

/**
* @param FiddleTag[] $newTags
*/
public function replaceTags(array $newTags)
{
$currentTags = $this->getTags();

// remove tags that are not in the new list of tags
foreach ($currentTags as $currentTag) {
if (!in_array($currentTag, $newTags, true)) {
$this->removeTag($currentTag);
}
}

// add tags that are not in the current list of tags
foreach ($newTags as $newTag) {
if (!in_array($newTag, $currentTags, true)) {
$this->addTag($newTag);
}
}
}

// ...
}

Теперь код в вашем ImportCommand может выглядеть примерно так:

$jsonTags = $this->getFromArray($json, 'tags');
$newTags  = [];

foreach ($jsonTags as $jsonTag) {
$tag = $tagRepo->findOneByTag($jsonTag);

if ($tag === null) {
$tag = new FiddleTag();
$tag->setTag($jsonTag);
}

$newTags[] = $tag;
}

$fiddle->replaceTags($newTags);

Затем, когда все в порядке и может быть сохранено, выполните:

$em->persist($fiddle);

foreach ($fiddle->getTags() as $tag) {
$em->persist($tag);
}

$em->flush();

Когда вы настроили cascade=persist в ассоциации вы должны быть в состоянии пропустить цикл, который вручную сохраняет теги.

Pro tip

Вы могли бы взглянуть на Сериализатор JMS библиотека, а сверток это интегрирует его в Symfony.

6

Другие решения

Других решений пока нет …

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector