Closed meiyasan closed 3 years ago
Certainly, I did it in a subscriber.... don't know itf this is the right way but it works
<?php
namespace App\EventSubscribers;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Exception\EntityRemoveException;
use EasyCorp\Bundle\EasyAdminBundle\Provider\AdminContextProvider;
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class PDOExceptionSubscriber implements EventSubscriberInterface {
private $session;
/**
* @var AdminContextProvider
*/
private AdminContextProvider $adminContextProvider;
/**
* @var AdminUrlGenerator
*/
private AdminUrlGenerator $adminUrlGenerator;
public function __construct(
SessionInterface $session,
AdminContextProvider $adminContextProvider,
AdminUrlGenerator $adminUrlGenerator
) {
$this->session = $session;
$this->adminContextProvider = $adminContextProvider;
$this->adminUrlGenerator = $adminUrlGenerator;
}
public static function getSubscribedEvents() {
return [
KernelEvents::EXCEPTION => [
'handlePDOException',
],
];
}
public function handlePDOException(ExceptionEvent $event) {
$exception = $event->getThrowable();
$message = $exception->getMessage();
if (!$exception instanceof EntityRemoveException) {
return;
}
$context = $this->adminContextProvider->getContext()->getCrud();
$redirectUrl = $this->adminUrlGenerator
->setAction(Action::INDEX)
->setController($context->getControllerFqcn());
$this->session->getFlashBag()->add('warning', 'PDO Exception :'.$message);
$event->setResponse(new RedirectResponse($redirectUrl));
}
}
any feedback to this hobby programmer is highly appreciated :-)
@parijke Many thanks, Paul ! I am not familiar enough with Symfony yet to tell, but it works like a charm :)
Is there a reason not to use validation constraints to ensure that invalid entities are never flushed at all?
Well, indeed this might be better.. if I knew how to do it :) So far I couldn't find how to input these validation constraints in a CrudController.
Anyhow, the ExceptionSubscriber +/- some adjustment is still good when I want to handle an unexpected issue. I will try to figure out these validation constraints and perhaps provide some working example for someone who might pass by this place.
@xKZL I recommend to do what Christian said. Add as many validation constraints as needed in your user entities to avoid attempting to persist entities with wrong information. See https://symfony.com/doc/current/validation.html#constraints-in-form-classes for details. I'm closing this issue for that reason. Thanks!
I just drop here the code I have been working on, today. Perhaps it will might help someone later. Many thanks @xabbuh for the tips about the basic Validators.
The documentation/details you provides @javiereguiluz looks kind of poor to me, but thanks. (e.g. buildForm is not an object I am working on here using EA)
I think these ones are more interesting: https://symfony.com/doc/current/validation/groups.html https://github.com/EasyCorp/EasyAdminBundle/issues/3690
So here is an example related to CrudController objects.
# src/Entity/User.php
<?php
namespace App\Entity;
use App\EntityListener\UserEntityListener;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity(repositoryClass=UserRepository::class)
*/
class User implements UserInterface
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string", length=180, unique=true)
*/
private $username;
// [...]
<?php
# src/Controller/Dashboard/UserCrudController.php
namespace App\Controller\Dashboard;
use App\Entity\User;
use App\Field\PasswordField;
use App\Field\LinkIdField;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Field\CountryField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use EasyCorp\Bundle\EasyAdminBundle\Field\EmailField;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateField;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField;
use EasyCorp\Bundle\EasyAdminBundle\Field\BooleanField;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
class UserCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return User::class;
}
public function configureCrud(Crud $crud): Crud
{
return $crud
//->showEntityActionsAsDropdown()
->setPageTitle('index', 'User Management')
->setDefaultSort(['id' => 'DESC'])
->setFormOptions(
['validation_groups' => ['new']], // Crud::PAGE_NEW
['validation_groups' => ['edit']] // Crud::PAGE_EDIT
);
}
The lines about $crud::setFormOptions() referring to "new" and "edit" are related to the validation_groups introduced in the user entity. I am only using basic Constraints, but I will design later a UserEntityValidator including DoctrineInterface https://stackoverflow.com/questions/16398714/symfony-validator-with-doctrine-query Although, here I have to figure out how to mension the validation group. Not a big deal, IMO.
In case the validation constrains were not robust enough, I can now quickly catch SQL exceptions without being throw to my custom ExceptionController that displays an error 500. At least it helps keeping focus and reduces frustration of being thrown out of the dashboard. (many thanks again @parijke, I just added a fix about infinite recursive redirections)
<?php
namespace App\EventSubscriber\Dashboard;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Exception\EntityRemoveException;
use EasyCorp\Bundle\EasyAdminBundle\Provider\AdminContextProvider;
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class DashboardExceptionSubscriber implements EventSubscriberInterface {
/**
* @var SessionInterface
*/
private $session;
/**
* @var AdminContextProvider
*/
private $adminContextProvider;
/**
* @var AdminUrlGenerator
*/
private $adminUrlGenerator;
public function __construct(SessionInterface $session, AdminContextProvider $adminContextProvider, AdminUrlGenerator $adminUrlGenerator
) {
$this->session = $session;
$this->adminContextProvider = $adminContextProvider;
$this->adminUrlGenerator = $adminUrlGenerator;
}
public static function getSubscribedEvents() {
return [ KernelEvents::EXCEPTION => ['onKernelException'] ];
}
public function sendFlashPrimary ($title = "", $message = "") { return $this->sendFlash("primary", $title, $message); }
public function sendFlashSecondary($title = "", $message = "") { return $this->sendFlash("secondary", $title, $message); }
public function sendFlashDark ($title = "", $message = "") { return $this->sendFlash("dark", $title, $message); }
public function sendFlashLight ($title = "", $message = "") { return $this->sendFlash("light", $title, $message); }
public function sendFlashSuccess ($title = "", $message = "") { return $this->sendFlash("success", $title, $message); }
public function sendFlashInfo ($title = "", $message = "") { return $this->sendFlash("info", $title, $message); }
public function sendFlashNotice ($title = "", $message = "") { return $this->sendFlash("notice", $title, $message); }
public function sendFlashWarning ($title = "", $message = "") { return $this->sendFlash("warning", $title, $message); }
public function sendFlashDanger ($title = "", $message = "") { return $this->sendFlash("danger", $title, $message); }
public function sendFlash($type, $title = "", $message = "")
{
if($title instanceof ExceptionEvent) {
$event = $title;
$exception = $event->getThrowable();
$title = get_class($exception)."<br/>";
$title .= "(".$exception->getFile().":".$exception->getLine().")";
$message = $exception->getMessage();
}
if(!empty($title)) $title = "<b>".$title."</b><br/>";
if(!empty($title.$message))
$this->session->getFlashBag()->add($type, $title.$message);
}
public function onKernelException(ExceptionEvent $event)
{
// Check if exception happened in EasyAdmin (avoid warning outside EA)
if(!$this->adminContextProvider) return;
if(!$this->adminContextProvider->getContext()) return;
// Get back exception & send flash message
$this->sendFlashDanger($event);
// Get back crud information
$crud = $this->adminContextProvider->getContext()->getCrud();
if(!$crud) return;
$controller = $crud->getControllerFqcn();
$action = $crud->getCurrentPage();
// Avoid infinite redirection
// - If exception happened in "index", redirect to dashboard
// - If exception happened in an other section, redirect to index page first
// - If exception happened after submitting a form, just redirect to the initial page
$url = $this->adminUrlGenerator->unsetAll();
switch($action) {
case "index": break;
default:
$url = $url->setController($controller);
if(isset($_POST) && !empty($_POST)) $url = $url->setAction($action);
}
$event->setResponse(new RedirectResponse($url));
}
}
This looks like perfectly solving my initial issue. Thanks guys!
Do you actually have different constraints for creating and editing entities? Otherwise you don't need to use validation groups which also means no extra configuration in your EasyAdmin forms.
yeah I do, like plainPassword can be empty while editing if I don't want to change it. do you have an idea how to handle this better ?
In this case, that's probably the best solution. 👍
Can I ask something? Maybe I am doing it totally wrong, but I want to prevent deletion of a Customer if it has Invoices. So I made that constaint in the database. Then, when trying to delete the cutomer having invoices, I get a PDO Exception which I catch using the subscriber method.
Is there an easier way to inform a user that he cannot delete Customers with Invoices?
Hello,
I have a "user" table in my database that is modified by admins using easyadmin3. Sometimes admins are wrongly filling forms, which results in a Doctrine Exception (e.g. NotNullConstraintViolationException)
I would like to flash an error message on the form page and keeping the previously provided information. Is that possible in the current implementation ? At this time I only see an ugly error 500 and can't find anything about it in the documentation.