Search code examples
symfonysymfony-3.3symfony2-easyadmin

Override Method or EventListener: stop creation process and show warning just the first time in EasyAdmin?


I am using EasyAdmin in my SF 3.3 project but I need to achieve something different from how EasyAdmin has been built for. Take a look at the following picture:

enter image description here

As you might notice a user can be in more than one GroupingRole. Having that information the challenge is:

  • Check if the user has been assigned to any other GroupingRole
  • If the criteria meets the condition then show a warning message saying "The user A is already assigned to GroupingRole A" and prevent the record to be created. (this message could be in a popup, a javascript alert or an alert from Bootstrap - since EA already uses it)
  • When the admin click once again on "Save changes" the record should be created.

What I want to achieve with this approach is to alert the admin that the user is already to any other group but not stop him for create the record.

I have achieve some part of it already by override the prePersist method for just that entity (see below):

class AdminController extends BaseAdminController
{
    /**
     * Check if the users has been assigned to any group
     */
    protected function prePersistGroupingRoleEntity($entity)
    {
        $usersToGroupRoleEntities = $this->em->getRepository('CommonBundle:UsersToGroupRole')->findAll();
        $usersToGroupRole         = [];

        /** @var UsersToGroupRole $groupRole */
        foreach ($usersToGroupRoleEntities as $groupRole) {
            $usersToGroupRole[$groupRole->getGroupingRoleId()][] = $groupRole->getUsersId();
        }

        $usersInGroup = [];

        /** @var Users $userEntity */
        foreach ($entity->getUsersInGroup() as $userEntity) {
            foreach ($usersToGroupRole as $group => $users) {
                if (\in_array($userEntity->getId(), $users, true)) {
                    $usersInGroup[$group][] = $userEntity->getId();
                }
            }
        }

        $groupingRoleEnt = $this->em->getRepository('CommonBundle:GroupingRole');
        $usersEnt        = $this->em->getRepository('CommonBundle:Users');

        $message = [];
        foreach ($usersInGroup as $group => $user) {
            foreach($user as $usr) {
                $message[] = sprintf(
                    'The user %s already exists in %s group!',
                    $usersEnt->find($usr)->getEmail(),
                    $groupingRoleEnt->find($group)->getName()
                );
            }
        }
    }
}

What I don't know is how to stop the record to be created and instead show the warning just the first time the button is clicked because the second time and having the warning in place I should allow to create the record.

Can any give me some ideas and/or suggestions?

UPDATE: adding entities information

In addition to the code displayed above here is the entities involved in such process:

/**
 * @ORM\Entity
 * @ORM\Table(name="grouping_role")
 */
class GroupingRole
{
    /**
     * @ORM\Id
     * @ORM\Column(name="id", type="integer",unique=true,nullable=false)
     * @ORM\GeneratedValue
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="role_name", type="string", nullable=false)
     */
    private $name;

    /**
     * @var string
     *
     * @ORM\Column(name="role_description", type="string", nullable=false)
     */
    private $description;

    /**
     * @var ArrayCollection
     *
     * @ORM\ManyToMany(targetEntity="Schneider\QuoteBundle\Entity\Distributor", inversedBy="groupingRole")
     * @ORM\JoinTable(name="grouping_to_role",
     *   joinColumns={
     *     @ORM\JoinColumn(name="grouping_role_id", referencedColumnName="id")
     *   },
     *   inverseJoinColumns={
     *     @ORM\JoinColumn(name="DistributorID", referencedColumnName="DistributorID", nullable=false)
     *   }
     * )
     *
     * @Assert\Count(
     *      min = 1,
     *      minMessage = "You must select at least one Distributor"
     * )
     */
    private $distributorGroup;

    /**
     * @var ArrayCollection
     *
     * @ORM\ManyToMany(targetEntity="CommonBundle\Entity\Users", inversedBy="usersGroup")
     * @ORM\JoinTable(name="users_to_group_role",
     *   joinColumns={
     *     @ORM\JoinColumn(name="grouping_role_id", referencedColumnName="id")
     *   },
     *   inverseJoinColumns={
     *     @ORM\JoinColumn(name="users_id", referencedColumnName="users_id", nullable=false)
     *   }
     * )
     *
     * @Assert\Count(
     *      min = 1,
     *      minMessage = "You must select at least one user"
     * )
     */
    private $usersInGroup;

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->distributorGroup = new ArrayCollection();
        $this->usersInGroup     = new ArrayCollection();
    }
}

/**
 * @ORM\Entity()
 * @ORM\Table(name="users_to_group_role")
 */
class UsersToGroupRole
{
    /**
     * @var int
     *
     * @ORM\Id()
     * @ORM\Column(type="integer",nullable=false)
     * @Assert\Type(type="integer")
     * @Assert\NotNull()
     */
    protected $usersId;

    /**
     * @var int
     *
     * @ORM\Id()
     * @ORM\Column(type="integer", nullable=false)
     * @Assert\Type(type="integer")
     * @Assert\NotNull()
     */
    protected $groupingRoleId;
}

Solution

  • A little example by using form validation approach in EasyAdminBundle:

    class AdminController extends EasyAdminController
    {
        // ...
    
        protected function create<EntityName>EntityFormBuilder($entity, $view)
        {
            $builder = parent::createEntityFormBuilder($entity, $view);
    
            $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
                $data = $event->getData();
    
                $flag = false;
                if (isset($data['flag'])) {
                    $flag = $data['flag'];
                    unset($data['flag']);
                }
                $key = md5(json_encode($data));
    
                if ($flag !== $key) {
                    $event->getForm()->add('flag', HiddenType::class, ['mapped' => false]);
                    $data['flag'] = $key;
                    $event->setData($data);
                }
            });
    
            return $builder;
        }
    
        protected function get<EntityName>EntityFormOptions($entity, $view)
        {
            $options = parent::getEntityFormOptions($entity, $view);
    
            $options['validation_groups'] = function (FormInterface $form) {
                if ($form->has('flag')) {
                    return ['Default', 'CheckUserGroup'];
                }
    
                return ['Default'];
            };
    
            $options['constraints'] = new Callback([
                'callback' => function($entity, ExecutionContextInterface $context) {
                    // validate here and adds the violation if applicable.
    
                    $context->buildViolation('Warning!')
                        ->atPath('<field>')
                        ->addViolation();
                },
                'groups' => 'CheckUserGroup',
            ]);
    
            return $options;
        }
    }
    

    Note that PRE_SUBMIT event is triggered before the validation process happen.

    The flag field is added (dynamically) the first time upon submitted the form, so the validation group CheckUserGroup is added and the callback constraint do its job. Later, the second time the submitted data contains the flag hash (if the data does not changes) the flag field is not added, so the validation group is not added either and the entity is saved (same if the callback constraint does not add the violation the first time).

    Also (if you prefer) you can do all this inside a custom form type for the target entity.