3
votes

I am importing users from an excel file. I have created entity manager:

$em = $this->getDoctrine()->getManager();

This is my loop:

...
for ($i=2; $i<=count($usersdatas); $i++) { // $userdatas: an array of users imported from excel
    $nuser = new User();
    $nuser->setEmail($usersdatas[$i]['A']); // email is unique in db
    $nuser->setFname($usersdatas[$i]['B']);
    $nuser->setLname($usersdatas[$i]['C']);
    $nuser->addChannel($channel); //ManytoMany relation
    $em->persist($nuser);
    $em->flush();
}
...
  • Email field is unique in db and I do not want to check duplicate before flush (db validation)
  • I want to flush one by one for log reasons (not batch insert).
  • All my other codes and files (entity, services, configurations, ...) are correct and has no errors.

The first problem is if one of the rows has an email that exist in db (duplicate), the loop breaks by an exception from db. I have solved the problem with this simple change:

...
try {
    $em->persist($nuser);
    $em->flush();
} catch (\Exception $e) {
    ...
}
...

The next problem is the "The EntityManager is closed" exception. If one email is duplicate, EntityManager ($em) will close automatically. I have solved this by creating the $em before try/catch:

...
if (!$em->isOpen()) {
    $em = $em->create(
    $em->getConnection(), $em->getConfiguration());
}
...

Everything is OK. But I have a big problem with adding the channel to user:

...
$nuser->addChannel($channel); //ManytoMany relation
...

$channel is not a new one. But after closing and creating the EntityManager, the system thinks it is new on line persist (The $channel will successfully add if there is no errors):

ORMInvalidArgumentException {#1400 ▼
  #message: "A new entity was found through the relationship 'My\UserBundle\Entity\User#channels' that was not configured to cascade persist operations for entity: My\PostBundle\Entity\Channel@000000002768057d0000000046371762. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist  this association in the mapping for example @ManyToOne(..,cascade={"persist"}). If you cannot find out which entity causes the problem implement 'My\PostBundle\Entity\Channel#__toString()' to get a clue."
  #code: 0
  #file: "...\vendor\doctrine\orm\lib\Doctrine\ORM\ORMInvalidArgumentException.php"
  #line: 91
  -trace: array:13 [▶]
}

All my problems are from recreating EntityManager. I think its a bug. Validating data in database side (Unique, Null, Foreign Keys, ...) is common. Closing the EntityManager after db errors means we can not communicate with db after any error.

I have 2 questions:

  1. Is there any way to prevent EntityManager close?!
  2. Can I use DBAL Doctrine to import my users? (users may have duplicated emails)
1
Can you avoid the unique constraint on your users email by configuring the field mapping correctly? (i.e. dont' use "unique: true") I also think you should do a single flush, and find another solution for whatever your logging needs are (write with mono log on creation, use entity listener to write on flush, something like that). - Donald P
:) I have to set unique for email and i can not remove it. The single flush is better. But it has the same problem on any db errors. - Mahmood Feiz
The entity manager is designed to close up shop when an exception is found. The system is just not designed to recover from these sorts of issues. Use DBAL. Much faster and easier to recover from. - Cerad

1 Answers

3
votes

Exceptions are for exceptional situations. They're designed to let you clean up and then return a friendly error. They're not designed for validation.

How you're using them here is to say "Is it possible to add this user?" and if not, restart the database. That is, stop the entity manager manager from throwing exceptions, instead of trying to carry on after it has thrown an exception.

So if you've identified that the entity manager is throwing an exception and closing when you try to add a duplicate user, then check whether the user is a duplicate before trying to add them.

However, you said above that you don't want to use this approach. In that case, you will need to write your own SQL queries and can use DBAL (or just raw PDOs) and handle the SQL responses yourself.