vendor/easycorp/easyadmin-bundle/src/Controller/AdminControllerTrait.php line 63

Open in your IDE?
  1. <?php
  2. namespace EasyCorp\Bundle\EasyAdminBundle\Controller;
  3. use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
  4. use Doctrine\ORM\EntityManager;
  5. use Doctrine\ORM\QueryBuilder;
  6. use EasyCorp\Bundle\EasyAdminBundle\Event\EasyAdminEvents;
  7. use EasyCorp\Bundle\EasyAdminBundle\Exception\EntityRemoveException;
  8. use EasyCorp\Bundle\EasyAdminBundle\Exception\ForbiddenActionException;
  9. use EasyCorp\Bundle\EasyAdminBundle\Exception\NoEntitiesConfiguredException;
  10. use EasyCorp\Bundle\EasyAdminBundle\Exception\NoPermissionException;
  11. use EasyCorp\Bundle\EasyAdminBundle\Exception\UndefinedEntityException;
  12. use EasyCorp\Bundle\EasyAdminBundle\Form\Filter\FilterRegistry;
  13. use EasyCorp\Bundle\EasyAdminBundle\Form\Type\EasyAdminBatchFormType;
  14. use EasyCorp\Bundle\EasyAdminBundle\Form\Type\EasyAdminFiltersFormType;
  15. use EasyCorp\Bundle\EasyAdminBundle\Form\Type\EasyAdminFormType;
  16. use EasyCorp\Bundle\EasyAdminBundle\Form\Type\FileUploadType;
  17. use EasyCorp\Bundle\EasyAdminBundle\Form\Type\Model\FileUploadState;
  18. use Pagerfanta\Pagerfanta;
  19. use Symfony\Component\EventDispatcher\GenericEvent;
  20. use Symfony\Component\Form\Extension\Core\Type\HiddenType;
  21. use Symfony\Component\Form\Extension\Core\Type\SubmitType;
  22. use Symfony\Component\Form\Form;
  23. use Symfony\Component\Form\FormBuilder;
  24. use Symfony\Component\Form\FormBuilderInterface;
  25. use Symfony\Component\Form\FormInterface;
  26. use Symfony\Component\HttpFoundation\JsonResponse;
  27. use Symfony\Component\HttpFoundation\RedirectResponse;
  28. use Symfony\Component\HttpFoundation\Request;
  29. use Symfony\Component\HttpFoundation\Response;
  30. use Symfony\Component\HttpKernel\Kernel;
  31. use Symfony\Component\PropertyAccess\PropertyAccess;
  32. use Symfony\Component\Routing\Annotation\Route;
  33. /**
  34.  * Common features needed in admin controllers.
  35.  *
  36.  * @internal
  37.  *
  38.  * @author Javier Eguiluz <javier.eguiluz@gmail.com>
  39.  */
  40. trait AdminControllerTrait
  41. {
  42.     /** @var array The full configuration of the entire backend */
  43.     protected $config;
  44.     /** @var array The full configuration of the current entity */
  45.     protected $entity = [];
  46.     /** @var Request The instance of the current Symfony request */
  47.     protected $request;
  48.     /** @var EntityManager|null The Doctrine entity manager for the current entity */
  49.     protected $em;
  50.     /**
  51.      * @Route("/", name="easyadmin")
  52.      *
  53.      * @param Request $request
  54.      *
  55.      * @return RedirectResponse|Response
  56.      *
  57.      * @throws ForbiddenActionException
  58.      */
  59.     public function indexAction(Request $request)
  60.     {
  61.         $this->initialize($request);
  62.         if (null === $request->query->get('entity')) {
  63.             return $this->redirectToBackendHomepage();
  64.         }
  65.         $action $request->query->get('action''list');
  66.         if (!$this->isActionAllowed($action)) {
  67.             throw new ForbiddenActionException(['action' => $action'entity_name' => $this->entity['name']]);
  68.         }
  69.         if (\in_array($action, ['show''edit''new'])) {
  70.             $id $this->request->query->get('id');
  71.             $entity $this->request->attributes->get('easyadmin')['item'];
  72.             $requiredPermission $this->entity[$action]['item_permission'];
  73.             $userHasPermission $this->get('easyadmin.security.authorization_checker')->isGranted($requiredPermission$entity);
  74.             if (false === $userHasPermission) {
  75.                 throw new NoPermissionException(['action' => $action'entity_name' => $this->entity['name'], 'entity_id' => $id]);
  76.             }
  77.         }
  78.         return $this->executeDynamicMethod($action.'<EntityName>Action');
  79.     }
  80.     /**
  81.      * Utility method which initializes the configuration of the entity on which
  82.      * the user is performing the action.
  83.      *
  84.      * @param Request $request
  85.      *
  86.      * @throws NoEntitiesConfiguredException
  87.      * @throws UndefinedEntityException
  88.      */
  89.     protected function initialize(Request $request)
  90.     {
  91.         $this->dispatch(EasyAdminEvents::PRE_INITIALIZE);
  92.         $this->config $this->get('easyadmin.config.manager')->getBackendConfig();
  93.         if (=== \count($this->config['entities'])) {
  94.             throw new NoEntitiesConfiguredException();
  95.         }
  96.         // this condition happens when accessing the backend homepage and before
  97.         // redirecting to the default page set as the homepage
  98.         if (null === $entityName $request->query->get('entity')) {
  99.             return;
  100.         }
  101.         if (!\array_key_exists($entityName$this->config['entities'])) {
  102.             throw new UndefinedEntityException(['entity_name' => $entityName]);
  103.         }
  104.         $this->entity $this->get('easyadmin.config.manager')->getEntityConfig($entityName);
  105.         $action $request->query->get('action''list');
  106.         if (!$request->query->has('sortField')) {
  107.             $sortField $this->entity[$action]['sort']['field'] ?? $this->entity['primary_key_field_name'];
  108.             $request->query->set('sortField'$sortField);
  109.         }
  110.         if (!$request->query->has('sortDirection')) {
  111.             $sortDirection $this->entity[$action]['sort']['direction'] ?? 'DESC';
  112.             $request->query->set('sortDirection'$sortDirection);
  113.         }
  114.         $this->em $this->getDoctrine()->getManagerForClass($this->entity['class']);
  115.         $this->request $request;
  116.         $this->dispatch(EasyAdminEvents::POST_INITIALIZE);
  117.     }
  118.     protected function dispatch($eventName, array $arguments = [])
  119.     {
  120.         $arguments array_replace([
  121.             'config' => $this->config,
  122.             'em' => $this->em,
  123.             'entity' => $this->entity,
  124.             'request' => $this->request,
  125.         ], $arguments);
  126.         $subject $arguments['paginator'] ?? $arguments['entity'];
  127.         $event = new GenericEvent($subject$arguments);
  128.         if (Kernel::VERSION_ID >= 40300) {
  129.             $this->get('event_dispatcher')->dispatch($event$eventName);
  130.         } else {
  131.             $this->get('event_dispatcher')->dispatch($eventName$event);
  132.         }
  133.     }
  134.     /**
  135.      * The method that returns the values displayed by an autocomplete field
  136.      * based on the user's input.
  137.      *
  138.      * @return JsonResponse
  139.      */
  140.     protected function autocompleteAction()
  141.     {
  142.         $results $this->get('easyadmin.autocomplete')->find(
  143.             $this->request->query->get('entity'),
  144.             $this->request->query->get('query'),
  145.             $this->request->query->get('page'1)
  146.         );
  147.         return new JsonResponse($results);
  148.     }
  149.     /**
  150.      * The method that is executed when the user performs a 'list' action on an entity.
  151.      *
  152.      * @return Response
  153.      */
  154.     protected function listAction()
  155.     {
  156.         $this->dispatch(EasyAdminEvents::PRE_LIST);
  157.         $fields $this->entity['list']['fields'];
  158.         $paginator $this->findAll($this->entity['class'], $this->request->query->get('page'1), $this->entity['list']['max_results'], $this->request->query->get('sortField'), $this->request->query->get('sortDirection'), $this->entity['list']['dql_filter']);
  159.         $this->dispatch(EasyAdminEvents::POST_LIST, ['paginator' => $paginator]);
  160.         $parameters = [
  161.             'paginator' => $paginator,
  162.             'fields' => $fields,
  163.             'batch_form' => $this->createBatchForm($this->entity['name'])->createView(),
  164.             'delete_form_template' => $this->createDeleteForm($this->entity['name'], '__id__')->createView(),
  165.         ];
  166.         return $this->executeDynamicMethod('render<EntityName>Template', ['list'$this->entity['templates']['list'], $parameters]);
  167.     }
  168.     /**
  169.      * The method that is executed when the user performs a 'edit' action on an entity.
  170.      *
  171.      * @return Response|RedirectResponse
  172.      *
  173.      * @throws \RuntimeException
  174.      */
  175.     protected function editAction()
  176.     {
  177.         $this->dispatch(EasyAdminEvents::PRE_EDIT);
  178.         $id $this->request->query->get('id');
  179.         $easyadmin $this->request->attributes->get('easyadmin');
  180.         $entity $easyadmin['item'];
  181.         if ($this->request->isXmlHttpRequest() && $property $this->request->query->get('property')) {
  182.             $newValue 'true' === mb_strtolower($this->request->query->get('newValue'));
  183.             $fieldsMetadata $this->entity['list']['fields'];
  184.             if (!isset($fieldsMetadata[$property]) || 'toggle' !== $fieldsMetadata[$property]['dataType']) {
  185.                 throw new \RuntimeException(sprintf('The type of the "%s" property is not "toggle".'$property));
  186.             }
  187.             $this->updateEntityProperty($entity$property$newValue);
  188.             // cast to integer instead of string to avoid sending empty responses for 'false'
  189.             return new Response((int) $newValue);
  190.         }
  191.         $fields $this->entity['edit']['fields'];
  192.         $editForm $this->executeDynamicMethod('create<EntityName>EditForm', [$entity$fields]);
  193.         $deleteForm $this->createDeleteForm($this->entity['name'], $id);
  194.         $editForm->handleRequest($this->request);
  195.         if ($editForm->isSubmitted() && $editForm->isValid()) {
  196.             $this->processUploadedFiles($editForm);
  197.             $this->dispatch(EasyAdminEvents::PRE_UPDATE, ['entity' => $entity]);
  198.             $this->executeDynamicMethod('update<EntityName>Entity', [$entity$editForm]);
  199.             $this->dispatch(EasyAdminEvents::POST_UPDATE, ['entity' => $entity]);
  200.             return $this->redirectToReferrer();
  201.         }
  202.         $this->dispatch(EasyAdminEvents::POST_EDIT);
  203.         $parameters = [
  204.             'form' => $editForm->createView(),
  205.             'entity_fields' => $fields,
  206.             'entity' => $entity,
  207.             'delete_form' => $deleteForm->createView(),
  208.         ];
  209.         return $this->executeDynamicMethod('render<EntityName>Template', ['edit'$this->entity['templates']['edit'], $parameters]);
  210.     }
  211.     /**
  212.      * The method that is executed when the user performs a 'show' action on an entity.
  213.      *
  214.      * @return Response
  215.      */
  216.     protected function showAction()
  217.     {
  218.         $this->dispatch(EasyAdminEvents::PRE_SHOW);
  219.         $id $this->request->query->get('id');
  220.         $easyadmin $this->request->attributes->get('easyadmin');
  221.         $entity $easyadmin['item'];
  222.         $fields $this->entity['show']['fields'];
  223.         $deleteForm $this->createDeleteForm($this->entity['name'], $id);
  224.         $this->dispatch(EasyAdminEvents::POST_SHOW, [
  225.             'deleteForm' => $deleteForm,
  226.             'fields' => $fields,
  227.             'entity' => $entity,
  228.         ]);
  229.         $parameters = [
  230.             'entity' => $entity,
  231.             'fields' => $fields,
  232.             'delete_form' => $deleteForm->createView(),
  233.         ];
  234.         return $this->executeDynamicMethod('render<EntityName>Template', ['show'$this->entity['templates']['show'], $parameters]);
  235.     }
  236.     /**
  237.      * The method that is executed when the user performs a 'new' action on an entity.
  238.      *
  239.      * @return Response|RedirectResponse
  240.      */
  241.     protected function newAction()
  242.     {
  243.         $this->dispatch(EasyAdminEvents::PRE_NEW);
  244.         $entity $this->executeDynamicMethod('createNew<EntityName>Entity');
  245.         $easyadmin $this->request->attributes->get('easyadmin');
  246.         $easyadmin['item'] = $entity;
  247.         $this->request->attributes->set('easyadmin'$easyadmin);
  248.         $fields $this->entity['new']['fields'];
  249.         $newForm $this->executeDynamicMethod('create<EntityName>NewForm', [$entity$fields]);
  250.         $newForm->handleRequest($this->request);
  251.         if ($newForm->isSubmitted() && $newForm->isValid()) {
  252.             $this->processUploadedFiles($newForm);
  253.             $this->dispatch(EasyAdminEvents::PRE_PERSIST, ['entity' => $entity]);
  254.             $this->executeDynamicMethod('persist<EntityName>Entity', [$entity$newForm]);
  255.             $this->dispatch(EasyAdminEvents::POST_PERSIST, ['entity' => $entity]);
  256.             return $this->redirectToReferrer();
  257.         }
  258.         $this->dispatch(EasyAdminEvents::POST_NEW, [
  259.             'entity_fields' => $fields,
  260.             'form' => $newForm,
  261.             'entity' => $entity,
  262.         ]);
  263.         $parameters = [
  264.             'form' => $newForm->createView(),
  265.             'entity_fields' => $fields,
  266.             'entity' => $entity,
  267.         ];
  268.         return $this->executeDynamicMethod('render<EntityName>Template', ['new'$this->entity['templates']['new'], $parameters]);
  269.     }
  270.     /**
  271.      * The method that is executed when the user performs a 'delete' action to
  272.      * remove any entity.
  273.      *
  274.      * @return RedirectResponse
  275.      *
  276.      * @throws EntityRemoveException
  277.      */
  278.     protected function deleteAction()
  279.     {
  280.         $this->dispatch(EasyAdminEvents::PRE_DELETE);
  281.         if ('DELETE' !== $this->request->getMethod()) {
  282.             return $this->redirect($this->generateUrl('easyadmin', ['action' => 'list''entity' => $this->entity['name']]));
  283.         }
  284.         $id $this->request->query->get('id');
  285.         $form $this->createDeleteForm($this->entity['name'], $id);
  286.         $form->handleRequest($this->request);
  287.         if ($form->isSubmitted() && $form->isValid()) {
  288.             $easyadmin $this->request->attributes->get('easyadmin');
  289.             $entity $easyadmin['item'];
  290.             $this->dispatch(EasyAdminEvents::PRE_REMOVE, ['entity' => $entity]);
  291.             try {
  292.                 $this->executeDynamicMethod('remove<EntityName>Entity', [$entity$form]);
  293.             } catch (ForeignKeyConstraintViolationException $e) {
  294.                 throw new EntityRemoveException(['entity_name' => $this->entity['name'], 'message' => $e->getMessage()]);
  295.             }
  296.             $this->dispatch(EasyAdminEvents::POST_REMOVE, ['entity' => $entity]);
  297.         }
  298.         $this->dispatch(EasyAdminEvents::POST_DELETE);
  299.         return $this->redirectToReferrer();
  300.     }
  301.     /**
  302.      * The method that is executed when the user performs a query on an entity.
  303.      *
  304.      * @return Response
  305.      */
  306.     protected function searchAction()
  307.     {
  308.         $this->dispatch(EasyAdminEvents::PRE_SEARCH);
  309.         $query trim($this->request->query->get('query'));
  310.         // if the search query is empty, redirect to the 'list' action
  311.         if ('' === $query) {
  312.             $queryParameters array_replace($this->request->query->all(), ['action' => 'list']);
  313.             unset($queryParameters['query']);
  314.             return $this->redirect($this->get('router')->generate('easyadmin'$queryParameters));
  315.         }
  316.         $searchableFields $this->entity['search']['fields'];
  317.         $paginator $this->findBy(
  318.             $this->entity['class'],
  319.             $query,
  320.             $searchableFields,
  321.             $this->request->query->get('page'1),
  322.             $this->entity['list']['max_results'],
  323.             $this->request->query->get('sortField'),
  324.             $this->request->query->get('sortDirection'),
  325.             $this->entity['search']['dql_filter']
  326.         );
  327.         $fields $this->entity['list']['fields'];
  328.         $this->dispatch(EasyAdminEvents::POST_SEARCH, [
  329.             'fields' => $fields,
  330.             'paginator' => $paginator,
  331.         ]);
  332.         $parameters = [
  333.             'paginator' => $paginator,
  334.             'fields' => $fields,
  335.             'batch_form' => $this->createBatchForm($this->entity['name'])->createView(),
  336.             'delete_form_template' => $this->createDeleteForm($this->entity['name'], '__id__')->createView(),
  337.         ];
  338.         return $this->executeDynamicMethod('render<EntityName>Template', ['search'$this->entity['templates']['list'], $parameters]);
  339.     }
  340.     /**
  341.      * The method that is executed when the user performs a 'batch' action to any entity.
  342.      */
  343.     protected function batchAction(): Response
  344.     {
  345.         $batchForm $this->createBatchForm($this->entity['name']);
  346.         $batchForm->handleRequest($this->request);
  347.         if ($batchForm->isSubmitted() && $batchForm->isValid()) {
  348.             $actionName $batchForm->get('name')->getData();
  349.             $actionIds $batchForm->get('ids')->getData();
  350.             $batchActionResult $this->executeDynamicMethod($actionName.'<EntityName>BatchAction', [$actionIds$batchForm]);
  351.             if ($batchActionResult instanceof Response) {
  352.                 return $batchActionResult;
  353.             }
  354.         }
  355.         return $this->redirectToReferrer();
  356.     }
  357.     protected function createBatchForm(string $entityName): FormInterface
  358.     {
  359.         return $this->get('form.factory')->createNamed('batch_form'EasyAdminBatchFormType::class, null, [
  360.             'action' => $this->generateUrl('easyadmin', ['action' => 'batch''entity' => $entityName]),
  361.             'entity' => $entityName,
  362.         ]);
  363.     }
  364.     protected function deleteBatchAction(array $ids): void
  365.     {
  366.         $class $this->entity['class'];
  367.         $primaryKey $this->entity['primary_key_field_name'];
  368.         $entities $this->em->getRepository($class)
  369.             ->findBy([$primaryKey => $ids]);
  370.         foreach ($entities as $entity) {
  371.             $this->em->remove($entity);
  372.         }
  373.         $this->em->flush();
  374.     }
  375.     /**
  376.      * The method that is executed when the user open the filters modal on an entity.
  377.      *
  378.      * @return Response
  379.      */
  380.     protected function filtersAction()
  381.     {
  382.         $filtersForm $this->createFiltersForm($this->entity['name']);
  383.         $filtersForm->handleRequest($this->request);
  384.         $easyadmin $this->request->attributes->get('easyadmin');
  385.         $easyadmin['filters']['applied'] = array_keys($this->request->get('filters', []));
  386.         $this->request->attributes->set('easyadmin'$easyadmin);
  387.         $parameters = [
  388.             'filters_form' => $filtersForm->createView(),
  389.             'referer_action' => $this->request->get('referer_action''list'),
  390.         ];
  391.         return $this->executeDynamicMethod('render<EntityName>Template', ['filters'$this->entity['templates']['filters'], $parameters]);
  392.     }
  393.     /**
  394.      * The method that apply all configured filter to the list QueryBuilder.
  395.      */
  396.     protected function filterQueryBuilder(QueryBuilder $queryBuilder): void
  397.     {
  398.         if (!$requestData $this->request->get('filters')) {
  399.             // Don't create the filters form if there is no filter applied
  400.             return;
  401.         }
  402.         /** @var Form $filtersForm */
  403.         $filtersForm $this->createFiltersForm($this->entity['name']);
  404.         $filtersForm->handleRequest($this->request);
  405.         if (!$filtersForm->isSubmitted()) {
  406.             return;
  407.         }
  408.         /** @var FilterRegistry $filterRegistry */
  409.         $filterRegistry $this->get('easyadmin.filter.registry');
  410.         $appliedFilters = [];
  411.         foreach ($filtersForm as $filterForm) {
  412.             $name $filterForm->getName();
  413.             if (!isset($requestData[$name])) {
  414.                 // this filter is not applied
  415.                 continue;
  416.             }
  417.             // if the form filter is not valid then
  418.             // we should not apply the filter
  419.             if (!$filterForm->isValid()) {
  420.                 continue;
  421.             }
  422.             // resolve the filter type related to this form field
  423.             $filterType $filterRegistry->resolveType($filterForm);
  424.             $metadata $this->entity['list']['filters'][$name] ?? [];
  425.             if (false !== $filterType->filter($queryBuilder$filterForm$metadata)) {
  426.                 $appliedFilters[] = $name;
  427.             }
  428.         }
  429.         $easyadmin $this->request->attributes->get('easyadmin');
  430.         $easyadmin['filters']['applied'] = $appliedFilters;
  431.         $this->request->attributes->set('easyadmin'$easyadmin);
  432.     }
  433.     protected function createFiltersForm(string $entityName): FormInterface
  434.     {
  435.         return $this->get('form.factory')->createNamed('filters'EasyAdminFiltersFormType::class, null, [
  436.             'method' => 'GET',
  437.             'entity' => $entityName,
  438.         ]);
  439.     }
  440.     /**
  441.      * Process all uploaded files in the current form if available.
  442.      */
  443.     protected function processUploadedFiles(FormInterface $form): void
  444.     {
  445.         /** @var FormInterface $child */
  446.         foreach ($form as $child) {
  447.             $config $child->getConfig();
  448.             if (!$config->getType()->getInnerType() instanceof FileUploadType) {
  449.                 if ($config->getCompound()) {
  450.                     $this->processUploadedFiles($child);
  451.                 }
  452.                 continue;
  453.             }
  454.             /** @var FileUploadState $state */
  455.             $state $config->getAttribute('state');
  456.             if (!$state->isModified()) {
  457.                 continue;
  458.             }
  459.             $uploadDelete $config->getOption('upload_delete');
  460.             if ($state->hasCurrentFiles() && ($state->isDelete() || (!$state->isAddAllowed() && $state->hasUploadedFiles()))) {
  461.                 foreach ($state->getCurrentFiles() as $file) {
  462.                     $uploadDelete($file);
  463.                 }
  464.                 $state->setCurrentFiles([]);
  465.             }
  466.             $filePaths = (array) $child->getData();
  467.             $uploadDir $config->getOption('upload_dir');
  468.             $uploadNew $config->getOption('upload_new');
  469.             foreach ($state->getUploadedFiles() as $index => $file) {
  470.                 $fileName mb_substr($filePaths[$index], mb_strlen($uploadDir));
  471.                 $uploadNew($file$uploadDir$fileName);
  472.             }
  473.         }
  474.     }
  475.     /**
  476.      * It updates the value of some property of some entity to the new given value.
  477.      *
  478.      * @param mixed  $entity   The instance of the entity to modify
  479.      * @param string $property The name of the property to change
  480.      * @param bool   $value    The new value of the property
  481.      *
  482.      * @throws \RuntimeException
  483.      */
  484.     protected function updateEntityProperty($entity$property$value)
  485.     {
  486.         $entityConfig $this->entity;
  487.         if (!$this->get('easyadmin.property_accessor')->isWritable($entity$property)) {
  488.             throw new \RuntimeException(sprintf('The "%s" property of the "%s" entity is not writable.'$property$entityConfig['name']));
  489.         }
  490.         $this->get('easyadmin.property_accessor')->setValue($entity$property$value);
  491.         $this->dispatch(EasyAdminEvents::PRE_UPDATE, ['entity' => $entity'property' => $property'newValue' => $value]);
  492.         $this->executeDynamicMethod('update<EntityName>Entity', [$entity]);
  493.         $this->dispatch(EasyAdminEvents::POST_UPDATE, ['entity' => $entity'property' => $property'newValue' => $value]);
  494.         $this->dispatch(EasyAdminEvents::POST_EDIT);
  495.     }
  496.     /**
  497.      * Creates a new object of the current managed entity.
  498.      * This method is mostly here for override convenience, because it allows
  499.      * the user to use his own method to customize the entity instantiation.
  500.      *
  501.      * @return object
  502.      */
  503.     protected function createNewEntity()
  504.     {
  505.         $entityFullyQualifiedClassName $this->entity['class'];
  506.         return new $entityFullyQualifiedClassName();
  507.     }
  508.     /**
  509.      * Allows applications to modify the entity associated with the item being
  510.      * created while persisting it.
  511.      *
  512.      * @param object $entity
  513.      */
  514.     protected function persistEntity($entity)
  515.     {
  516.         $this->em->persist($entity);
  517.         $this->em->flush();
  518.     }
  519.     /**
  520.      * Allows applications to modify the entity associated with the item being
  521.      * edited before updating it.
  522.      *
  523.      * @param object $entity
  524.      */
  525.     protected function updateEntity($entity)
  526.     {
  527.         $this->em->persist($entity);
  528.         $this->em->flush();
  529.     }
  530.     /**
  531.      * Allows applications to modify the entity associated with the item being
  532.      * deleted before removing it.
  533.      *
  534.      * @param object $entity
  535.      */
  536.     protected function removeEntity($entity)
  537.     {
  538.         $this->em->remove($entity);
  539.         $this->em->flush();
  540.     }
  541.     /**
  542.      * Performs a database query to get all the records related to the given
  543.      * entity. It supports pagination and field sorting.
  544.      *
  545.      * @param string      $entityClass
  546.      * @param int         $page
  547.      * @param int         $maxPerPage
  548.      * @param string|null $sortField
  549.      * @param string|null $sortDirection
  550.      * @param string|null $dqlFilter
  551.      *
  552.      * @return Pagerfanta The paginated query results
  553.      */
  554.     protected function findAll($entityClass$page 1$maxPerPage 15$sortField null$sortDirection null$dqlFilter null)
  555.     {
  556.         if (null === $sortDirection || !\in_array(strtoupper($sortDirection), ['ASC''DESC'])) {
  557.             $sortDirection 'DESC';
  558.         }
  559.         $queryBuilder $this->executeDynamicMethod('create<EntityName>ListQueryBuilder', [$entityClass$sortDirection$sortField$dqlFilter]);
  560.         $this->filterQueryBuilder($queryBuilder);
  561.         $this->dispatch(EasyAdminEvents::POST_LIST_QUERY_BUILDER, [
  562.             'query_builder' => $queryBuilder,
  563.             'sort_field' => $sortField,
  564.             'sort_direction' => $sortDirection,
  565.         ]);
  566.         return $this->get('easyadmin.paginator')->createOrmPaginator($queryBuilder$page$maxPerPage);
  567.     }
  568.     /**
  569.      * Creates Query Builder instance for all the records.
  570.      *
  571.      * @param string      $entityClass
  572.      * @param string      $sortDirection
  573.      * @param string|null $sortField
  574.      * @param string|null $dqlFilter
  575.      *
  576.      * @return QueryBuilder The Query Builder instance
  577.      */
  578.     protected function createListQueryBuilder($entityClass$sortDirection$sortField null$dqlFilter null)
  579.     {
  580.         return $this->get('easyadmin.query_builder')->createListQueryBuilder($this->entity$sortField$sortDirection$dqlFilter);
  581.     }
  582.     /**
  583.      * Performs a database query based on the search query provided by the user.
  584.      * It supports pagination and field sorting.
  585.      *
  586.      * @param string      $entityClass
  587.      * @param string      $searchQuery
  588.      * @param array       $searchableFields
  589.      * @param int         $page
  590.      * @param int         $maxPerPage
  591.      * @param string|null $sortField
  592.      * @param string|null $sortDirection
  593.      * @param string|null $dqlFilter
  594.      *
  595.      * @return Pagerfanta The paginated query results
  596.      */
  597.     protected function findBy($entityClass$searchQuery, array $searchableFields$page 1$maxPerPage 15$sortField null$sortDirection null$dqlFilter null)
  598.     {
  599.         if (empty($sortDirection) || !\in_array(strtoupper($sortDirection), ['ASC''DESC'])) {
  600.             $sortDirection 'DESC';
  601.         }
  602.         $queryBuilder $this->executeDynamicMethod('create<EntityName>SearchQueryBuilder', [$entityClass$searchQuery$searchableFields$sortField$sortDirection$dqlFilter]);
  603.         $this->filterQueryBuilder($queryBuilder);
  604.         $this->dispatch(EasyAdminEvents::POST_SEARCH_QUERY_BUILDER, [
  605.             'query_builder' => $queryBuilder,
  606.             'search_query' => $searchQuery,
  607.             'searchable_fields' => $searchableFields,
  608.         ]);
  609.         return $this->get('easyadmin.paginator')->createOrmPaginator($queryBuilder$page$maxPerPage);
  610.     }
  611.     /**
  612.      * Creates Query Builder instance for search query.
  613.      *
  614.      * @param string      $entityClass
  615.      * @param string      $searchQuery
  616.      * @param array       $searchableFields
  617.      * @param string|null $sortField
  618.      * @param string|null $sortDirection
  619.      * @param string|null $dqlFilter
  620.      *
  621.      * @return QueryBuilder The Query Builder instance
  622.      */
  623.     protected function createSearchQueryBuilder($entityClass$searchQuery, array $searchableFields$sortField null$sortDirection null$dqlFilter null)
  624.     {
  625.         return $this->get('easyadmin.query_builder')->createSearchQueryBuilder($this->entity$searchQuery$sortField$sortDirection$dqlFilter);
  626.     }
  627.     /**
  628.      * Creates the form used to edit an entity.
  629.      *
  630.      * @param object $entity
  631.      * @param array  $entityProperties
  632.      *
  633.      * @return Form|FormInterface
  634.      */
  635.     protected function createEditForm($entity, array $entityProperties)
  636.     {
  637.         return $this->createEntityForm($entity$entityProperties'edit');
  638.     }
  639.     /**
  640.      * Creates the form used to create an entity.
  641.      *
  642.      * @param object $entity
  643.      * @param array  $entityProperties
  644.      *
  645.      * @return Form|FormInterface
  646.      */
  647.     protected function createNewForm($entity, array $entityProperties)
  648.     {
  649.         return $this->createEntityForm($entity$entityProperties'new');
  650.     }
  651.     /**
  652.      * Creates the form builder of the form used to create or edit the given entity.
  653.      *
  654.      * @param object $entity
  655.      * @param string $view   The name of the view where this form is used ('new' or 'edit')
  656.      *
  657.      * @return FormBuilder
  658.      */
  659.     protected function createEntityFormBuilder($entity$view)
  660.     {
  661.         $formOptions $this->executeDynamicMethod('get<EntityName>EntityFormOptions', [$entity$view]);
  662.         return $this->get('form.factory')->createNamedBuilder(mb_strtolower($this->entity['name']), EasyAdminFormType::class, $entity$formOptions);
  663.     }
  664.     /**
  665.      * Retrieves the list of form options before sending them to the form builder.
  666.      * This allows adding dynamic logic to the default form options.
  667.      *
  668.      * @param object $entity
  669.      * @param string $view
  670.      *
  671.      * @return array
  672.      */
  673.     protected function getEntityFormOptions($entity$view)
  674.     {
  675.         $formOptions $this->entity[$view]['form_options'];
  676.         $formOptions['entity'] = $this->entity['name'];
  677.         $formOptions['view'] = $view;
  678.         return $formOptions;
  679.     }
  680.     /**
  681.      * Creates the form object used to create or edit the given entity.
  682.      *
  683.      * @param object $entity
  684.      * @param array  $entityProperties
  685.      * @param string $view
  686.      *
  687.      * @return FormInterface
  688.      *
  689.      * @throws \Exception
  690.      */
  691.     protected function createEntityForm($entity, array $entityProperties$view)
  692.     {
  693.         if (method_exists($this$customMethodName 'create'.$this->entity['name'].'EntityForm')) {
  694.             $form $this->{$customMethodName}($entity$entityProperties$view);
  695.             if (!$form instanceof FormInterface) {
  696.                 throw new \UnexpectedValueException(sprintf('The "%s" method must return a FormInterface, "%s" given.'$customMethodName, \is_object($form) ? \get_class($form) : \gettype($form)));
  697.             }
  698.             return $form;
  699.         }
  700.         $formBuilder $this->executeDynamicMethod('create<EntityName>EntityFormBuilder', [$entity$view]);
  701.         if (!$formBuilder instanceof FormBuilderInterface) {
  702.             throw new \UnexpectedValueException(sprintf('The "%s" method must return a FormBuilderInterface, "%s" given.''createEntityForm', \is_object($formBuilder) ? \get_class($formBuilder) : \gettype($formBuilder)));
  703.         }
  704.         return $formBuilder->getForm();
  705.     }
  706.     /**
  707.      * Creates the form used to delete an entity. It must be a form because
  708.      * the deletion of the entity are always performed with the 'DELETE' HTTP method,
  709.      * which requires a form to work in the current browsers.
  710.      *
  711.      * @param string     $entityName
  712.      * @param int|string $entityId   When reusing the delete form for multiple entities, a pattern string is passed instead of an integer
  713.      *
  714.      * @return Form|FormInterface
  715.      */
  716.     protected function createDeleteForm($entityName$entityId)
  717.     {
  718.         /** @var FormBuilder $formBuilder */
  719.         $formBuilder $this->get('form.factory')->createNamedBuilder('delete_form')
  720.             ->setAction($this->generateUrl('easyadmin', ['action' => 'delete''entity' => $entityName'id' => $entityId]))
  721.             ->setMethod('DELETE')
  722.         ;
  723.         $formBuilder->add('submit'SubmitType::class, ['label' => 'delete_modal.action''translation_domain' => 'EasyAdminBundle']);
  724.         // needed to avoid submitting empty delete forms (see issue #1409)
  725.         $formBuilder->add('_easyadmin_delete_flag'HiddenType::class, ['data' => '1']);
  726.         return $formBuilder->getForm();
  727.     }
  728.     /**
  729.      * Utility method that checks if the given action is allowed for
  730.      * the current entity.
  731.      *
  732.      * @param string $actionName
  733.      *
  734.      * @return bool
  735.      */
  736.     protected function isActionAllowed($actionName)
  737.     {
  738.         return false === \in_array($actionName$this->entity['disabled_actions'], true);
  739.     }
  740.     /**
  741.      * Given a method name pattern, it looks for the customized version of that
  742.      * method (based on the entity name) and executes it. If the custom method
  743.      * does not exist, it executes the regular method.
  744.      *
  745.      * For example:
  746.      *   executeDynamicMethod('create<EntityName>Entity') and the entity name is 'User'
  747.      *   if 'createUserEntity()' exists, execute it; otherwise execute 'createEntity()'
  748.      *
  749.      * @param string $methodNamePattern The pattern of the method name (dynamic parts are enclosed with <> angle brackets)
  750.      * @param array  $arguments         The arguments passed to the executed method
  751.      *
  752.      * @return mixed
  753.      */
  754.     protected function executeDynamicMethod($methodNamePattern, array $arguments = [])
  755.     {
  756.         $methodName str_replace('<EntityName>'$this->entity['name'], $methodNamePattern);
  757.         if (!\is_callable([$this$methodName])) {
  758.             $methodName str_replace('<EntityName>'''$methodNamePattern);
  759.         }
  760.         if (!method_exists($this$methodName)) {
  761.             throw new \BadMethodCallException(sprintf('The "%s()" method does not exist in the %s class'$methodName, \get_class($this)));
  762.         }
  763.         return \call_user_func_array([$this$methodName], $arguments);
  764.     }
  765.     /**
  766.      * Generates the backend homepage and redirects to it.
  767.      */
  768.     protected function redirectToBackendHomepage()
  769.     {
  770.         $homepageConfig $this->config['homepage'];
  771.         $url $homepageConfig['url'] ?? $this->get('router')->generate($homepageConfig['route'], $homepageConfig['params']);
  772.         return $this->redirect($url);
  773.     }
  774.     /**
  775.      * @return RedirectResponse
  776.      */
  777.     protected function redirectToReferrer()
  778.     {
  779.         $refererUrl $this->request->query->get('referer''');
  780.         $refererAction $this->request->query->get('action');
  781.         // 1. redirect to list if possible
  782.         if ($this->isActionAllowed('list')) {
  783.             if (!empty($refererUrl)) {
  784.                 return $this->redirect(urldecode($refererUrl));
  785.             }
  786.             return $this->redirectToRoute('easyadmin', [
  787.                 'action' => 'list',
  788.                 'entity' => $this->entity['name'],
  789.                 'menuIndex' => $this->request->query->get('menuIndex'),
  790.                 'submenuIndex' => $this->request->query->get('submenuIndex'),
  791.             ]);
  792.         }
  793.         // 2. from new|edit action, redirect to edit if possible
  794.         if (\in_array($refererAction, ['new''edit']) && $this->isActionAllowed('edit')) {
  795.             return $this->redirectToRoute('easyadmin', [
  796.                 'action' => 'edit',
  797.                 'entity' => $this->entity['name'],
  798.                 'menuIndex' => $this->request->query->get('menuIndex'),
  799.                 'submenuIndex' => $this->request->query->get('submenuIndex'),
  800.                 'id' => ('new' === $refererAction)
  801.                     ? PropertyAccess::createPropertyAccessor()->getValue($this->request->attributes->get('easyadmin')['item'], $this->entity['primary_key_field_name'])
  802.                     : $this->request->query->get('id'),
  803.             ]);
  804.         }
  805.         // 3. from new action, redirect to new if possible
  806.         if ('new' === $refererAction && $this->isActionAllowed('new')) {
  807.             return $this->redirectToRoute('easyadmin', [
  808.                 'action' => 'new',
  809.                 'entity' => $this->entity['name'],
  810.                 'menuIndex' => $this->request->query->get('menuIndex'),
  811.                 'submenuIndex' => $this->request->query->get('submenuIndex'),
  812.             ]);
  813.         }
  814.         return $this->redirectToBackendHomepage();
  815.     }
  816.     /**
  817.      * Used to add/modify/remove parameters before passing them to the Twig template.
  818.      * Instead of defining a render method per action (list, show, search, etc.) use
  819.      * the $actionName argument to discriminate between actions.
  820.      *
  821.      * @param string $actionName   The name of the current action (list, show, new, etc.)
  822.      * @param string $templatePath The path of the Twig template to render
  823.      * @param array  $parameters   The parameters passed to the template
  824.      *
  825.      * @return Response
  826.      */
  827.     protected function renderTemplate($actionName$templatePath, array $parameters = [])
  828.     {
  829.         return $this->render($templatePath$parameters);
  830.     }
  831. }