Проверка на наличие дубликатов ключей с помощью Doctrine 2 - PullRequest
47 голосов
/ 19 октября 2010

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

Ответы [ 10 ]

46 голосов
/ 02 марта 2016

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

use Doctrine\DBAL\Exception\UniqueConstraintViolationException;

// ...

try {
   // ...
   $em->flush();
}
catch (UniqueConstraintViolationException $e) {
    // ....
}
19 голосов
/ 22 мая 2011

Я использую эту стратегию для проверки уникальных ограничений после 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;
}

Если вам нужно получить имя ошибочного столбца :

Создайте индексы таблиц с префиксными именами, например.'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;

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

7 голосов
/ 19 февраля 2015

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

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

        try {
            $em->flush($user);
        } catch (\Doctrine\DBAL\DBALException $e) {
            if ($e->getPrevious() &&  0 === strpos($e->getPrevious()->getCode(), '23')) {
                throw new YourCustomException();
            }
        }
4 голосов
/ 08 января 2011

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

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

  1. Это можно сделать с помощью прослушивателей событий , особенно события 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)
}
3 голосов
/ 14 июня 2014

Если вы используете 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"})
 * })
 */
2 голосов
/ 15 февраля 2014

В Symfony 2 фактически выдается \ Exception, а не \ PDOException

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

}

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

Возникла исключительная ситуация при выполнении 'INSERT INTO (...) VALUES (?,?,?,?)' С параметрами [...]:

SQLSTATE [23000]: нарушение ограничения целостности: 1062 Повторяющаяся запись '...' для ключа 'PRIMARY'

2 голосов
/ 02 февраля 2014

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

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

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

                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 {
                        //....
                    }
                }
0 голосов
/ 08 июля 2013

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

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

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

0 голосов
/ 21 января 2013

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

$product    = $entityManager->getRepository("\Api\Product\Entity\Product")->findBy(array('productName' => $data['product_name']));
if(!empty($product)){
 // duplicate
}
0 голосов
/ 18 июля 2012

Я хотел бы добавить к этому конкретную информацию, касающуюся исключений PDOException -

Код ошибки 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.

...