用Doctrine 2检查重复的键

有一个简单的方法来检查重复密钥与教条2之前做一个刷新?

您可以像这样捕获UniqueConstraintViolationException

 use Doctrine\DBAL\Exception\UniqueConstraintViolationException; // ... try { // ... $em->flush(); } catch (UniqueConstraintViolationException $e) { // .... } 

我使用这个策略来检查flush()后的唯一约束,可能不是你想要的,但可能会帮助其他人。


当你调用flush()时 ,如果一个唯一的约束失败,抛出一个PDOException ,代码为23000

 try { // ... $em->flush(); } catch( \PDOException $e ) { if( $e->getCode() === '23000' ) { echo $e->getMessage(); // Will output an SQLSTATE[23000] message, similar to: // Integrity constraint violation: 1062 Duplicate entry 'x' // ... for key 'UNIQ_BB4A8E30E7927C74' } else throw $e; } 

如果您需要获取失败列的名称

创build具有前缀名称的表索引,例如。 '独特_'

  * @Entity * @Table(name="table_name", * uniqueConstraints={ * @UniqueConstraint(name="unique_name",columns={"name"}), * @UniqueConstraint(name="unique_email",columns={"email"}) * }) 

请勿在@Column定义中将您的列指定为唯一

这似乎用一个随机的重写索引名称…

  **ie.** Do not have 'unique=true' in your @Column definition 

重新生成表格后(可能需要删除并重build),您应该能够从exception消息中提取列名称。

 // ... if( $e->getCode() === '23000' ) { if( \preg_match( "%key 'unique_(?P<key>.+)'%", $e->getMessage(), $match ) ) { echo 'Unique constraint failed for key "' . $match[ 'key' ] . '"'; } else throw $e; } else throw $e; 

不完美,但它的作品…

如果在插入之前运行SELECT查询不是您想要的,那么只能运行flush()并捕获exception。

在Doctrine DBAL 2.3中,您可以通过查看由Doctrine DBALException包装的PDOexception错误代码(23000 for MySQL,23050 for Postgres)来安全地理解唯一的约束错误:

  try { $em->flush($user); } catch (\Doctrine\DBAL\DBALException $e) { if ($e->getPrevious() && 0 === strpos($e->getPrevious()->getCode(), '23')) { throw new YourCustomException(); } } 

前段时间我也遇到过这个问题。 主要问题不是特定于数据库的exception,但是当抛出PDOException时,EntityManager被closures。 这意味着你不能确定你想要刷新的数据会发生什么。 但可能它不会保存在数据库中,因为我认为这是在一个事务中完成的。

所以当我想到这个问题的时候,我想出了这个解决scheme,但是我没有时间写下来。

  1. 这可以使用事件监听器来完成,特别是onFlush事件。 在数据发送到数据库之前调用此事件(在计算变更集之后 – 因此您已经知道哪些实体已更改)。
  2. 在这个事件监听器中,你将不得不浏览所有被修改的实体的键值(对于主键来说,它将在@Id的类元数据中查找)。
  3. 那么你将不得不使用你的密钥标准的查找方法。 如果你能find一个结果,你有机会抛出自己的exception,这将不会closuresEntityManager,你可以在你的模型中捕获它,并在再次尝试刷新之前对数据进行一些更正。

这个解决scheme的问题是,它可能会产生相当多的数据库查询,所以这将需要相当多的优化。 如果你只想在很less的地方使用这样的东西,我build议你检查重复的地方。 所以例如你想创build一个实体并保存它:

 $user = new User('login'); $presentUsers = $em->getRepository('MyProject\Domain\User')->findBy(array('login' => 'login')); if (count($presentUsers)>0) { // this login is already taken (throw exception) } 

如果您使用的是Symfony2,则可以使用UniqueEntity(…)和form->isValid()在flush()之前捕获重复项。

我在这里发布这个答案,但它似乎很有价值,因为很多教条用户也将使用Symfony2。 要清楚的是:这使用Symfony的validation类,它是在使用实体存储库检查(可configuration,但默认为findBy )。

在您的实体上,您可以添加注释:

 use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** * @UniqueEntity("email") */ class YourEntity { 

然后在您的控制器中,将请求交给表单后,您可以检查您的validation。

 $form->handleRequest($request); if ( ! $form->isValid()) { if ($email_errors = $form['email']->getErrors()) { foreach($email_errors as $error) { // all validation errors related to email } } … 

我build议把这个和Peter的答案结合起来,因为你的数据库模式也应该强制唯一:

 /** * @UniqueEntity('email') * @Orm\Entity() * @Orm\Table(name="table_name", * uniqueConstraints={ * @UniqueConstraint(name="unique_email",columns={"email"}) * }) */ 

如果你只是想捕获重复的错误。 你不应该只是检查代码号码

 $e->getCode() === '23000' 

因为这会捕获其他错误,如字段'用户'不能为空。 我的解决scheme是检查错误消息,如果它包含文本'重复条目'

  try { $em->flush(); } catch (\Doctrine\DBAL\DBALException $e) { if (is_int(strpos($e->getPrevious()->getMessage(), 'Duplicate entry'))) { $error = 'The name of the site must be a unique name!'; } else { //.... } } 

在Symfony 2中,它实际上抛出了一个\ Exception,而不是一个\ PDOException

 try { // ... $em->flush(); } catch( \Exception $e ) { echo $e->getMessage(); echo $e->getCode(); //shows '0' ### handle ### } 

$ e-> getMessage()回声如下所示:

使用params […]执行'INSERT INTO(…)VALUES(?,?,?,?)'时发生exception:

SQLSTATE [23000]:完整性约束违规:1062键“PRIMARY”的重复条目“…”

我想补充一点,关于PDOException –

23000错误代码是MySQL可以返回的一系列完整性约束违规的一揽子代码。

因此,处理23000错误代码对于某些用例来说不够具体。

例如,您可能希望对重复的logging冲突做出不同的反应,而不是缺less外键冲突。

这是一个如何处理这个问题的例子:

 try { $pdo -> executeDoomedToFailQuery(); } catch(\PDOException $e) { // log the actual exception here $code = PDOCode::get($e); // Decide what to do next based on meaningful MySQL code } // ... The PDOCode::get function public static function get(\PDOException $e) { $message = $e -> getMessage(); $matches = array(); $code = preg_match('/ (\d\d\d\d) / ', $message, $matches); return $code; } 

我意识到这并不像问题所要求的那么详细,但是我发现这在许多情况下是非常有用的,而不是Doctrine2特定的。

最简单的方法应该是这样的:

 $product = $entityManager->getRepository("\Api\Product\Entity\Product")->findBy(array('productName' => $data['product_name'])); if(!empty($product)){ // duplicate } 

我用这个,它似乎工作。 它返回特定的MySQL错误号 – 即1062重复的条目 – 准备好让你处理你喜欢的方式。

 try { $em->flush(); } catch(\PDOException $e) { $code = $e->errorInfo[1]; // Do stuff with error code echo $code; } 

我testing了一些其他情况,它会返回其他代码,如1146(表不存在)和1054(未知列)。