You could use pessimistic locks in a transaction for validation AND have an index on the e-mail field: http://docs.doctrine-project.org/en/2.0.x/reference/transactions-and-concurrency.html In a serializable transaction, read queries lock index ranges explored by a SELECT, even false hits.
It's a bit more complicated than just dealing with the exception; also note that you are taking a bit of a responsability on your hands: as your model gets increasingly complicated ACID exceptions are bound to occur, unless you use a really aggressive locking strategy; you'd need to validate and version (see optimistic locking) absolutely every piece of data that's about to get written (including relations etc), and to be able to do that is kind of a birth gift for complicated models. However, since such conditions are remote, I use a generic handle that just re-submits the request. This way, you don't have to parse the exception to know what really happened: on the second pass, the duplicate email address will pe picked up in the validation (read) phase and an appropriate message will be displayed tothe user. This can be done either automatically or manually by the user (in case some exception occurs, prompt him a generic message kindly asking him to try again). On Friday, December 20, 2013 4:35:20 PM UTC+2, Adam Zielinski wrote: > > I have a entity called Contact with a single unique field email. Before > persisting, I am validating values to make sure my unique constraint won't > be violated. > > Let's assume I want to persist a Contact with an email > [email protected] <javascript:>, of course it works. Then I try again > and bam, validation kicked in, I know the insert is not going to work, and > I can show nice error message right next to my "email" field. Perfect! > > Now I want to add another contact, this time with an email > [email protected] <javascript:>, but oops, someone decided to add > the same email in exactly the same moment as me. That's okay, it happens. > Both requests are processed like this: > > | My Request | Other Request-+-----------------+-----------------1| > Validation | Validation 2| $em->flush() | $em->flush() > > In both cases validation passed since the Contact entity with such email > wasn't in database yet. This leads to two Insert queries sent with the > same email. MySQL will prevent the second one, so Doctrine will throw an > exception, user will see error 500 instead of "Email has been already > taken". Documentation says I should avoid database exceptions at all cost, > but this time database exception is perfectly okay, there is no way I can > tell if this insert will succeed other than performing an insert. > > My question is: *What is the Doctrine Way **™ to **recover from that > exception **elegantly**?* I just want to tell the user that he have to > type in different email address *(and I don't want to set an exclusive > lock for the entire table).* > > I could of course do something like this: > > try { > $em->flush();} catch (DBALException $e) { > $pdoException = $e->getPrevious(); > if ($pdoException && > $pdoException instanceof PDOException && > $pdoException->getCode() === '23000' > ) {// let the form know about the error > } else throw $e;} > > But that's wrong, requires copy-pasting the code each time I have to deal > with unique constraints, and is trouble in case there is more than one > unique index. *Is there any built-in component that will parse the > MySQL/PostgreSQL/whatever error message and tell me "hey you violated this > and this constraint"?* > -- You received this message because you are subscribed to the Google Groups "doctrine-user" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To post to this group, send email to [email protected]. Visit this group at http://groups.google.com/group/doctrine-user. For more options, visit https://groups.google.com/groups/opt_out.
