2010-10-19 21 views
43

¿Hay alguna manera fácil de verificar si hay llaves duplicadas con Doctrine 2 antes de hacer una descarga?Comprobación de llaves duplicadas con Doctrine 2

+1

realmente no tienen una respuesta, pero me pregunto cómo comprobar antes de un color que es diferente de hacer el rubor y la manipulación del error (suponiendo una clave duplicada existe). –

+0

En un color se lanzarán excepciones específicas de la base de datos. – tom

+3

La mayoría de las soluciones presentadas aquí no tienen en cuenta el hecho de que * no * puede verificar los duplicados de antemano, porque esa no es una operación atómica y, por lo tanto, * todavía * puede tener valores duplicados, si otras inserciones de hilo en la mesa, por ejemplo. Entonces, las únicas soluciones posibles en mi mente son manejar la falla de forma manual o usar el bloqueo. El primero es bastante feo con Doctrine (ya que el EM se cierra), el último puede tener consecuencias nefastas para el rendimiento, si no tienes cuidado. Me gustaría ver una buena respuesta a esto yo mismo. –

Respuesta

32

se puede coger el UniqueConstraintViolationException como tal:

use Doctrine\DBAL\Exception\UniqueConstraintViolationException; 

// ... 

try { 
    // ... 
    $em->flush(); 
} 
catch (UniqueConstraintViolationException $e) { 
    // .... 
} 
+0

Esto se ha agregado en 2014. Esta debería ser la forma de hacerlo ahora. – tom

+0

Esto ha estado disponible desde que Doctrine DBAL 2.5 - UniqueConstraintViolationException hereda de ConstraintViolationException, consulte: https://github.com/doctrine/dbal/blob/master/lib/Doctrine/DBAL/Exception/ConstraintViolationException.php # L27 –

+0

Para la versión actual, detecte esto en su lugar: \ Doctrine \ DBAL \ Exception \ UniqueConstraintViolationException – nicolallias

4

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 todavía.

  1. Se podría hacer uso de event listeners, en particular el caso 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 oyente de eventos, debería buscar todas las entidades modificadas para sus claves (para las primarias, buscaría en los metadatos de la clase para @Id).
  3. Luego tendría que 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 intentarlo.

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. Así por ejemplo, cuando se desea crear una entidad y guardarlo:

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

Esto tampoco es seguro para concurrencia. Si lo implementa, aún podría obtener excepciones duplicadas en color. –

18

utilizo esta estrategia para comprobar si hay restricciones únicas después flush(), no puede ser lo que quiera, pero podría ayudar a alguien más.


Cuando se llama a flush(), si falla una restricción única, un PDOExceptionse lanza 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 no:

Crear índices de mesa con nombres prefijados, por ejemplo. 'Unique_'

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

no especifican sus columnas como único en la definición @Column

Esto parece anular el nombre de índice con al azar ...

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

Después de regenerar su tabla (puede que tenga que caer & reconstruir), usted debe ser capaz de extraer el nombre de columna en el 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 ...

+3

Supongo que Doctrine cambió el manejo de excepciones hace algún tiempo. Obtengo una excepción PDO dentro de una \ Doctrine \ DBAL \ DBALException para estas situaciones. El código anterior sería algo así como catch (\ Doctrine \ DBAL \ DBALException $ e) {if ($ e-> getPrevious() -> getCode() === '23000') {/ * do stuff * /}}. Es importante tener en cuenta que detectar esta excepción es la única forma de tratar algunas situaciones con alta concurrencia. Una consulta de selección para la validación simplemente no es suficiente. –

0

me gustaría añadir a esto específicamente con respecto PDOExceptions--

El código de error 23000 es el código de la manta 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 infracción de registro duplicada que a una violación de clave externa faltante.

Aquí es un ejemplo de cómo hacer frente a 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; 
} 

Soy consciente de que esto no es tan detallada como la cuestión estaba pidiendo, pero me parece que esto es muy útil en muchos casos y no se Doctrine2 específica .

0

La forma más fácil debería ser la siguiente:

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

No es muy seguro en un entorno de alta concurrencia, como comprobar si los nombres de usuario ya se han registrado en un sitio web popular. – Andrew

0

he usado esto y parece que funciona. Devuelve el número de error de MySQL específico, 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; 
} 

He probado esto con algunos otros escenarios y volverá otros códigos también como 1146 (La tabla no existe) y 1054 (columna Desconocido).

2

Si solo quiere atrapar errores duplicados. Usted no sólo debe comprobar el número de código

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

porque este cogerá otros errores como 'usuario' campo no puede estar vacío. Mi solución es comprobar 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 { 
         //.... 
        } 
       } 
2

En Symfony 2, que en realidad produce una \ excepción, no una \ PDOException

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

} 

$ e- > getMessage() ecos algo así como lo siguiente:

ha producido una excepción durante la ejecución de 'INSERT INTO (...) VALORES (,?,?,?)' con params [...]:

SQLSTATE [23000]: Integridad violación de la restricción: 1062 Entrada duplicada '...' para la tecla 'primaria'

3

Si está usando Symfony2 se puede utilizar con UniqueEntity(…)form->isValid() para atrapar duplicados antes de enjuagar().

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

En su entidad puede añadir la anotación:

use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; 

/** 
* @UniqueEntity("email") 
*/ 
class YourEntity { 

Luego, en su controlador, después de entregar la solicitud a la forma que podrá consultar su validaciones.

$form->handleRequest($request); 

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

me gustaría recomendar la combinación de esto con la respuesta de Pedro, ya que el esquema de base de datos debe cumplir también la singularidad:

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

Si se ejecuta una consulta SELECT antes de la inserción no es lo que quiere, se puede solo ejecuta el flush() y atrapa la excepción.

En Doctrina DBAL 2.3, se puede entender de forma segura un error restricción única observando el código de error de excepción DOP (23000 para MySQL, 23050 para Postgres), que está envuelto por la Doctrina DBALException:

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

En realidad, el código 23000 no es específico de los errores de restricción únicos. Ejemplo tomado del [documento de MySQL] (https://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html): Error: 1048 SQLSTATE: 23000 (ER_BAD_NULL_ERROR) Mensaje: Columna '% s' no puede ser nulo – Emilie

+0

@emile ¿ese error devuelve una excepción con el código 23000, o es solo un número interno de MySQL? –