2010-10-19 2 views
43

Есть ли простой способ проверить дубликаты ключей с помощью Doctrine 2 перед тем, как сделать флеш?Проверка дубликатов ключей с помощью Doctrine 2

+1

Я действительно не имеют ответа, но мне интересно, как проверять перед флеша, что иначе, чем делать флеш и обработки ошибок (предполагая, что дубликат ключа существует). –

+0

На флеше будут выбраны исключения для конкретной базы данных. – tom

+3

Большинство представленных здесь решений не учитывают тот факт, что вы просто * не можете * проверить наличие дубликатов заранее, потому что это не атомная операция, и поэтому вы можете * еще * иметь повторяющиеся значения, если другие вставки потоков в таблицу, например. Таким образом, единственные возможные решения, на мой взгляд, - это либо обработка отказа вручную, либо использование блокировки. Первый довольно уродливый с Доктриной (поскольку ЕМ закрывается), последний может иметь ужасные последствия, если вы не будете осторожны. Я хотел бы получить хороший ответ на это сам. –

ответ

32

Вы можете поймать UniqueConstraintViolationException как таковой:

use Doctrine\DBAL\Exception\UniqueConstraintViolationException; 

// ... 

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

Это было добавлено в 2014 году. Это должно быть так, как это сделать сейчас. – tom

+0

Это было доступно, так как Doctrine DBAL 2.5 - UniqueConstraintViolationException наследует от ConstraintViolationException: https://github.com/doctrine/dbal/blob/master/lib/Doctrine/DBAL/Exception/ConstraintViolationException.php # L27 –

+0

Для текущей версии поймайте это вместо: \ Doctrine \ DBAL \ Exception \ UniqueConstraintViolationException – nicolallias

4

Я тоже сталкивался с этой проблемой некоторое время назад. Основная проблема заключается не в исключениях для конкретной базы данных, а в том, что при исключении PDOException EntityManager закрывается. Это означает, что вы не можете быть уверены, что произойдет с данными, которые вы хотите сбросить. Но, вероятно, это не будет сохранено в базе данных, потому что я думаю, что это делается в рамках транзакции.

Итак, когда я думал об этой проблеме, я придумал это решение, но у меня не было времени, чтобы написать его еще.

  1. Это можно сделать, используя event listeners, в частности событие onFlush. Это событие вызывается до того, как данные будут отправлены в базу данных (после вычисления наборов изменений, чтобы вы уже знали, какие объекты были изменены).
  2. В этом случае слушателю необходимо будет просмотреть все измененные объекты для своих ключей (для первичного поиска он будет искать метаданные класса для @Id).
  3. Тогда вам нужно будет использовать метод find с критериями ваших ключей. Если вы найдете результат, у вас есть шанс выбросить собственное исключение, которое не закроет EntityManager, и вы сможете поймать его в своей модели и внести некоторые исправления в данные, прежде чем снова попытаться сбросить флажок.

Проблема с этим решением заключается в том, что он может генерировать довольно много запросов к базе данных, поэтому для этого потребуется довольно много оптимизации. Если вы хотите использовать такую ​​вещь только в нескольких местах, я рекомендую сделать чек на месте, где может возникнуть дубликат. Так, например, когда вы хотите создать объект и сохранить его:

$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

Это также не совместимо с одновременным использованием. Если вы его реализуете, вы можете получить дубликаты исключений на флеш. –

18

Я использую эту стратегию, чтобы проверить наличие уникальных ограничений после флеша(), не может быть то, что вы хотите, но может помочь кому-то еще.


При вызове Flush(), если уникальный Constrain не удается, PDOException брошен с кодом .

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

Если вам нужно получить имя неисправных колонок:

Создания индексов таблиц с префиксами именами, например. «Unique_»

* @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 

После регенерации таблицы (возможно, придется отказаться от его & восстановления), вы должны быть в состоянии извлечь имя столбца из сообщения исключений.

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

Не идеально, но это работает ...

+3

Я полагаю, что доктрина изменила обработку исключений некоторое время назад. Я получаю PDOException внутри \ Doctrine \ DBAL \ DBALException для таких ситуаций. Вышеприведенный код был бы чем-то вроде catch (\ Doctrine \ DBAL \ DBALException $ e) {if ($ e-> getPrevious() -> getCode() === '23000') {/ * do stuff * /}}. Важно отметить, что перехват этого исключения - единственный способ справиться с некоторыми ситуациями с высокой параллелизмом. Выбирать запрос для проверки просто недостаточно –

0

Я хотел бы добавить к этому конкретно в отношении PDOExceptions--

Код ошибки 23000 Одеяло код для семьи Нарушение целостности Нарушения, которые MySQL может вернуть.

Следовательно, обработка кода ошибки 23000 не является достаточно конкретной для некоторых случаев использования.

Например, вы можете по-разному реагировать на дублирующееся нарушение записи, чем на отсутствие нарушения внешнего ключа.

Вот пример того, как иметь дело с этим:

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 конкретных ,

0

Самый простой способ должен быть таким:

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

Это не очень безопасно в среде с высоким уровнем параллелизма, например, проверка того, что имена пользователей уже зарегистрированы на популярном веб-сайте. – Andrew

0

Я использовал это, и это, кажется, работает. Он возвращает конкретный номер ошибки MySQL - т. Е. 1062 для дублированной записи - готов для вас, как вам нравится.

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

Я испытал это с несколькими другими сценариями, и он будет возвращать другие коды тоже, как 1146 (таблица не существует) и 1054 (не колонка).

2

Если вы просто хотите поймать повторяющиеся ошибки. Вы не должны просто проверять код

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

потому что это поймает другие ошибки, такие как поле «пользователь» не может быть пустым. Мое решение, чтобы проверить сообщение об ошибке, если он содержит текст 'Дублировать запись'

   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

В Symfony 2, он фактически бросает \ Exception, а не \ PDOException

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

} 

$ e- > GetMessage() вторит что-то вроде следующего:

произошло исключение при выполнении 'INSERT INTO (...) VALUES (,???)' с Params [...]:

SQLSTATE [23000]: Целостность нарушение ограничений: 1062 Дублируемая запись '...' для ключа 'PRIMARY'

3

Если вы используете Symfony2, вы можете использовать UniqueEntity(…) с form->isValid(), чтобы поймать дубликаты перед флешем().

Я нахожусь на заборе, разместив здесь этот ответ, но кажется полезным с много пользователей Doctrine также будет использовать Symfony2. Чтобы быть ясным: это использует класс проверки Symfony, который под капотом использует репозиторий сущности для проверки (настраивается, но по умолчанию - findBy).

На вашей организации вы можете добавить аннотацию:

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

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

Тогда в контроллере, после передачи запроса к форме вы можете проверить свои валидаций.

$form->handleRequest($request); 

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

Я рекомендовал бы совмещая это с ответом Питера, так как ваша схема базы данных должна обеспечивать слишком уникальность:

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

Если работает SELECT, запрос до начала вставки не то, что вы хотите, вы можете только запустите flush() и поймайте исключение.

В Доктрине DBAL 2.3, вы можете спокойно понять уникальную ошибку ограничения, глядя на код ошибки Исключение PDO (23000 для MySQL, 23050 для Postgres), который обернут Доктрина DBALException:

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

На самом деле код 23000 не является специфическим для уникальных ошибок ограничения. Пример, взятый из [MySQL doc] (https://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html): Ошибка: 1048 SQLSTATE: 23000 (ER_BAD_NULL_ERROR) Сообщение: Столбец '% s' не может быть нулевым – Emilie

+0

@emile эта ошибка возвращает исключение с кодом 23000, или это просто внутренний номер MySQL? –

Смежные вопросы