Почему Doctrine пытается дублировать связь «многие-ко-многим», даже если я заранее проверяю, существует ли она? И почемуPhp

Кемеровские программисты php общаются здесь
Ответить Пред. темаСлед. тема
Anonymous
 Почему Doctrine пытается дублировать связь «многие-ко-многим», даже если я заранее проверяю, существует ли она? И почему

Сообщение Anonymous »

Мне очень тяжело из-за того, что Doctrine не работает должным образом.

Что пытается сделать мой код.

Я пишу команду CLI в своем веб-приложении Symfony 3, которая должна привести в порядок таблицу тегов в моей базе данных. Есть Актеры и есть Теги. Между актерами и тегами существует связь «многие-ко-многим» (двунаправленная). Моя команда импортирует файл CSV, в котором в одном столбце перечислены текущие теги, а в другом столбце есть некоторые их заменители. Он проходит по файлу, строка за строкой, находит существующий тег, считывает все его текущие отношения с актерами, удаляет тег, создает новый тег (замену) или использует существующий и присоединяет к нему все отношения актера удалил один.

Код (его важная часть)

Код: Выделить всё

protected function doReplace(InputInterface $input, OutputInterface $output, $input_file)
{
$em = $this->getContainer()->get('doctrine')->getManager();
$con = $em->getConnection();

//open the input CSV
$input_fhndl = fopen($input_file, 'r');
if (!$input_fhndl)
throw new \Exception('Unable to open file!');

//do everything in a big transaction, so that if anything fails
//everything rolls back and there's no half-finished information
//in the DB
$con->beginTransaction();
try
{
//I was trying to use official Doctrine recommendation for batch inserts
//to clear the entity manager after a bunch of operations,
//but it does neither help nor make things worse
//      $batchSize = 20;
$i = 0;
//reading the file line by line
while (($line = fgetcsv($input_fhndl)) !== false)
{
//$line[0] - source tag ID (the one to be substituted)
//$line[1] - source tag type ('language' or 'skill')
//$line[2] - source tag value (e.g. 'pole dancing (advanced)')
//$line[3] - replacement tag value (e.g. 'pole dancing')

$i++;

if ($i === 1) //omit table headers
continue;

$line[3] = trim($line[3]);
if ($line[3] === null || $line[3] === '') //omit lines with no replacements
continue;

//getting the tag to be replaced
$src_tag = $em->getRepository('AppBundle:Tag')
->find($line[0]);
if (!$src_tag)
{
//if the tag that is supposed to be replaced doesn't exist, just skip it
continue;
}

$replacement_tag = null;
$skip = false;

//if the replacement value is '!' they just want to delete the original
//tag without replacing it
if (trim($line[3]) === '!')
{
$output->writeln('Removing '.$line[2].' ');
}
//here comes the proper replacement
else
{
//there can be a few replacement values for one source tag
//in such case they're separated with | in the input file
$replacements = explode('|', $line[3]);

foreach ($replacements as $replacement)
{
$skip = false;
$output->write('Replacing '.$line[2].' with '.trim($replacement).'.  ');

//getOrCreateTag looks for a tag with the same type and value as the replacement
//if it finds one, it retrieves the entity, if it doesn't it creates a new one
$replacement_tag = $this->getOrCreateTag($em, $src_tag->getTagType(), trim($replacement), $output);
if ($replacement_tag === $src_tag) //delete the original tag only if it is different from the replacement
{
$skip = true;
}
else
{
//we iterate through deleted Tag's relationships with Actors
foreach ($src_tag->getActors() as $actor)
{
//this part used to be the many-to-many fail point but i managed to fix it by removing indexBy: id line from Actor->Tag relation definition
if (!$replacement_tag->getActors() || !$replacement_tag->getActors()->contains($actor))
$replacement_tag->addActor ($actor);
}
$em->persist($replacement_tag);
//...and if I uncomment this flush()
//I get errors like Notice: Undefined index: 000000005f12fa20000000000088a5f2
//from Doctrine internals
//even though it should be harmless
//                      $em->flush();
}
}
}

if (!$skip) //delete the original tag only if it is different from the replacement
{
$em->remove($src_tag);
$em->flush(); //this flush both deletes the original tag and sets up the new one
//with its relations
}

//          if (($i % $batchSize) === 0) {
//              $em->flush(); // Executes all updates.
//              $em->clear(); // Detaches all objects from Doctrine!
//          }
}
$em->flush(); //one final flush just in case
$con->commit();
}
catch (\Exception $e)
{
$output->writeln(' Something went wrong! Rolling back... ');
$con->rollback();
throw $e;
}

//closing the input file
fclose($input_fhndl);
}

protected function getOrCreateTag($em, $tag_type, $value, $output)
{
$value = trim($value);

$replacement_tag = $em
->createQuery('SELECT t FROM AppBundle:Tag t WHERE t.tagType = :tagType AND t.value = :value')
->setParameter('tagType', $tag_type)
->setParameter('value', $value)
->getOneOrNullResult();

if (!$replacement_tag)
{
$replacement_tag = new Tag();
$replacement_tag->setTagType($tag_type);
$replacement_tag->setValue($value);
$output->writeln('Creating new.');
}
else
{
$output->writeln('Using existing.');
}

return $replacement_tag;
}
Как это не работает

Хотя я выполняю эту проверку: $replacement_tag->getActors()->contains($actor) Doctrine пытается создать дубликат отношения Actor-Tag:

Код: Выделить всё

[Doctrine\DBAL\Exception\UniqueConstraintViolationException]
An exception occurred while executing 'INSERT INTO actor_tags (actor_id, tag_id) VALUES (?, ?)' with params [280, 708]:
SQLSTATE[23505]: Unique violation: 7 ERROR:  duplicate key value violates unique constraint "actor_tags_pkey"
DETAIL:  Key (actor_id, tag_id)=(280, 708) already exists.


Мне удалось исправить это, удалив indexBy: id из Определение отношения Актер->Тег (оно появилось случайно).

И кроме того, когда я делаю некоторые теоретически безобидные изменения, например, раскомментирую закомментированный вызовlush() или не использую большую транзакцию, я получаю это

Тем не менее, даже без каких-либо изменений в коде, в какой-то момент импорта я получаю следующее:

Код: Выделить всё

  [Symfony\Component\Debug\Exception\ContextErrorException]
Notice: Undefined index: 000000001091cbbe000000000b4818c6

Exception trace:
() at /src/__sources/atm/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:2907
Doctrine\ORM\UnitOfWork->getEntityIdentifier() at /src/__sources/atm/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/Collection/ManyToManyPersister.php:543
Doctrine\ORM\Persisters\Collection\ManyToManyPersister->collectJoinTableColumnParameters() at /src/__sources/atm/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/Collection/ManyToManyPersister.php:473
Doctrine\ORM\Persisters\Collection\ManyToManyPersister->getDeleteRowSQLParameters() at /src/__sources/atm/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/Collection/ManyToManyPersister.php:77
Doctrine\ORM\Persisters\Collection\ManyToManyPersister->update() at /src/__sources/atm/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:388
Doctrine\ORM\UnitOfWork->commit() at /src/__sources/atm/vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php:359
Doctrine\ORM\EntityManager->flush() at /src/__sources/atm/src/AppBundle/Command/AtmReplaceTagsCommand.php:176
AppBundle\Command\AtmReplaceTagsCommand->doReplace() at /src/__sources/atm/src/AppBundle/Command/AtmReplaceTagsCommand.php:60
AppBundle\Command\AtmReplaceTagsCommand->execute() at /src/__sources/atm/vendor/symfony/symfony/src/Symfony/Component/Console/Command/Command.php:262
Symfony\Component\Console\Command\Command->run() at /src/__sources/atm/vendor/symfony/symfony/src/Symfony/Component/Console/Application.php:848
Symfony\Component\Console\Application->doRunCommand() at /src/__sources/atm/vendor/symfony/symfony/src/Symfony/Component/Console/Application.php:190
Symfony\Component\Console\Application->doRun() at /src/__sources/atm/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Console/Application.php:80
Symfony\Bundle\FrameworkBundle\Console\Application->doRun() at /src/__sources/atm/vendor/symfony/symfony/src/Symfony/Component/Console/Application.php:121
Symfony\Component\Console\Application->run() at /src/__sources/atm/bin/console:28
Выполнение $em->clear() каждые несколько строк не помогает.

Что я пробовал
  • Я пытался изменить последовательность вызововlush(), что часто приводило к такому результату странная неопределенная ошибка индекса.
  • Я пытался закомментировать большую транзакцию (безрезультатно).
  • Я пытался вызвать $em->clear( ) после каждых 20 записей - это тоже ничего не изменило.
Буду очень признателен за любую помощь это.

Дополнительная информация

Определение YAML отношения Актер->Тег ( для объекта-актера):

Код: Выделить всё

 manyToMany:
tags:
targetEntity: AppBundle\Entity\Tag
inversedBy: actors
#indexBy: id
#the above line caused the Many-To-Many duplicate problem - commenting it out fixed that part of the problem.
joinTable:
name: actor_tags
joinColumns:
actor_id:
referencedColumnName: id
inverseJoinColumns:
tag_id:
referencedColumnName: id
Определение связи Тег->Актер в формате YAML (для объекта Тег):

Код: Выделить всё

  manyToMany:
actors:
targetEntity: AppBundle\Entity\Actor
mappedBy: tags

Код: Выделить всё

Tag::addActor()
определение функции

Код: Выделить всё

public function addActor(\AppBundle\Entity\Actor $actor)
{
$this->actor[] = $actor;
$actor->addTag($this);

return $this;
}

Код: Выделить всё

Actor::addTag()
определение функции

Код: Выделить всё

public function addTag(\AppBundle\Entity\Tag $tag)
{
$this->tags[] = $tag;

$this->serializeTagIds();

return $this;
}
Если вам нужна дополнительная информация, просто спросите. Спасибо большое.

Подробнее здесь: https://stackoverflow.com/questions/446 ... ough-i-che
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение

Вернуться в «Php»