Есть ли простой способ проверить дубликаты ключей с помощью Doctrine 2 перед тем, как сделать флеш?
Проверка дубликатов ключей с помощью Doctrine 2
Ответ 1
Вы можете поймать UniqueConstraintViolationException
как таковой:
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
// ...
try {
// ...
$em->flush();
}
catch (UniqueConstraintViolationException $e) {
// ....
}
Ответ 2
Я использую эту стратегию для проверки уникальных ограничений после 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;
}
Если вам нужно получить имя столбца с ошибкой:
Создать индексы таблиц с префиксными именами, например. 'Уникальный _'
* @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
Если выполнение запроса 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
Я столкнулся с этой проблемой некоторое время назад. Основная проблема заключается не в исключениях для конкретной базы данных, а в том, что при исключении PDOException EntityManager закрывается. Это означает, что вы не можете быть уверены, что произойдет с данными, которые вы хотите сбросить. Но, вероятно, это не будет сохранено в базе данных, потому что я думаю, что это делается в рамках транзакции.
Итак, когда я думал об этой проблеме, я придумал это решение, но у меня не было времени, чтобы написать его еще.
- Это можно сделать, используя прослушиватели событий, в частности событие onFlush. Это событие вызывается перед отправкой данных в базу данных (после вычисления наборов изменений, чтобы вы уже знали, какие объекты были изменены).
- В этом прослушивателе событий вам нужно будет просмотреть все измененные объекты для своих ключей (для первичного поиска он будет искать метаданные класса для @Id).
- Тогда вам придется использовать метод 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)
}
Ответ 5
Если вы используете 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"})
* })
*/
Ответ 6
Если вы просто хотите поймать повторяющиеся ошибки. Вы не должны просто проверять код
$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 {
//....
}
}
Ответ 7
В Symfony 2 он фактически выбрасывает \Exception, а не \PDOException
try {
// ...
$em->flush();
}
catch( \Exception $e )
{
echo $e->getMessage();
echo $e->getCode(); //shows '0'
### handle ###
}
$e > getMessage() echos что-то вроде следующего:
Исключение произошло при выполнении 'INSERT INTO (...) VALUES (?,?,?,?)' с параметрами [...]:
SQLSTATE [23000]: нарушение ограничения целостности: 1062 Дублирующая запись "..." для ключа "PRIMARY"
Ответ 8
Я хотел бы добавить к этому конкретно о 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.
Ответ 9
Самый простой способ - это:
$product = $entityManager->getRepository("\Api\Product\Entity\Product")->findBy(array('productName' => $data['product_name']));
if(!empty($product)){
// duplicate
}
Ответ 10
Я использовал это и, похоже, сработал. Он возвращает конкретный номер ошибки MySQL - т.е. 1062 для дублированной записи - готов для вас, как вам нравится.
try
{
$em->flush();
}
catch(\PDOException $e)
{
$code = $e->errorInfo[1];
// Do stuff with error code
echo $code;
}
Я проверил это с несколькими другими сценариями, и он вернет другие коды тоже, как 1146 (таблица не существует) и 1054 (Неизвестный столбец).