删除受限条目时捕获"Integrity constraint violation: 19 FOREIGN KEY constraint failed"



这个问题与我使用的技术堆栈有关:

  • Symfony 4.2.3
  • 教义ORM 2.6.3
  • 奏鸣曲管理员 3.45.2
  • sqlite3 3.22(尽管RDBMS不应该发挥作用)

假设我们有两个实体:CategoryProduct,其中产品类别与产品的关系为 1:n,产品与类别的关系为 n:1。这看起来像:

Category.php

class Category
{
// ...
/**
* @ORMOneToMany(
*     targetEntity="AppEntityProduct",
*     mappedBy="category",
*     cascade={"persist"}
* )
* @AssertValid()
*/
private $products;
// ...
}

Product.php

class Product
{
// ...
/**
* @ORMManyToOne(
*     targetEntity="AppEntityCategory", 
*     inversedBy="products"
* )
* @ORMJoinColumn(nullable=false)
* @AssertNotBlank()
*/
private $category;
// ...
}

产品必须分配到类别类别可以有 0 个或多个产品。如果类别包含任何产品,则不得将其删除。仅当未分配任何产品时,才能删除类别

当我尝试删除Sonata管理中具有产品的类别时,按预期阻止了删除,并抛出了异常:

PDOException

SQLSTATE[23000]:完整性约束冲突:19 外键约束失败

现在,这是意料之中的,但对最终用户来说不是很好。我想提供一条消息并通知用户无法删除该类别,因为它仍然包含产品

在Sonata Admin中,我使用一种解决方法,编写CategoryAdminController并实现preDelete钩子:

public function preDelete(Request $request, $object)
{
if ($object->getProducts()->isEmpty()) {
return null;
}
$count = $object->getProducts()->count();
$objectName = $this->admin->toString($object);
$this->addFlash(
'sonata_flash_error',
sprintf(
'The category "%s" can not be deleted because it contains %s product(s).',
$objectName,
$count
)
);
return $this->redirectTo($object);
}

但是这感觉不对,因为我必须在管理员之外重新实现它。

处理此问题的最佳实践是什么?我可以在实体中实现某种验证吗?或者也许教义事件侦听器是正确的?

我相信你在这里描述你想做的事情:

Symfony + Doctrine - 定义完整性约束错误时的错误消息

我不会复制粘贴整个消息,但概念是创建onKernelResponse侦听器并侦听PDOException。如何做到这一点有很多文章,我相信你可以在网上轻松找到,我选择了我找到的第一个

。在该侦听器中,您可以确定它是什么异常,并使用flashbag或默认的symfony:

https://symfony.com/doc/current/components/http_foundation/sessions.html

$session->getFlashBag()->add('notice', 'Profile updated');

或者你可以使用Sonata Core Flashbag:

https://sonata-project.org/bundles/core/master/doc/reference/flash_messages.html

要在 PHP 类/控制器中使用此功能,请执行以下操作:

$flashManager = $this->get('sonata.core.flashmessage.manager');

$messages = $flashManager->get('success');要在模板中使用此功能,请包含以下模板(带有可选的域参数):

{% include '@SonataCore/FlashMessage/render.html.twig' %}

注意 如有必要,您还可以在此处指定转换域以覆盖配置:

{% include '@SonataCore/FlashMessage/render.html.twig' with { domain: 'MyCustomBundle' } %}

您还可以 https://tocacar.com/symfony2-how-to-modify-sonataadminbundles-error-message-on-entity-deletion-ca77cac343fa 查看本文并覆盖CRUDController::deleteAction以便处理此类错误。

在这里,您可以在Sonata Admin GitHub页面上找到一些与您的问题相关的代码 https://github.com/sonata-project/SonataAdminBundle/issues/4485 它捕获PDOException,因此还要检查您正在使用的版本,也许您需要的是更新。

我设法通过添加自定义侦听器来解决问题。它在删除受限制的对象时捕获ModelManagerException。它适用于所有注册管理员。这是类:

<?php
namespace AppEventListener;
use SymfonyComponentHttpFoundationSessionSessionInterface;
use SymfonyComponentRoutingGeneratorUrlGeneratorInterface;
use DoctrineORMEntityManagerInterface;
use SymfonyComponentHttpKernelEventGetResponseForExceptionEvent;
use SymfonyComponentHttpFoundationRedirectResponse;
use SonataAdminBundleExceptionModelManagerException;
class ModelManagerExceptionResponseListener
{
private $session;
private $router;
private $em;
public function __construct(SessionInterface $session, UrlGeneratorInterface $router, EntityManagerInterface $em)
{
$this->session = $session;
$this->router = $router;
$this->em = $em;
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
// get the exception
$exception =  $event->getException();
// we proceed only if it is ModelManagerException
if (!$exception instanceof ModelManagerException) {
return;
}
// get the route and id
// if it wasn't a delete route we don't want to proceed
$request = $event->getRequest();
$route = $request->get('_route');
$id = $request->get('id');
if (substr($route, -6) !== 'delete') {
return;
}
$route = str_replace('delete', 'edit', $route);
// get the message
// we proceed only if it is the desired message
$message = $exception->getMessage();
$failure = 'Failed to delete object: ';
if (strpos($message, $failure) < 0) {
return;
}
// get the object that can't be deleted
$entity = str_replace($failure, '', $message);
$repository = $this->em->getRepository($entity);
$object = $repository->findOneById($id);
$this->session->getFlashBag()
->add(
'sonata_flash_error',
sprintf('The item "%s" can not be deleted because other items depend on it.', $object)
)
;
// redirect to the edit form of the object
$url = $this->router->generate($route, ['id' =>$id]);
$response = new RedirectResponse($url);
$event->setResponse($response);
}
}

我们注册服务:

app.event_listener.pdoexception_listener:
class: AppEventListenerModelManagerExceptionResponseListener
arguments:
- '@session'
- '@router'
- '@doctrine.orm.entity_manager'
tags:
- { name: kernel.event_listener, event: kernel.exception }
public: true # this maybe isn't needed

在我的特定情况下,可能不允许删除管理员之外的任何对象。因此,此解决方案满足要求。我希望这个例子可以帮助其他人。您必须根据需要调整某些部件。

相关内容

最新更新