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:

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.