Itโs really hard for me when Doctrine is not working properly.
What my code is trying to do.
I am writing a CLI command in my Symfony 3 web application, which should clean up the tag table in my database. There are actors and tags. There is a many-to-many relationship between participants and tags (bidirectional). My team imports a CSV file in which tags are listed in one column and some replacements for them in another column. It goes through the file, takes turns, finds the existing tag, reads all current relationships with the Actors, removes the tag, creates a new tag (replaces) or uses the existing one, and attaches all the deleted relationships to it to it.
Code (its key part)
protected function doReplace(InputInterface $input, OutputInterface $output, $input_file)
{
$em = $this->getContainer()->get('doctrine')->getManager();
$con = $em->getConnection();
$input_fhndl = fopen($input_file, 'r');
if (!$input_fhndl)
throw new \Exception('Unable to open file!');
$con->beginTransaction();
try
{
$i = 0;
while (($line = fgetcsv($input_fhndl)) !== false)
{
$i++;
if ($i === 1)
continue;
$line[3] = trim($line[3]);
if ($line[3] === null || $line[3] === '')
continue;
$src_tag = $em->getRepository('AppBundle:Tag')
->find($line[0]);
if (!$src_tag)
{
continue;
}
$replacement_tag = null;
$skip = false;
if (trim($line[3]) === '!')
{
$output->writeln('Removing '.$line[2].' ');
}
else
{
$replacements = explode('|', $line[3]);
foreach ($replacements as $replacement)
{
$skip = false;
$output->write('Replacing '.$line[2].' with '.trim($replacement).'. ');
$replacement_tag = $this->getOrCreateTag($em, $src_tag->getTagType(), trim($replacement), $output);
if ($replacement_tag === $src_tag)
{
$skip = true;
}
else
{
foreach ($src_tag->getActors() as $actor)
{
if (!$replacement_tag->getActors() || !$replacement_tag->getActors()->contains($actor))
$replacement_tag->addActor ($actor);
}
$em->persist($replacement_tag);
}
}
}
if (!$skip)
{
$em->remove($src_tag);
$em->flush();
}
}
$em->flush();
$con->commit();
}
catch (\Exception $e)
{
$output->writeln('<error> Something went wrong! Rolling back... </error>');
$con->rollback();
throw $e;
}
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;
}
How it fails
Even if I do this: $replacement_tag->getActors()->contains($actor)Doctrine tries to create a duplicate Actor-Tag relation:
[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.
Strike>
I managed to fix this by removing indexBy: idActor-> Tag from the definition of the relation (it was random).
, , flush() ,
- , - :
[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() .
flush(), undefined.- ().
$em->clear() 20 - .
.
YAML Actor- > Tag ( Actor):
manyToMany:
tags:
targetEntity: AppBundle\Entity\Tag
inversedBy: actors
joinTable:
name: actor_tags
joinColumns:
actor_id:
referencedColumnName: id
inverseJoinColumns:
tag_id:
referencedColumnName: id
YAML Tag- > Actor ( Tag):
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;
}
, . .