src/App/Controller/CRUD/FormController.php line 174

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace App\Controller\CRUD;
  3. use App\Model\ImpersonatingUser;
  4. use App\RabbitMq\TasksPool;
  5. use App\Response\ActionResult;
  6. use App\Service\FormSavedMessage;
  7. use Doctrine\DBAL\Connection;
  8. use Doctrine\ORM\EntityManagerInterface;
  9. use InvalidArgumentException;
  10. use LogicException;
  11. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  12. use Symfony\Component\Form\FormError;
  13. use Symfony\Component\Form\FormInterface;
  14. use Symfony\Component\HttpFoundation\JsonResponse;
  15. use Symfony\Component\HttpFoundation\ParameterBag;
  16. use Symfony\Component\HttpFoundation\Request;
  17. use Symfony\Component\HttpFoundation\Response;
  18. use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
  19. use Symfony\Component\Security\Core\Exception\AccessDeniedException;
  20. use Symfony\Component\Security\Core\Security;
  21. use Throwable;
  22. /**
  23.  * @template TEntityClass of object
  24.  * @template-extends EntityController<TEntityClass>
  25.  */
  26. abstract class FormController extends EntityController
  27. {
  28.     protected Security $security;
  29.     protected TasksPool $tasksPool;
  30.     protected FormSavedMessage $formSavedMessage;
  31.     protected EventDispatcherInterface $eventDispatcher;
  32.     protected bool $useFormDataOptions false;
  33.     /**
  34.      * @phpstan-param TEntityClass $entity
  35.      */
  36.     abstract protected function getFormType($entity): string;
  37.     /**
  38.      * @param ParameterBag<mixed> $parameters Request parameters
  39.      * @return mixed
  40.      * @phpstan-return TEntityClass
  41.      */
  42.     abstract public function provideEntity(ParameterBag $parameters);
  43.     /**
  44.      * @return string path to template
  45.      */
  46.     abstract protected function getTemplate(FormInterface $form): string;
  47.     /**
  48.      * @phpstan-param TEntityClass $entity
  49.      */
  50.     abstract protected function getRedirectUrl(Request $requestFormInterface $form$entity): string;
  51.     /**
  52.      * @return array<string, array<mixed>|int|object|string|null>
  53.      * @phpstan-param TEntityClass $entity
  54.      */
  55.     protected function processSubmit(Request $requestFormInterface $form$entity): array
  56.     {
  57.         /** @var Connection $connection */
  58.         $connection $this->doctrine->getConnection();
  59.         try {
  60.             $connection->beginTransaction();
  61.             $this->doctrine->getManager()->persist($entity);
  62.             // need to persist here ,so if in beforeUpdate we will have flush we will have managed entity
  63.             $this->doctrine->getManager()->persist($entity);
  64.             $this->beforeUpdate($request$form$entity);
  65.             $this->doctrine->getManager()->flush();
  66.             $this->afterUpdate($request$form$entity);
  67.             $this->doctrine->getManager()->flush();
  68.             $connection->commit();
  69.         } catch (Throwable $e) {
  70.             $connection->rollBack();
  71.             throw $e;
  72.         }
  73.         // this issue should be after all commits to the database are done, because otherwise consumer may receive the
  74.         // entity which does not exist yet in the database.
  75.         $this->dispatchPoolSend();
  76.         $entityClass get_class($entity);
  77.         /** @var EntityManagerInterface|null $manager */
  78.         $manager $this->doctrine->getManagerForClass($entityClass);
  79.         if (null !== $manager) {
  80.             $cache $manager->getCache();
  81.             if (null !== $cache) {
  82.                 if (!method_exists($entity'getId')) {
  83.                     throw new InvalidArgumentException('Entity class must have getId method.');
  84.                 }
  85.                 $cache->evictEntity($entityClass$entity->getId());
  86.             }
  87.         }
  88.         $this->showSavedMessage($entity);
  89.         return [];
  90.     }
  91.     /**
  92.      * @param array<string, array<mixed>|bool|int|float|object|string|null> $filters
  93.      */
  94.     public function execute(array $filters = []): ActionResult
  95.     {
  96.         $attributes = new ParameterBag($filters);
  97.         $entity $this->provideEntity($attributes);
  98.         $form $this->createFormType(
  99.             $this->getFormType($entity),
  100.             $entity,
  101.             array_merge($this->getFormOptions($entity), $this->useFormDataOptions ? ['request_attributes' => $filters] : [])
  102.         );
  103.         return new ActionResult(
  104.             $this->getTemplate($form),
  105.             array_merge($this->templateParams, ['form' => $form->createView(), 'entity' => $entity]),
  106.             !($form->isSubmitted() && !$form->isValid()) // not is valid and was submitted.. else response considered valid
  107.         );
  108.     }
  109.     /**
  110.      * @param mixed[] $filters
  111.      * @param mixed[] $data
  112.      * @return mixed[]|FormInterface
  113.      */
  114.     public function submit(array $filters, array $data)
  115.     {
  116.         $attributes = new ParameterBag($filters);
  117.         $entity $this->provideEntity($attributes);
  118.         $options array_merge($this->getFormOptions($entity), $this->useFormDataOptions ? ['request_attributes' => $filters] : []);
  119.         $options array_merge(['csrf_protection' => false], $options);
  120.         $form $this->createFormType(
  121.             $this->getFormType($entity),
  122.             $entity,
  123.             $options
  124.         );
  125.         $request = new Request();
  126.         if ($this->isValid($request$form->submit($datafalse), $entity)) {
  127.             return $this->processSubmit($request$form$entity);
  128.         }
  129.         return $form;
  130.     }
  131.     /**
  132.      * @return Response|ActionResult
  133.      */
  134.     public function handleRequest(Request $request)
  135.     {
  136.         try {
  137.             $entity $this->provideEntity($request->attributes);
  138.         } catch (AccessDeniedException $ex) {
  139.             if ($this->isHandlingJson($request) && $this->isAjaxRequest($request)) {
  140.                 return new JsonResponse(['status' => false], 403);
  141.             }
  142.             throw $ex;
  143.         }
  144.         $form $this->createFormType(
  145.             $this->getFormType($entity),
  146.             $entity,
  147.             array_merge($this->getFormOptions($entity), $this->useFormDataOptions ? ['request_attributes' => $request->attributes->all()] : [])
  148.         );
  149.         if ($this->isValid($request$form->handleRequest($request), $entity)) {
  150.             $processedData $this->processSubmit($request$form$entity);
  151.             if ($this->isHandlingJson($request) && $this->isAjaxRequest($request)) {
  152.                 return $this->handleAjax($request$form$entity$processedData);
  153.             }
  154.             $response = new ActionResult(null$processedData);
  155.             $response->setRedirect($this->getRedirectUrl($request$form$entity));
  156.             return $response;
  157.         }
  158.         if ($this->isHandlingJson($request) && $this->isAjaxRequest($request)) {
  159.             return $this->handleAjax($request$form$entitynull);
  160.         }
  161.         return new ActionResult(
  162.             $this->getTemplate($form),
  163.             array_merge($this->templateParams, ['form' => $form->createView(), 'entity' => $entity]),
  164.             !($form->isSubmitted() && !$form->isValid()) // not is valid and was submitted.. else response considered valid
  165.         );
  166.     }
  167.     /**
  168.      * @phpstan-param TEntityClass $entity
  169.      * @return mixed[]
  170.      */
  171.     protected function getFormOptions($entity): array
  172.     {
  173.         return [];
  174.     }
  175.     /**
  176.      * @phpstan-param TEntityClass $entity
  177.      */
  178.     protected function isValid(Request $requestFormInterface $form, &$entity): bool
  179.     {
  180.         if (!($form->isSubmitted() && $form->isValid())) {
  181.             return false;
  182.         }
  183.         $token $this->security->getToken();
  184.         if ($token instanceof SwitchUserToken) {
  185.             $impersonatorUser $token->getOriginalToken()->getUser();
  186.             if (!($impersonatorUser instanceof ImpersonatingUser)) {
  187.                 throw $this->createAccessDeniedException();
  188.             }
  189.             if (!$impersonatorUser->isDestructiveActions()) {
  190.                 $form->addError(new FormError('Impersonation does not allow you to save anything.'));
  191.                 return false;
  192.             }
  193.         }
  194.         return true;
  195.     }
  196.     /**
  197.      * @phpstan-param TEntityClass $entity
  198.      */
  199.     protected function beforeUpdate(Request $requestFormInterface $form$entity): void
  200.     {
  201.         // do nothing
  202.     }
  203.     /**
  204.      * @phpstan-param TEntityClass $entity
  205.      */
  206.     protected function afterUpdate(Request $requestFormInterface $form$entity): void
  207.     {
  208.         // do nothing
  209.     }
  210.     /**
  211.      * @phpstan-param TEntityClass $entity
  212.      * @param mixed[]|null $processedData
  213.      */
  214.     protected function handleAjax(Request $requestFormInterface $form$entity, ?array $processedData): Response
  215.     {
  216.         if ($form->isSubmitted() && $form->isValid()) {
  217.             return new JsonResponse(['status' => true'redirect' => $this->getRedirectUrl($request$form$entity)]);
  218.         }
  219.         $response = new ActionResult(
  220.             $this->getTemplate($form),
  221.             array_merge($this->templateParams, ['form' => $form->createView(), 'entity' => $entity]),
  222.             !($form->isSubmitted() && !$form->isValid())
  223.         );
  224.         if (null === $template $response->getTemplate()) {
  225.             throw new LogicException('Can not render view without template name');
  226.         }
  227.         $httpResponse $this->render($template$response->getParams(), $response->getResponse());
  228.         $httpResponse->setStatusCode(Response::HTTP_BAD_REQUEST);
  229.         return $httpResponse;
  230.     }
  231.     /**
  232.      * Creates and returns a Form instance from the type of the form.
  233.      *
  234.      * @param string $type    The fully qualified class name of the form type
  235.      * @param TEntityClass|null  $entity    The initial data for the form
  236.      * @param mixed[]  $options Options for the form
  237.      */
  238.     protected function createFormType(string $type$entity null, array $options = []): FormInterface
  239.     {
  240.         return $this->createForm($type$entity$options);
  241.     }
  242.     /**
  243.      * Sends pool of tasks if any available
  244.      */
  245.     protected function dispatchPoolSend(): void
  246.     {
  247.         $this->tasksPool->send();
  248.     }
  249.     /**
  250.      * @phpstan-param TEntityClass $entity
  251.      */
  252.     protected function showSavedMessage($entity): void
  253.     {
  254.         $this->formSavedMessage->setEnabled(true);
  255.         $this->formSavedMessage->showMessage($entity);
  256.     }
  257.     /**
  258.      * @required
  259.      */
  260.     final public function setSecurity(Security $security): void
  261.     {
  262.         $this->security $security;
  263.     }
  264.     /**
  265.      * @required
  266.      */
  267.     final public function setTasksPool(TasksPool $tasksPool): void
  268.     {
  269.         $this->tasksPool $tasksPool;
  270.     }
  271.     /**
  272.      * @required
  273.      */
  274.     final public function setFormSavedMessage(FormSavedMessage $formSavedMessage): void
  275.     {
  276.         $this->formSavedMessage $formSavedMessage;
  277.     }
  278.     /**
  279.      * @required
  280.      */
  281.     final public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void
  282.     {
  283.         $this->eventDispatcher $eventDispatcher;
  284.     }
  285. }