web-dev-qa-db-fra.com

Vérification des clés en double avec Doctrine 2

Existe-t-il un moyen facile de vérifier les clés en double avec Doctrine 2 avant de faire un vidage?

48
tom

Vous pouvez attraper le UniqueConstraintViolationException comme tel:

use Doctrine\DBAL\Exception\UniqueConstraintViolationException;

// ...

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

J'utilise cette stratégie pour vérifier les contraintes uniques après flush () , peut ne pas être ce que vous voulez, mais pourrait aider quelqu'un d'autre.


Lorsque vous appelez flush (), si une contrainte unique échoue, une PDOException est lancée avec le code 230.

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 vous avez besoin d'obtenir le nom de la colonne défaillante:

Créez des index de table avec des noms préfixés, par exemple. 'unique_'

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

NE PAS spécifier vos colonnes comme uniques dans la définition @Column

Cela semble remplacer le nom d'index par un nom aléatoire ...

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

Après avoir régénéré votre table (vous devrez peut-être la supprimer et la reconstruire), vous devriez pouvoir extraire le nom de la colonne du message d'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;

Pas parfait, mais ça marche ...

20
Peter Johnson

Si l'exécution d'une requête SELECT avant l'insertion n'est pas ce que vous voulez, vous pouvez uniquement exécuter flush () et intercepter l'exception.

Dans Doctrine DBAL 2.3, vous pouvez détecter en toute sécurité une erreur de contrainte unique en consultant le code d'erreur d'exception PDO (23000 pour MySQL, 23050 pour Postgres), qui est encapsulé par le Doctrine DBALException:

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

J'ai aussi rencontré ce problème il y a quelque temps. Le problème principal n'est pas les exceptions spécifiques à la base de données mais le fait, lorsqu'une exception PDOException est levée, l'EntityManager est fermé. Cela signifie que vous ne pouvez pas être sûr de ce qui se passera avec les données que vous souhaitez vider. Mais il ne serait probablement pas enregistré dans la base de données car je pense que cela se fait dans le cadre d'une transaction.

Alors, quand je pensais à ce problème, j'ai trouvé cette solution, mais je n'ai pas encore eu le temps de l'écrire.

  1. Cela pourrait être fait en utilisant écouteurs d'événements , en particulier l'événement onFlush. Cet événement est appelé avant l'envoi des données à la base de données (après le calcul des ensembles de modifications - vous savez donc déjà quelles entités ont été modifiées).
  2. Dans cet écouteur d'événements, vous devrez parcourir toutes les entités modifiées pour leurs clés (pour le primaire, il recherchera dans les métadonnées de classe @Id).
  3. Ensuite, vous devrez utiliser une méthode de recherche avec les critères de vos clés. Si vous trouviez un résultat, vous avez la possibilité de lever votre propre exception, qui ne fermera pas l'EntityManager et vous pourrez l'attraper dans votre modèle et apporter quelques corrections aux données avant de réessayer le vidage.

Le problème avec cette solution serait qu'elle pourrait générer beaucoup de requêtes vers la base de données, donc elle nécessiterait beaucoup d'optimisation. Si vous ne souhaitez utiliser une telle chose que dans quelques endroits, je vous recommande de vérifier l'endroit où le doublon pourrait survenir. Ainsi, par exemple, où vous souhaitez créer une entité et l'enregistrer:

$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)
}
4
Vašek Purchart

Si vous utilisez Symfony2, vous pouvez utiliser niqueEntity (…) avec form->isValid() pour détecter les doublons avant flush ().

Je suis sur la clôture en postant cette réponse ici, mais elle semble valable car beaucoup de Doctrine user sera également en utilisant Symfony 2. Pour être clair: ceci utilise la classe de validations de Symfony qui sous le capot utilise un référentiel d'entités pour vérifier (est configurable mais par défaut à findBy).

Sur votre entité, vous pouvez ajouter l'annotation:

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

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

Ensuite, dans votre contrôleur, après avoir transmis la demande au formulaire, vous pouvez vérifier vos validations.

$form->handleRequest($request);

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

Je recommanderais de combiner cela avec la réponse de Peter, car votre schéma de base de données devrait également appliquer l'unicité:

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

Dans Symfony 2, il lève en fait une\Exception, pas une\PDOException

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

}

$ e-> getMessage () fait écho à quelque chose comme ceci:

Une exception s'est produite lors de l'exécution de 'INSERT INTO (...) VALUES (?,?,?,?)' Avec les paramètres [...]:

SQLSTATE [23000]: violation de contrainte d'intégrité: 1062 entrée en double '...' pour la clé 'PRIMARY'

2
Aris

Si vous souhaitez simplement détecter les erreurs en double. Vous ne devez pas simplement vérifier le numéro de code

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

car cela va attraper d'autres erreurs comme le champ 'utilisateur' ne peut pas être vide. Ma solution est de vérifier le message d'erreur, s'il contient le texte 'Entrée en double'

                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

La façon la plus simple devrait être la suivante:

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

Je voudrais ajouter à cela spécifiquement en ce qui concerne PDOExceptions--

Le code d'erreur 23000 est un code général pour une famille de violations de contraintes d'intégrité que MySQL peut renvoyer.

Par conséquent, la gestion du code d'erreur 23000 n'est pas suffisamment spécifique pour certains cas d'utilisation.

Par exemple, vous souhaiterez peut-être réagir différemment à une violation d'enregistrement en double qu'à une violation de clé étrangère manquante.

Voici un exemple de la façon de gérer cela:

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

Je me rends compte que ce n'est pas aussi détaillé que la question le demandait mais je trouve que c'est très utile dans de nombreux cas et n'est pas spécifique à Doctrine2.

0
Peter M. Elias

Je l'ai utilisé et cela semble fonctionner. Il renvoie le numéro d'erreur MySQL spécifique - c'est-à-dire 1062 pour une entrée en double - prêt pour que vous puissiez gérer comme vous le souhaitez.

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

J'ai testé cela avec quelques autres scénarios et il retournera également d'autres codes comme 1146 (le tableau n'existe pas) et 1054 (colonne inconnue).

0
Jeff S.