without unidirectional one manytoone many foreign doctrine doctrine2

unidirectional - doctrine orm mapping



ComprobaciĆ³n de claves duplicadas con Doctrine 2 (10)

En Symfony 2, arroja una / Exception, no una / PDOException

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

$ e-> getMessage () echos algo como lo siguiente:

Se produjo una excepción al ejecutar ''INSERT INTO (...) VALUES (?,?,?,?)'' Con params [...]:

SQLSTATE [23000]: Violación de restricción de integridad: 1062 Entrada duplicada ''...'' para clave ''PRIMARY''

¿Hay alguna manera fácil de verificar claves duplicadas con Doctrine 2 antes de hacer un color?


La manera más fácil debería ser esto:

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


Me gustaría agregar a esto específicamente con respecto a PDOExceptions--

El código de error 23000 es un código general para una familia de violaciones de restricción de integridad que MySQL puede devolver.

Por lo tanto, manejar el código de error 23000 no es lo suficientemente específico para algunos casos de uso.

Por ejemplo, es posible que desee reaccionar de manera diferente a una violación de registro duplicada que a una violación de clave externa faltante.

Aquí hay un ejemplo de cómo lidiar con esto:

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; }

Me doy cuenta de que esto no es tan detallado como la pregunta, pero me parece que es muy útil en muchos casos y no es específico de Doctrine2.


Me he encontrado con este problema hace un tiempo, también. El principal problema no son las excepciones específicas de la base de datos, sino el hecho de que cuando se lanza una excepción PDOException se cierra el EntityManager. Eso significa que no puede estar seguro de qué sucederá con los datos que desea vaciar. Pero probablemente no se guarde en la base de datos porque creo que esto se hace dentro de una transacción.

Entonces, cuando estaba pensando en este problema, se me ocurrió esta solución, pero no tuve tiempo de escribirla realmente.

  1. Se podría hacer usando oyentes de eventos , particularmente el evento onFlush. Este evento se invoca antes de que los datos se envíen a la base de datos (después de que se hayan calculado los conjuntos de cambios, para que ya sepas qué entidades se cambiaron).
  2. En este caso, el oyente tendría que buscar todas las entidades modificadas para sus claves (para las primarias buscaría en los metadatos de la clase para @Id).
  3. Entonces, debería usar un método de búsqueda con los criterios de sus claves. Si encuentra un resultado, tiene la posibilidad de lanzar su propia excepción, que no cerrará el EntityManager y podrá verlo en su modelo y hacer algunas correcciones a los datos antes de volver a intentar el color.

El problema con esta solución sería que podría generar bastantes consultas a la base de datos, por lo que requeriría una gran cantidad de optimización. Si desea usar tal cosa solo en algunos lugares, le recomiendo verificar el lugar donde podría aparecer el duplicado. Entonces, por ejemplo, donde desea crear una entidad y guardarla:

$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) }


Si ejecutar una consulta SELECT antes de la inserción no es lo que desea, solo puede ejecutar flush () y capturar la excepción.

En Doctrine DBAL 2.3, puede comprender con seguridad un error de restricción único mirando el código de error de excepción de PDO (23000 para MySQL, 23050 para Postgres), que está incluido en Doctrine DBALException:

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


Si está utilizando Symfony2, puede usar UniqueEntity(…) con form->isValid() para capturar duplicados antes de flush ().

Estoy en la valla publicando esta respuesta aquí, pero parece valiosa ya que muchos usuarios de Doctrine también usarán Symfony2. Para ser claros: esto usa la clase de validaciones de Symfony que bajo el capó está usando un repositorio de entidades para verificar (es configurable pero predeterminado para findBy ).

En su entidad puede agregar la anotación:

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

Luego, en su controlador, después de entregar la solicitud al formulario, puede verificar sus validaciones.

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

Recomiendo combinar esto con la respuesta de Peter, ya que el esquema de su base de datos también debe hacer cumplir la singularidad:

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


Si solo quieres atrapar errores duplicados. No deberías simplemente verificar el número de código

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

porque esto captará otros errores, como el campo ''usuario'' no puede estar vacío. Mi solución es verificar el mensaje de error, si contiene el texto ''Entrada duplicada''

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 { //.... } }


Usé esto y parece funcionar. Devuelve el número de error específico de MySQL, es decir, 1062 para una entrada duplicada, listo para que lo maneje como lo desee.

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

Probé esto con algunos otros escenarios y devolverá otros códigos también, como 1146 (la tabla no existe) y 1054 (columna desconocida).


Uso esta estrategia para verificar las restricciones únicas después de flush () , puede que no sea lo que quieres, pero podría ayudar a alguien más.

Cuando llamas a flush () , si falla una restricción única, se lanza una excepción PDO con el código 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; }

Si necesita obtener el nombre de la columna anómala :

Crear índices de tablas con nombres prefijados, ej. ''único_''

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

NO especifique sus columnas como únicas en la definición @Column

Esto parece anular el nombre del índice con uno aleatorio ...

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

Después de volver a generar su tabla (puede que tenga que soltarla y reconstruirla), debería poder extraer el nombre de la columna del mensaje de excepción.

// ... 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;

No es perfecto, pero funciona ...


Puede atrapar la UniqueConstraintViolationException como tal:

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