vendor/sonata-project/admin-bundle/src/Admin/AbstractAdmin.php line 2545

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4.  * This file is part of the Sonata Project package.
  5.  *
  6.  * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
  7.  *
  8.  * For the full copyright and license information, please view the LICENSE
  9.  * file that was distributed with this source code.
  10.  */
  11. namespace Sonata\AdminBundle\Admin;
  12. use Knp\Menu\ItemInterface;
  13. use Sonata\AdminBundle\BCLayer\BCHelper;
  14. use Sonata\AdminBundle\Datagrid\DatagridInterface;
  15. use Sonata\AdminBundle\Datagrid\DatagridMapper;
  16. use Sonata\AdminBundle\Datagrid\ListMapper;
  17. use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
  18. use Sonata\AdminBundle\DependencyInjection\Admin\AbstractTaggedAdmin;
  19. use Sonata\AdminBundle\Exception\AdminClassNotFoundException;
  20. use Sonata\AdminBundle\FieldDescription\FieldDescriptionCollection;
  21. use Sonata\AdminBundle\FieldDescription\FieldDescriptionInterface;
  22. use Sonata\AdminBundle\Form\FormMapper;
  23. use Sonata\AdminBundle\Form\Type\ModelHiddenType;
  24. use Sonata\AdminBundle\Manipulator\ObjectManipulator;
  25. use Sonata\AdminBundle\Model\ProxyResolverInterface;
  26. use Sonata\AdminBundle\Object\Metadata;
  27. use Sonata\AdminBundle\Object\MetadataInterface;
  28. use Sonata\AdminBundle\Route\RouteCollection;
  29. use Sonata\AdminBundle\Route\RouteCollectionInterface;
  30. use Sonata\AdminBundle\Security\Acl\Permission\AdminPermissionMap;
  31. use Sonata\AdminBundle\Security\Handler\AclSecurityHandlerInterface;
  32. use Sonata\AdminBundle\Show\ShowMapper;
  33. use Sonata\AdminBundle\Util\Instantiator;
  34. use Sonata\AdminBundle\Util\ParametersManipulator;
  35. use Symfony\Component\Form\Extension\Core\Type\HiddenType;
  36. use Symfony\Component\Form\FormBuilderInterface;
  37. use Symfony\Component\Form\FormEvent;
  38. use Symfony\Component\Form\FormEvents;
  39. use Symfony\Component\Form\FormInterface;
  40. use Symfony\Component\HttpFoundation\InputBag;
  41. use Symfony\Component\HttpFoundation\ParameterBag;
  42. use Symfony\Component\HttpFoundation\Request;
  43. use Symfony\Component\PropertyAccess\Exception\AccessException;
  44. use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException;
  45. use Symfony\Component\PropertyAccess\PropertyAccess;
  46. use Symfony\Component\Routing\Generator\UrlGeneratorInterface as RoutingUrlGeneratorInterface;
  47. use Symfony\Component\Security\Acl\Model\DomainObjectInterface;
  48. use Symfony\Component\Security\Core\Exception\AccessDeniedException;
  49. /**
  50.  * @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
  51.  *
  52.  * @phpstan-template T of object
  53.  * @phpstan-extends AbstractTaggedAdmin<T>
  54.  * @phpstan-implements AdminInterface<T>
  55.  */
  56. abstract class AbstractAdmin extends AbstractTaggedAdmin implements AdminInterfaceDomainObjectInterfaceAdminTreeInterface
  57. {
  58.     // NEXT_MAJOR: Remove the CONTEXT constants.
  59.     /** @deprecated */
  60.     public const CONTEXT_MENU 'menu';
  61.     /** @deprecated */
  62.     public const CONTEXT_DASHBOARD 'dashboard';
  63.     public const CLASS_REGEX =
  64.         '@
  65.         (?:([A-Za-z0-9]*)\\\)?        # vendor name / app name
  66.         (Bundle\\\)?                  # optional bundle directory
  67.         ([A-Za-z0-9]+?)(?:Bundle)?\\\ # bundle name, with optional suffix
  68.         (
  69.             Entity|Document|Model|PHPCR|CouchDocument|Phpcr|
  70.             Doctrine\\\Orm|Doctrine\\\Phpcr|Doctrine\\\MongoDB|Doctrine\\\CouchDB
  71.         )\\\(.*)@x';
  72.     private const ACTION_TREE 1;
  73.     private const ACTION_SHOW 2;
  74.     private const ACTION_EDIT 4;
  75.     private const ACTION_DELETE 8;
  76.     private const ACTION_ACL 16;
  77.     private const ACTION_HISTORY 32;
  78.     private const ACTION_LIST 64;
  79.     private const ACTION_BATCH 128;
  80.     private const INTERNAL_ACTIONS = [
  81.         'tree' => self::ACTION_TREE,
  82.         'show' => self::ACTION_SHOW,
  83.         'edit' => self::ACTION_EDIT,
  84.         'delete' => self::ACTION_DELETE,
  85.         'acl' => self::ACTION_ACL,
  86.         'history' => self::ACTION_HISTORY,
  87.         'list' => self::ACTION_LIST,
  88.         'batch' => self::ACTION_BATCH,
  89.     ];
  90.     private const MASK_OF_ACTION_CREATE self::ACTION_TREE self::ACTION_SHOW self::ACTION_EDIT self::ACTION_DELETE self::ACTION_LIST self::ACTION_BATCH;
  91.     private const MASK_OF_ACTION_SHOW self::ACTION_EDIT self::ACTION_HISTORY self::ACTION_ACL;
  92.     private const MASK_OF_ACTION_EDIT self::ACTION_SHOW self::ACTION_DELETE self::ACTION_ACL self::ACTION_HISTORY;
  93.     private const MASK_OF_ACTION_HISTORY self::ACTION_SHOW self::ACTION_EDIT self::ACTION_ACL;
  94.     private const MASK_OF_ACTION_ACL self::ACTION_EDIT self::ACTION_HISTORY;
  95.     private const MASK_OF_ACTION_LIST self::ACTION_SHOW self::ACTION_EDIT self::ACTION_DELETE self::ACTION_ACL self::ACTION_BATCH;
  96.     private const MASK_OF_ACTIONS_USING_OBJECT self::MASK_OF_ACTION_SHOW self::MASK_OF_ACTION_EDIT self::MASK_OF_ACTION_HISTORY self::MASK_OF_ACTION_ACL;
  97.     private const DEFAULT_LIST_PER_PAGE_RESULTS 25;
  98.     private const DEFAULT_LIST_PER_PAGE_OPTIONS = [102550100250];
  99.     /**
  100.      * @deprecated since sonata-project/admin-bundle 4.15, will be removed in 5.0.
  101.      *
  102.      * The base route name used to generate the routing information.
  103.      *
  104.      * @var string|null
  105.      */
  106.     protected $baseRouteName;
  107.     /**
  108.      * @deprecated since sonata-project/admin-bundle 4.15, will be removed in 5.0.
  109.      *
  110.      * The base route pattern used to generate the routing information.
  111.      *
  112.      * @var string|null
  113.      */
  114.     protected $baseRoutePattern;
  115.     /**
  116.      * The label class name  (used in the title/breadcrumb ...).
  117.      *
  118.      * @var string|null
  119.      */
  120.     protected $classnameLabel;
  121.     /**
  122.      * Setting to true will enable preview mode for
  123.      * the entity and show a preview button in the
  124.      * edit/create forms.
  125.      *
  126.      * @var bool
  127.      */
  128.     protected $supportsPreviewMode false;
  129.     /**
  130.      * The list FieldDescription constructed from the configureListField method.
  131.      *
  132.      * @var array<string, FieldDescriptionInterface>
  133.      */
  134.     private array $listFieldDescriptions = [];
  135.     /**
  136.      * The show FieldDescription constructed from the configureShowFields method.
  137.      *
  138.      * @var FieldDescriptionInterface[]
  139.      */
  140.     private array $showFieldDescriptions = [];
  141.     /**
  142.      * The list FieldDescription constructed from the configureFormField method.
  143.      *
  144.      * @var FieldDescriptionInterface[]
  145.      */
  146.     private array $formFieldDescriptions = [];
  147.     /**
  148.      * The filter FieldDescription constructed from the configureFilterField method.
  149.      *
  150.      * @var FieldDescriptionInterface[]
  151.      */
  152.     private array $filterFieldDescriptions = [];
  153.     /**
  154.      * The maximum number of page numbers to display in the list.
  155.      */
  156.     private int $maxPageLinks 25;
  157.     /**
  158.      * The translation domain to be used to translate messages.
  159.      */
  160.     private string $translationDomain 'messages';
  161.     /**
  162.      * Array of routes related to this admin.
  163.      */
  164.     private ?RouteCollectionInterface $routes null;
  165.     /**
  166.      * The subject only set in edit/update/create mode.
  167.      *
  168.      * @phpstan-var T|null
  169.      */
  170.     private ?object $subject null;
  171.     /**
  172.      * Define a Collection of child admin, ie /admin/order/{id}/order-element/{childId}.
  173.      *
  174.      * @var array<string, AdminInterface<object>>
  175.      */
  176.     private array $children = [];
  177.     /**
  178.      * Reference the parent admin.
  179.      *
  180.      * @var AdminInterface<object>|null
  181.      */
  182.     private ?AdminInterface $parent null;
  183.     /**
  184.      * Reference the parent FieldDescription related to this admin
  185.      * only set for FieldDescription which is associated to an Sub Admin instance.
  186.      */
  187.     private ?FieldDescriptionInterface $parentFieldDescription null;
  188.     /**
  189.      * If true then the current admin is part of the nested admin set (from the url).
  190.      */
  191.     private bool $currentChild false;
  192.     /**
  193.      * The uniqId is used to avoid clashing with 2 admin related to the code
  194.      * ie: a Block linked to a Block.
  195.      */
  196.     private ?string $uniqId null;
  197.     /**
  198.      * The current request object.
  199.      */
  200.     private ?Request $request null;
  201.     /**
  202.      * @phpstan-var DatagridInterface<ProxyQueryInterface<T>>|null
  203.      */
  204.     private ?DatagridInterface $datagrid null;
  205.     private ?ItemInterface $menu null;
  206.     /**
  207.      * @var string[]
  208.      */
  209.     private array $formTheme = [];
  210.     /**
  211.      * @var string[]
  212.      */
  213.     private array $filterTheme = [];
  214.     /**
  215.      * @var AdminExtensionInterface[]
  216.      *
  217.      * @phpstan-var array<AdminExtensionInterface<T>>
  218.      */
  219.     private array $extensions = [];
  220.     /**
  221.      * @var array<string, bool>
  222.      */
  223.     private array $cacheIsGranted = [];
  224.     /**
  225.      * @var array<string, string|null>
  226.      */
  227.     private array $parentAssociationMapping = [];
  228.     /**
  229.      * The subclasses supported by the admin class.
  230.      *
  231.      * @var string[]
  232.      *
  233.      * @phpstan-var array<string, class-string<T>>
  234.      */
  235.     private array $subClasses = [];
  236.     /**
  237.      * The list collection.
  238.      *
  239.      * @var FieldDescriptionCollection<FieldDescriptionInterface>|null
  240.      */
  241.     private ?FieldDescriptionCollection $list null;
  242.     /**
  243.      * @var FieldDescriptionCollection<FieldDescriptionInterface>|null
  244.      */
  245.     private ?FieldDescriptionCollection $show null;
  246.     private ?FormInterface $form null;
  247.     /**
  248.      * The cached base route name.
  249.      */
  250.     private ?string $cachedBaseRouteName null;
  251.     /**
  252.      * The cached base route pattern.
  253.      */
  254.     private ?string $cachedBaseRoutePattern null;
  255.     /**
  256.      * The form group disposition.
  257.      *
  258.      * @var array<string, array<string, mixed>>
  259.      */
  260.     private array $formGroups = [];
  261.     /**
  262.      * The form tabs disposition.
  263.      *
  264.      * @var array<string, array<string, mixed>>
  265.      */
  266.     private array $formTabs = [];
  267.     /**
  268.      * The view group disposition.
  269.      *
  270.      * @var array<string, array<string, mixed>>
  271.      */
  272.     private array $showGroups = [];
  273.     /**
  274.      * The view tab disposition.
  275.      *
  276.      * @var array<string, array<string, mixed>>
  277.      */
  278.     private array $showTabs = [];
  279.     /**
  280.      * @var array<string, bool>
  281.      */
  282.     private array $loaded = [
  283.         'routes' => false,
  284.         'tab_menu' => false,
  285.         'show' => false,
  286.         'list' => false,
  287.         'form' => false,
  288.         'datagrid' => false,
  289.     ];
  290.     public function getExportFormats(): array
  291.     {
  292.         return [];
  293.     }
  294.     final public function getExportFields(): array
  295.     {
  296.         $fields $this->configureExportFields();
  297.         foreach ($this->getExtensions() as $extension) {
  298.             $fields $extension->configureExportFields($this$fields);
  299.         }
  300.         return $fields;
  301.     }
  302.     final public function getDataSourceIterator(): \Iterator
  303.     {
  304.         $datagrid $this->getDatagrid();
  305.         $datagrid->buildPager();
  306.         $fields = [];
  307.         foreach ($this->getExportFields() as $key => $field) {
  308.             if (!\is_string($key)) {
  309.                 $label $this->getTranslationLabel($field'export''label');
  310.                 $key $this->getTranslator()->trans($label, [], $this->getTranslationDomain());
  311.             }
  312.             $fields[$key] = $field;
  313.         }
  314.         $query $datagrid->getQuery();
  315.         return $this->getDataSource()->createIterator($query$fields);
  316.     }
  317.     final public function initialize(): void
  318.     {
  319.         if (null === $this->classnameLabel) {
  320.             $namespaceSeparatorPos strrpos($this->getClass(), '\\');
  321.             $this->classnameLabel false !== $namespaceSeparatorPos
  322.                 substr($this->getClass(), $namespaceSeparatorPos 1)
  323.                 : $this->getClass();
  324.         }
  325.         $this->configure();
  326.         foreach ($this->getExtensions() as $extension) {
  327.             $extension->configure($this);
  328.         }
  329.     }
  330.     final public function update(object $object): object
  331.     {
  332.         $this->preUpdate($object);
  333.         foreach ($this->getExtensions() as $extension) {
  334.             $extension->preUpdate($this$object);
  335.         }
  336.         $this->getModelManager()->update($object);
  337.         $this->postUpdate($object);
  338.         foreach ($this->getExtensions() as $extension) {
  339.             $extension->postUpdate($this$object);
  340.         }
  341.         return $object;
  342.     }
  343.     final public function create(object $object): object
  344.     {
  345.         $this->prePersist($object);
  346.         foreach ($this->getExtensions() as $extension) {
  347.             $extension->prePersist($this$object);
  348.         }
  349.         $this->getModelManager()->create($object);
  350.         $this->postPersist($object);
  351.         foreach ($this->getExtensions() as $extension) {
  352.             $extension->postPersist($this$object);
  353.         }
  354.         $this->createObjectSecurity($object);
  355.         return $object;
  356.     }
  357.     final public function delete(object $object): void
  358.     {
  359.         $this->preRemove($object);
  360.         foreach ($this->getExtensions() as $extension) {
  361.             $extension->preRemove($this$object);
  362.         }
  363.         $this->getSecurityHandler()->deleteObjectSecurity($this$object);
  364.         $this->getModelManager()->delete($object);
  365.         $this->postRemove($object);
  366.         foreach ($this->getExtensions() as $extension) {
  367.             $extension->postRemove($this$object);
  368.         }
  369.     }
  370.     public function preBatchAction(string $actionNameProxyQueryInterface $query, array &$idxbool $allElements false): void
  371.     {
  372.     }
  373.     final public function getDefaultFilterParameters(): array
  374.     {
  375.         return array_merge(
  376.             $this->getDefaultSortValues(),
  377.             $this->getDefaultFilterValues()
  378.         );
  379.     }
  380.     final public function getFilterParameters(): array
  381.     {
  382.         $parameters $this->getDefaultFilterParameters();
  383.         // build the values array
  384.         if ($this->hasRequest()) {
  385.             /** @var InputBag|ParameterBag $bag */
  386.             $bag $this->getRequest()->query;
  387.             if ($bag instanceof InputBag) {
  388.                 // symfony 5.1+
  389.                 $filters $bag->all('filter');
  390.             } else {
  391.                 $filters $bag->get('filter', []);
  392.                 \assert(\is_array($filters));
  393.             }
  394.             if (isset($filters[DatagridInterface::PAGE])) {
  395.                 $filters[DatagridInterface::PAGE] = (int) $filters[DatagridInterface::PAGE];
  396.             }
  397.             if (isset($filters[DatagridInterface::PER_PAGE])) {
  398.                 $filters[DatagridInterface::PER_PAGE] = (int) $filters[DatagridInterface::PER_PAGE];
  399.             }
  400.             // if filter persistence is configured
  401.             if ($this->hasFilterPersister()) {
  402.                 // if reset filters is asked, remove from storage
  403.                 if ('reset' === $this->getRequest()->query->get('filters')) {
  404.                     $this->getFilterPersister()->reset($this->getCode());
  405.                 }
  406.                 // if no filters, fetch from storage
  407.                 // otherwise save to storage
  408.                 if ([] === $filters) {
  409.                     $filters $this->getFilterPersister()->get($this->getCode());
  410.                 } else {
  411.                     $this->getFilterPersister()->set($this->getCode(), $filters);
  412.                 }
  413.             }
  414.             $parameters ParametersManipulator::merge($parameters$filters);
  415.             // always force the parent value
  416.             if ($this->isChild()) {
  417.                 $parentAssociationMapping $this->getParentAssociationMapping();
  418.                 if (null !== $parentAssociationMapping) {
  419.                     $name str_replace('.''__'$parentAssociationMapping);
  420.                     $parameters[$name] = ['value' => $this->getRequest()->get($this->getParent()->getIdParameter())];
  421.                 }
  422.             }
  423.         }
  424.         if (
  425.             !isset($parameters[DatagridInterface::PER_PAGE])
  426.             || !\is_int($parameters[DatagridInterface::PER_PAGE])
  427.             || !$this->determinedPerPageValue($parameters[DatagridInterface::PER_PAGE])
  428.         ) {
  429.             $parameters[DatagridInterface::PER_PAGE] = $this->getMaxPerPage();
  430.         }
  431.         $parameters $this->configureFilterParameters($parameters);
  432.         foreach ($this->getExtensions() as $extension) {
  433.             $parameters $extension->configureFilterParameters($this$parameters);
  434.         }
  435.         return $parameters;
  436.     }
  437.     /**
  438.      * Returns the name of the parent related field, so the field can be use to set the default
  439.      * value (ie the parent object) or to filter the object.
  440.      *
  441.      * @throws \LogicException
  442.      */
  443.     final public function getParentAssociationMapping(): ?string
  444.     {
  445.         if (!$this->isChild()) {
  446.             throw new \LogicException(sprintf(
  447.                 'Admin "%s" has no parent.',
  448.                 static::class
  449.             ));
  450.         }
  451.         $parent $this->getParent()->getCode();
  452.         return $this->parentAssociationMapping[$parent];
  453.     }
  454.     final public function getBaseRoutePattern(): string
  455.     {
  456.         if (null !== $this->cachedBaseRoutePattern) {
  457.             return $this->cachedBaseRoutePattern;
  458.         }
  459.         if ($this->isChild()) { // the admin class is a child, prefix it with the parent route pattern
  460.             $this->cachedBaseRoutePattern sprintf(
  461.                 '%s/%s/%s',
  462.                 $this->getParent()->getBaseRoutePattern(),
  463.                 $this->getParent()->getRouterIdParameter(),
  464.                 $this->generateBaseRoutePattern(true)
  465.             );
  466.         } else {
  467.             $this->cachedBaseRoutePattern $this->generateBaseRoutePattern();
  468.         }
  469.         return $this->cachedBaseRoutePattern;
  470.     }
  471.     /**
  472.      * Returns the baseRouteName used to generate the routing information.
  473.      *
  474.      * @return string the baseRouteName used to generate the routing information
  475.      */
  476.     final public function getBaseRouteName(): string
  477.     {
  478.         if (null !== $this->cachedBaseRouteName) {
  479.             return $this->cachedBaseRouteName;
  480.         }
  481.         if ($this->isChild()) { // the admin class is a child, prefix it with the parent route name
  482.             $this->cachedBaseRouteName sprintf(
  483.                 '%s_%s',
  484.                 $this->getParent()->getBaseRouteName(),
  485.                 $this->generateBaseRouteName(true)
  486.             );
  487.         } else {
  488.             $this->cachedBaseRouteName $this->generateBaseRouteName();
  489.         }
  490.         return $this->cachedBaseRouteName;
  491.     }
  492.     final public function getClass(): string
  493.     {
  494.         if ($this->hasActiveSubClass()) {
  495.             if ($this->hasParentFieldDescription()) {
  496.                 throw new \LogicException('Feature not implemented: an embedded admin cannot have subclass');
  497.             }
  498.             $subClass $this->getRequest()->query->get('subclass');
  499.             \assert(\is_string($subClass));
  500.             if (!$this->hasSubClass($subClass)) {
  501.                 throw new \LogicException(sprintf('Subclass "%s" is not defined.'$subClass));
  502.             }
  503.             return $this->getSubClass($subClass);
  504.         }
  505.         // Do not use `$this->hasSubject()` and `$this->getSubject()` here to avoid infinite loop.
  506.         // `getSubject` use `hasSubject()` which use `getObject()` which use `getClass()`.
  507.         if (null !== $this->subject) {
  508.             $modelManager $this->getModelManager();
  509.             /** @phpstan-var class-string<T> $class */
  510.             $class $modelManager instanceof ProxyResolverInterface
  511.                 $modelManager->getRealClass($this->subject)
  512.                 // NEXT_MAJOR: Change to `\get_class($this->subject)` instead
  513.                 BCHelper::getClass($this->subject);
  514.             return $class;
  515.         }
  516.         return $this->getModelClass();
  517.     }
  518.     final public function getSubClasses(): array
  519.     {
  520.         return $this->subClasses;
  521.     }
  522.     final public function setSubClasses(array $subClasses): void
  523.     {
  524.         $this->subClasses $subClasses;
  525.     }
  526.     final public function hasSubClass(string $name): bool
  527.     {
  528.         return isset($this->subClasses[$name]);
  529.     }
  530.     final public function hasActiveSubClass(): bool
  531.     {
  532.         if (\count($this->subClasses) > && $this->hasRequest()) {
  533.             return \is_string($this->getRequest()->query->get('subclass'));
  534.         }
  535.         return false;
  536.     }
  537.     final public function getActiveSubClass(): string
  538.     {
  539.         if (!$this->hasActiveSubClass()) {
  540.             throw new \LogicException(sprintf(
  541.                 'Admin "%s" has no active subclass.',
  542.                 static::class
  543.             ));
  544.         }
  545.         return $this->getSubClass($this->getActiveSubclassCode());
  546.     }
  547.     final public function getActiveSubclassCode(): string
  548.     {
  549.         if (!$this->hasActiveSubClass()) {
  550.             throw new \LogicException(sprintf(
  551.                 'Admin "%s" has no active subclass.',
  552.                 static::class
  553.             ));
  554.         }
  555.         $subClass = (string) $this->getRequest()->query->get('subclass');
  556.         if (!$this->hasSubClass($subClass)) {
  557.             throw new \LogicException(sprintf(
  558.                 'Admin "%s" has no active subclass.',
  559.                 static::class
  560.             ));
  561.         }
  562.         return $subClass;
  563.     }
  564.     final public function getBatchActions(): array
  565.     {
  566.         if (!$this->hasRoute('batch')) {
  567.             return [];
  568.         }
  569.         $actions = [];
  570.         if ($this->hasRoute('delete') && $this->hasAccess('delete')) {
  571.             $actions['delete'] = [
  572.                 'label' => 'action_delete',
  573.                 'translation_domain' => 'SonataAdminBundle',
  574.                 'ask_confirmation' => true// by default always true
  575.             ];
  576.         }
  577.         $actions $this->configureBatchActions($actions);
  578.         foreach ($this->getExtensions() as $extension) {
  579.             $actions $extension->configureBatchActions($this$actions);
  580.         }
  581.         foreach ($actions as $name => &$action) {
  582.             if (!\array_key_exists('label'$action)) {
  583.                 $action['label'] = $this->getTranslationLabel($name'batch''label');
  584.             }
  585.             if (!\array_key_exists('translation_domain'$action)) {
  586.                 $action['translation_domain'] = $this->getTranslationDomain();
  587.             }
  588.         }
  589.         return $actions;
  590.     }
  591.     final public function getRoutes(): RouteCollectionInterface
  592.     {
  593.         $routes $this->buildRoutes();
  594.         if (null === $routes) {
  595.             throw new \LogicException('Cannot access routes during the building process.');
  596.         }
  597.         return $routes;
  598.     }
  599.     public function getRouterIdParameter(): string
  600.     {
  601.         return sprintf('{%s}'$this->getIdParameter());
  602.     }
  603.     public function getIdParameter(): string
  604.     {
  605.         $parameter 'id';
  606.         for ($i 0$i $this->getChildDepth(); ++$i) {
  607.             $parameter sprintf('child%s'ucfirst($parameter));
  608.         }
  609.         return $parameter;
  610.     }
  611.     final public function hasRoute(string $name): bool
  612.     {
  613.         return $this->getRouteGenerator()->hasAdminRoute($this$name);
  614.     }
  615.     final public function isCurrentRoute(string $name, ?string $adminCode null): bool
  616.     {
  617.         if (!$this->hasRequest()) {
  618.             return false;
  619.         }
  620.         $request $this->getRequest();
  621.         $route $request->get('_route');
  622.         if (null !== $adminCode) {
  623.             $pool $this->getConfigurationPool();
  624.             if ($pool->hasAdminByAdminCode($adminCode)) {
  625.                 $admin $pool->getAdminByAdminCode($adminCode);
  626.             } else {
  627.                 return false;
  628.             }
  629.         } else {
  630.             $admin $this;
  631.         }
  632.         return $admin->getRoutes()->getRouteName($name) === $route;
  633.     }
  634.     final public function generateObjectUrl(string $nameobject $object, array $parameters = [], int $referenceType RoutingUrlGeneratorInterface::ABSOLUTE_PATH): string
  635.     {
  636.         $parameters[$this->getIdParameter()] = $this->getUrlSafeIdentifier($object);
  637.         return $this->generateUrl($name$parameters$referenceType);
  638.     }
  639.     final public function generateUrl(string $name, array $parameters = [], int $referenceType RoutingUrlGeneratorInterface::ABSOLUTE_PATH): string
  640.     {
  641.         return $this->getRouteGenerator()->generateUrl($this$name$parameters$referenceType);
  642.     }
  643.     final public function generateMenuUrl(string $name, array $parameters = [], int $referenceType RoutingUrlGeneratorInterface::ABSOLUTE_PATH): array
  644.     {
  645.         return $this->getRouteGenerator()->generateMenuUrl($this$name$parameters$referenceType);
  646.     }
  647.     final public function getNewInstance(): object
  648.     {
  649.         $object $this->createNewInstance();
  650.         $this->alterNewInstance($object);
  651.         foreach ($this->getExtensions() as $extension) {
  652.             $extension->alterNewInstance($this$object);
  653.         }
  654.         return $object;
  655.     }
  656.     final public function getFormBuilder(): FormBuilderInterface
  657.     {
  658.         $formBuilder $this->getFormContractor()->getFormBuilder(
  659.             $this->getUniqId(),
  660.             ['data_class' => $this->getClass()] + $this->getFormOptions(),
  661.         );
  662.         $this->defineFormBuilder($formBuilder);
  663.         return $formBuilder;
  664.     }
  665.     /**
  666.      * This method is being called by the main admin class and the child class,
  667.      * the getFormBuilder is only call by the main admin class.
  668.      */
  669.     final public function defineFormBuilder(FormBuilderInterface $formBuilder): void
  670.     {
  671.         if (!$this->hasSubject()) {
  672.             throw new \LogicException(sprintf(
  673.                 'Admin "%s" has no subject.',
  674.                 static::class
  675.             ));
  676.         }
  677.         $mapper = new FormMapper($this->getFormContractor(), $formBuilder$this);
  678.         $this->configureFormFields($mapper);
  679.         foreach ($this->getExtensions() as $extension) {
  680.             $extension->configureFormFields($mapper);
  681.         }
  682.     }
  683.     final public function attachAdminClass(FieldDescriptionInterface $fieldDescription): void
  684.     {
  685.         $pool $this->getConfigurationPool();
  686.         try {
  687.             $admin $pool->getAdminByFieldDescription($fieldDescription);
  688.         } catch (AdminClassNotFoundException $exception) {
  689.             // Using a fieldDescription with no admin class for the target model is a valid case.
  690.             // Since there is no easy way to check for this case, we catch the exception instead.
  691.             return;
  692.         }
  693.         if ($this->hasRequest()) {
  694.             $admin->setRequest($this->getRequest());
  695.         }
  696.         $fieldDescription->setAssociationAdmin($admin);
  697.     }
  698.     /**
  699.      * @param string|int|null $id
  700.      *
  701.      * @phpstan-return T|null
  702.      */
  703.     final public function getObject($id): ?object
  704.     {
  705.         if (null === $id) {
  706.             return null;
  707.         }
  708.         $object $this->getModelManager()->find($this->getClass(), $id);
  709.         if (null === $object) {
  710.             return null;
  711.         }
  712.         $this->alterObject($object);
  713.         foreach ($this->getExtensions() as $extension) {
  714.             $extension->alterObject($this$object);
  715.         }
  716.         return $object;
  717.     }
  718.     final public function getForm(): FormInterface
  719.     {
  720.         $form $this->buildForm();
  721.         if (null === $form) {
  722.             throw new \LogicException('Cannot access form during the building process.');
  723.         }
  724.         return $form;
  725.     }
  726.     final public function getList(): FieldDescriptionCollection
  727.     {
  728.         $list $this->buildList();
  729.         if (null === $list) {
  730.             throw new \LogicException('Cannot access list during the building process.');
  731.         }
  732.         return $list;
  733.     }
  734.     final public function createQuery(): ProxyQueryInterface
  735.     {
  736.         $query $this->getModelManager()->createQuery($this->getClass());
  737.         $query $this->configureQuery($query);
  738.         foreach ($this->getExtensions() as $extension) {
  739.             $extension->configureQuery($this$query);
  740.         }
  741.         return $query;
  742.     }
  743.     final public function getDatagrid(): DatagridInterface
  744.     {
  745.         $datagrid $this->buildDatagrid();
  746.         if (null === $datagrid) {
  747.             throw new \LogicException('Cannot access datagrid during the building process.');
  748.         }
  749.         return $datagrid;
  750.     }
  751.     final public function getSideMenu(string $action, ?AdminInterface $childAdmin null): ItemInterface
  752.     {
  753.         if ($this->isChild()) {
  754.             return $this->getParent()->getSideMenu($action$this);
  755.         }
  756.         $menu $this->buildTabMenu($action$childAdmin);
  757.         if (null === $menu) {
  758.             throw new \LogicException('Cannot access menu during the building process.');
  759.         }
  760.         return $menu;
  761.     }
  762.     final public function getRootCode(): string
  763.     {
  764.         return $this->getRoot()->getCode();
  765.     }
  766.     final public function getRoot(): AdminInterface
  767.     {
  768.         if (!$this->hasParentFieldDescription()) {
  769.             return $this;
  770.         }
  771.         return $this->getParentFieldDescription()->getAdmin()->getRoot();
  772.     }
  773.     final public function getMaxPerPage(): int
  774.     {
  775.         $sortValues $this->getDefaultSortValues();
  776.         return $sortValues[DatagridInterface::PER_PAGE] ?? self::DEFAULT_LIST_PER_PAGE_RESULTS;
  777.     }
  778.     final public function setMaxPageLinks(int $maxPageLinks): void
  779.     {
  780.         $this->maxPageLinks $maxPageLinks;
  781.     }
  782.     final public function getMaxPageLinks(): int
  783.     {
  784.         return $this->maxPageLinks;
  785.     }
  786.     final public function getFormGroups(): array
  787.     {
  788.         return $this->formGroups;
  789.     }
  790.     final public function setFormGroups(array $formGroups): void
  791.     {
  792.         $this->formGroups $formGroups;
  793.     }
  794.     final public function removeFieldFromFormGroup(string $key): void
  795.     {
  796.         foreach ($this->formGroups as $name => $_formGroup) {
  797.             unset($this->formGroups[$name]['fields'][$key]);
  798.             if ([] === $this->formGroups[$name]['fields']) {
  799.                 unset($this->formGroups[$name]);
  800.             }
  801.         }
  802.     }
  803.     final public function reorderFormGroup(string $group, array $keys): void
  804.     {
  805.         $formGroups $this->getFormGroups();
  806.         $formGroups[$group]['fields'] = array_merge(array_flip($keys), $formGroups[$group]['fields']);
  807.         $this->setFormGroups($formGroups);
  808.     }
  809.     final public function getFormTabs(): array
  810.     {
  811.         return $this->formTabs;
  812.     }
  813.     final public function setFormTabs(array $formTabs): void
  814.     {
  815.         $this->formTabs $formTabs;
  816.     }
  817.     final public function getShowTabs(): array
  818.     {
  819.         return $this->showTabs;
  820.     }
  821.     final public function setShowTabs(array $showTabs): void
  822.     {
  823.         $this->showTabs $showTabs;
  824.     }
  825.     final public function getShowGroups(): array
  826.     {
  827.         return $this->showGroups;
  828.     }
  829.     final public function setShowGroups(array $showGroups): void
  830.     {
  831.         $this->showGroups $showGroups;
  832.     }
  833.     final public function removeFieldFromShowGroup(string $key): void
  834.     {
  835.         foreach ($this->showGroups as $name => $_showGroup) {
  836.             unset($this->showGroups[$name]['fields'][$key]);
  837.             if ([] === $this->showGroups[$name]['fields']) {
  838.                 unset($this->showGroups[$name]);
  839.             }
  840.         }
  841.     }
  842.     final public function reorderShowGroup(string $group, array $keys): void
  843.     {
  844.         $showGroups $this->getShowGroups();
  845.         $showGroups[$group]['fields'] = array_merge(array_flip($keys), $showGroups[$group]['fields']);
  846.         $this->setShowGroups($showGroups);
  847.     }
  848.     final public function setParentFieldDescription(FieldDescriptionInterface $parentFieldDescription): void
  849.     {
  850.         $this->parentFieldDescription $parentFieldDescription;
  851.     }
  852.     final public function getParentFieldDescription(): FieldDescriptionInterface
  853.     {
  854.         if (!$this->hasParentFieldDescription()) {
  855.             throw new \LogicException(sprintf(
  856.                 'Admin "%s" has no parent field description.',
  857.                 static::class
  858.             ));
  859.         }
  860.         return $this->parentFieldDescription;
  861.     }
  862.     /**
  863.      * @phpstan-assert-if-true !null $this->parentFieldDescription
  864.      */
  865.     final public function hasParentFieldDescription(): bool
  866.     {
  867.         return null !== $this->parentFieldDescription;
  868.     }
  869.     final public function setSubject(?object $subject): void
  870.     {
  871.         if (null !== $subject && !is_a($subject$this->getModelClass(), true)) {
  872.             throw new \LogicException(sprintf(
  873.                 'Admin "%s" does not allow this subject: %s, use the one register with this admin class %s',
  874.                 static::class,
  875.                 \get_class($subject),
  876.                 $this->getModelClass()
  877.             ));
  878.         }
  879.         $this->subject $subject;
  880.     }
  881.     final public function getSubject(): object
  882.     {
  883.         if (!$this->hasSubject()) {
  884.             throw new \LogicException(sprintf(
  885.                 'Admin "%s" has no subject.',
  886.                 static::class
  887.             ));
  888.         }
  889.         return $this->subject;
  890.     }
  891.     /**
  892.      * @phpstan-assert-if-true !null $this->subject
  893.      */
  894.     final public function hasSubject(): bool
  895.     {
  896.         if (null === $this->subject && $this->hasRequest() && !$this->hasParentFieldDescription()) {
  897.             $id $this->getRequest()->get($this->getIdParameter());
  898.             if (null !== $id) {
  899.                 $this->subject $this->getObject($id);
  900.             }
  901.         }
  902.         return null !== $this->subject;
  903.     }
  904.     final public function getFormFieldDescriptions(): array
  905.     {
  906.         $this->buildForm();
  907.         return $this->formFieldDescriptions;
  908.     }
  909.     final public function getFormFieldDescription(string $name): FieldDescriptionInterface
  910.     {
  911.         $this->buildForm();
  912.         if (!$this->hasFormFieldDescription($name)) {
  913.             throw new \LogicException(sprintf(
  914.                 'Admin "%s" has no form field description for the field %s.',
  915.                 static::class,
  916.                 $name
  917.             ));
  918.         }
  919.         return $this->formFieldDescriptions[$name];
  920.     }
  921.     /**
  922.      * Returns true if the admin has a FieldDescription with the given $name.
  923.      */
  924.     final public function hasFormFieldDescription(string $name): bool
  925.     {
  926.         $this->buildForm();
  927.         return \array_key_exists($name$this->formFieldDescriptions);
  928.     }
  929.     final public function addFormFieldDescription(string $nameFieldDescriptionInterface $fieldDescription): void
  930.     {
  931.         $this->formFieldDescriptions[$name] = $fieldDescription;
  932.     }
  933.     /**
  934.      * remove a FieldDescription.
  935.      */
  936.     final public function removeFormFieldDescription(string $name): void
  937.     {
  938.         unset($this->formFieldDescriptions[$name]);
  939.     }
  940.     /**
  941.      * build and return the collection of form FieldDescription.
  942.      *
  943.      * @return FieldDescriptionInterface[] collection of form FieldDescription
  944.      */
  945.     final public function getShowFieldDescriptions(): array
  946.     {
  947.         $this->buildShow();
  948.         return $this->showFieldDescriptions;
  949.     }
  950.     /**
  951.      * Returns the form FieldDescription with the given $name.
  952.      */
  953.     final public function getShowFieldDescription(string $name): FieldDescriptionInterface
  954.     {
  955.         $this->buildShow();
  956.         if (!$this->hasShowFieldDescription($name)) {
  957.             throw new \LogicException(sprintf(
  958.                 'Admin "%s" has no show field description for the field %s.',
  959.                 static::class,
  960.                 $name
  961.             ));
  962.         }
  963.         return $this->showFieldDescriptions[$name];
  964.     }
  965.     final public function hasShowFieldDescription(string $name): bool
  966.     {
  967.         $this->buildShow();
  968.         return \array_key_exists($name$this->showFieldDescriptions);
  969.     }
  970.     final public function addShowFieldDescription(string $nameFieldDescriptionInterface $fieldDescription): void
  971.     {
  972.         $this->showFieldDescriptions[$name] = $fieldDescription;
  973.     }
  974.     final public function removeShowFieldDescription(string $name): void
  975.     {
  976.         unset($this->showFieldDescriptions[$name]);
  977.     }
  978.     final public function getListFieldDescriptions(): array
  979.     {
  980.         $this->buildList();
  981.         return $this->listFieldDescriptions;
  982.     }
  983.     final public function getListFieldDescription(string $name): FieldDescriptionInterface
  984.     {
  985.         $this->buildList();
  986.         if (!$this->hasListFieldDescription($name)) {
  987.             throw new \LogicException(sprintf(
  988.                 'Admin "%s" has no list field description for %s.',
  989.                 static::class,
  990.                 $name
  991.             ));
  992.         }
  993.         return $this->listFieldDescriptions[$name];
  994.     }
  995.     final public function hasListFieldDescription(string $name): bool
  996.     {
  997.         $this->buildList();
  998.         return \array_key_exists($name$this->listFieldDescriptions);
  999.     }
  1000.     final public function addListFieldDescription(string $nameFieldDescriptionInterface $fieldDescription): void
  1001.     {
  1002.         $this->listFieldDescriptions[$name] = $fieldDescription;
  1003.     }
  1004.     final public function removeListFieldDescription(string $name): void
  1005.     {
  1006.         unset($this->listFieldDescriptions[$name]);
  1007.     }
  1008.     final public function getFilterFieldDescription(string $name): FieldDescriptionInterface
  1009.     {
  1010.         $this->buildDatagrid();
  1011.         if (!$this->hasFilterFieldDescription($name)) {
  1012.             throw new \LogicException(sprintf(
  1013.                 'Admin "%s" has no filter field description for the field %s.',
  1014.                 static::class,
  1015.                 $name
  1016.             ));
  1017.         }
  1018.         return $this->filterFieldDescriptions[$name];
  1019.     }
  1020.     final public function hasFilterFieldDescription(string $name): bool
  1021.     {
  1022.         $this->buildDatagrid();
  1023.         return \array_key_exists($name$this->filterFieldDescriptions);
  1024.     }
  1025.     final public function addFilterFieldDescription(string $nameFieldDescriptionInterface $fieldDescription): void
  1026.     {
  1027.         $this->filterFieldDescriptions[$name] = $fieldDescription;
  1028.     }
  1029.     final public function removeFilterFieldDescription(string $name): void
  1030.     {
  1031.         unset($this->filterFieldDescriptions[$name]);
  1032.     }
  1033.     final public function getFilterFieldDescriptions(): array
  1034.     {
  1035.         $this->buildDatagrid();
  1036.         return $this->filterFieldDescriptions;
  1037.     }
  1038.     /**
  1039.      * @psalm-suppress PossiblyNullArgument Will be solved in NEXT_MAJOR
  1040.      */
  1041.     final public function addChild(AdminInterface $child, ?string $field null): void
  1042.     {
  1043.         $parentAdmin $this;
  1044.         while ($parentAdmin->isChild() && $parentAdmin->getCode() !== $child->getCode()) {
  1045.             $parentAdmin $parentAdmin->getParent();
  1046.         }
  1047.         if ($parentAdmin->getCode() === $child->getCode()) {
  1048.             throw new \LogicException(sprintf(
  1049.                 'Circular reference detected! The child admin `%s` is already in the parent tree of the `%s` admin.',
  1050.                 $child->getCode(),
  1051.                 $this->getCode()
  1052.             ));
  1053.         }
  1054.         $this->children[$child->getCode()] = $child;
  1055.         // @phpstan-ignore-next-line Will be solved in NEXT_MAJOR
  1056.         $child->setParent($this$field);
  1057.     }
  1058.     final public function hasChild(string $code): bool
  1059.     {
  1060.         return isset($this->children[$code]);
  1061.     }
  1062.     final public function getChildren(): array
  1063.     {
  1064.         return $this->children;
  1065.     }
  1066.     final public function getChild(string $code): AdminInterface
  1067.     {
  1068.         if (!$this->hasChild($code)) {
  1069.             throw new \LogicException(sprintf(
  1070.                 'Admin "%s" has no child for the code %s.',
  1071.                 static::class,
  1072.                 $code
  1073.             ));
  1074.         }
  1075.         return $this->getChildren()[$code];
  1076.     }
  1077.     final public function setParent(AdminInterface $parent, ?string $parentAssociationMapping null): void
  1078.     {
  1079.         $this->parent $parent;
  1080.         $this->parentAssociationMapping[$parent->getCode()] = $parentAssociationMapping;
  1081.     }
  1082.     final public function getParent(): AdminInterface
  1083.     {
  1084.         if (null === $this->parent) {
  1085.             throw new \LogicException(sprintf(
  1086.                 'Admin "%s" has no parent.',
  1087.                 static::class
  1088.             ));
  1089.         }
  1090.         return $this->parent;
  1091.     }
  1092.     final public function getRootAncestor(): AdminInterface
  1093.     {
  1094.         $parent $this;
  1095.         while ($parent->isChild()) {
  1096.             $parent $parent->getParent();
  1097.         }
  1098.         return $parent;
  1099.     }
  1100.     final public function getChildDepth(): int
  1101.     {
  1102.         $parent $this;
  1103.         $depth 0;
  1104.         while ($parent->isChild()) {
  1105.             $parent $parent->getParent();
  1106.             ++$depth;
  1107.         }
  1108.         return $depth;
  1109.     }
  1110.     final public function getCurrentLeafChildAdmin(): ?AdminInterface
  1111.     {
  1112.         $child $this->getCurrentChildAdmin();
  1113.         if (null === $child) {
  1114.             return null;
  1115.         }
  1116.         for ($c $childnull !== $c$c $child->getCurrentChildAdmin()) {
  1117.             $child $c;
  1118.         }
  1119.         return $child;
  1120.     }
  1121.     final public function isChild(): bool
  1122.     {
  1123.         return $this->parent instanceof AdminInterface;
  1124.     }
  1125.     /**
  1126.      * Returns true if the admin has children, false otherwise.
  1127.      *
  1128.      * @phpstan-assert-if-true non-empty-array $this->children
  1129.      */
  1130.     final public function hasChildren(): bool
  1131.     {
  1132.         return \count($this->children) > 0;
  1133.     }
  1134.     final public function setUniqId(string $uniqId): void
  1135.     {
  1136.         $this->uniqId $uniqId;
  1137.     }
  1138.     final public function getUniqId(): string
  1139.     {
  1140.         if (null === $this->uniqId) {
  1141.             $this->uniqId sprintf('s%s'uniqid());
  1142.         }
  1143.         return $this->uniqId;
  1144.     }
  1145.     final public function getClassnameLabel(): string
  1146.     {
  1147.         if (null === $this->classnameLabel) {
  1148.             throw new \LogicException(sprintf(
  1149.                 'Admin "%s" has no classname label. Did you forgot to initialize the admin ?',
  1150.                 static::class
  1151.             ));
  1152.         }
  1153.         return $this->classnameLabel;
  1154.     }
  1155.     final public function getPersistentParameters(): array
  1156.     {
  1157.         $parameters $this->configurePersistentParameters();
  1158.         foreach ($this->getExtensions() as $extension) {
  1159.             $parameters $extension->configurePersistentParameters($this$parameters);
  1160.         }
  1161.         return $parameters;
  1162.     }
  1163.     final public function getPersistentParameter(string $name$default null)
  1164.     {
  1165.         $parameters $this->getPersistentParameters();
  1166.         return $parameters[$name] ?? $default;
  1167.     }
  1168.     final public function setCurrentChild(bool $currentChild): void
  1169.     {
  1170.         $this->currentChild $currentChild;
  1171.     }
  1172.     final public function isCurrentChild(): bool
  1173.     {
  1174.         return $this->currentChild;
  1175.     }
  1176.     final public function getCurrentChildAdmin(): ?AdminInterface
  1177.     {
  1178.         foreach ($this->getChildren() as $child) {
  1179.             if ($child->isCurrentChild()) {
  1180.                 return $child;
  1181.             }
  1182.         }
  1183.         return null;
  1184.     }
  1185.     final public function setTranslationDomain(string $translationDomain): void
  1186.     {
  1187.         $this->translationDomain $translationDomain;
  1188.     }
  1189.     final public function getTranslationDomain(): string
  1190.     {
  1191.         return $this->translationDomain;
  1192.     }
  1193.     final public function getTranslationLabel(string $labelstring $context ''string $type ''): string
  1194.     {
  1195.         return $this->getLabelTranslatorStrategy()->getLabel($label$context$type);
  1196.     }
  1197.     final public function setRequest(Request $request): void
  1198.     {
  1199.         $this->request $request;
  1200.         foreach ($this->getChildren() as $children) {
  1201.             $children->setRequest($request);
  1202.         }
  1203.     }
  1204.     final public function getRequest(): Request
  1205.     {
  1206.         if (!$this->hasRequest()) {
  1207.             throw new \LogicException('The Request object has not been set');
  1208.         }
  1209.         return $this->request;
  1210.     }
  1211.     /**
  1212.      * @phpstan-assert-if-true !null $this->request
  1213.      */
  1214.     final public function hasRequest(): bool
  1215.     {
  1216.         return null !== $this->request;
  1217.     }
  1218.     final public function getBaseCodeRoute(): string
  1219.     {
  1220.         if ($this->isChild()) {
  1221.             return $this->getParent()->getBaseCodeRoute().'|'.$this->getCode();
  1222.         }
  1223.         return $this->getCode();
  1224.     }
  1225.     /**
  1226.      * @return string
  1227.      */
  1228.     public function getObjectIdentifier()
  1229.     {
  1230.         return $this->getCode();
  1231.     }
  1232.     public function showInDashboard(): bool
  1233.     {
  1234.         // NEXT_MAJOR: Remove those lines and uncomment the last one.
  1235.         $permissionShow $this->getPermissionsShow(self::CONTEXT_DASHBOARD'sonata_deprecation_mute');
  1236.         $permission === \count($permissionShow) ? reset($permissionShow) : $permissionShow;
  1237.         return $this->isGranted($permission);
  1238.         // return $this->isGranted('LIST');
  1239.     }
  1240.     /**
  1241.      * NEXT_MAJOR: Remove this method.
  1242.      *
  1243.      * @deprecated since sonata-project/admin-bundle version 4.7 use showInDashboard instead
  1244.      */
  1245.     final public function showIn(string $context): bool
  1246.     {
  1247.         if ('sonata_deprecation_mute' !== (\func_get_args()[1] ?? null)) {
  1248.             @trigger_error(sprintf(
  1249.                 'The "%s()" method is deprecated since sonata-project/admin-bundle version 4.7 and will be'
  1250.                 .' removed in 5.0 version. Use showInDashboard() instead.',
  1251.                 __METHOD__
  1252.             ), \E_USER_DEPRECATED);
  1253.         }
  1254.         $permissionShow $this->getPermissionsShow($context'sonata_deprecation_mute');
  1255.         // Avoid isGranted deprecation if there is only one permission show.
  1256.         $permission === \count($permissionShow) ? reset($permissionShow) : $permissionShow;
  1257.         return $this->isGranted($permission);
  1258.     }
  1259.     final public function createObjectSecurity(object $object): void
  1260.     {
  1261.         $this->getSecurityHandler()->createObjectSecurity($this$object);
  1262.     }
  1263.     final public function isGranted($name, ?object $object null): bool
  1264.     {
  1265.         if (\is_array($name)) {
  1266.             @trigger_error(
  1267.                 sprintf(
  1268.                     'Passing an array as argument 1 of "%s()" is deprecated since sonata-project/admin-bundle 4.6'
  1269.                     .' and will throw an error in 5.0. You MUST pass a string instead.',
  1270.                     __METHOD__
  1271.                 ),
  1272.                 \E_USER_DEPRECATED
  1273.             );
  1274.         }
  1275.         $objectRef null !== $object sprintf('/%s#%s'spl_object_hash($object), $this->id($object) ?? '') : '';
  1276.         $key md5(json_encode($name\JSON_THROW_ON_ERROR).$objectRef);
  1277.         if (!\array_key_exists($key$this->cacheIsGranted)) {
  1278.             $this->cacheIsGranted[$key] = $this->getSecurityHandler()->isGranted($this$name$object ?? $this);
  1279.         }
  1280.         return $this->cacheIsGranted[$key];
  1281.     }
  1282.     final public function getUrlSafeIdentifier(object $model): ?string
  1283.     {
  1284.         return $this->getModelManager()->getUrlSafeIdentifier($model);
  1285.     }
  1286.     final public function getNormalizedIdentifier(object $model): ?string
  1287.     {
  1288.         return $this->getModelManager()->getNormalizedIdentifier($model);
  1289.     }
  1290.     public function id(object $model): ?string
  1291.     {
  1292.         return $this->getNormalizedIdentifier($model);
  1293.     }
  1294.     final public function getShow(): FieldDescriptionCollection
  1295.     {
  1296.         $show $this->buildShow();
  1297.         if (null === $show) {
  1298.             throw new \LogicException('Cannot access show during the building process.');
  1299.         }
  1300.         return $show;
  1301.     }
  1302.     final public function setFormTheme(array $formTheme): void
  1303.     {
  1304.         $this->formTheme $formTheme;
  1305.     }
  1306.     final public function getFormTheme(): array
  1307.     {
  1308.         return $this->formTheme;
  1309.     }
  1310.     final public function setFilterTheme(array $filterTheme): void
  1311.     {
  1312.         $this->filterTheme $filterTheme;
  1313.     }
  1314.     final public function getFilterTheme(): array
  1315.     {
  1316.         return $this->filterTheme;
  1317.     }
  1318.     final public function addExtension(AdminExtensionInterface $extension): void
  1319.     {
  1320.         $this->extensions[] = $extension;
  1321.     }
  1322.     /**
  1323.      * @phpstan-param AdminExtensionInterface<T> $extension
  1324.      */
  1325.     final public function removeExtension(AdminExtensionInterface $extension): void
  1326.     {
  1327.         $key array_search($extension$this->extensionstrue);
  1328.         if (false === $key) {
  1329.             throw new \InvalidArgumentException(
  1330.                 sprintf('The extension "%s" was not set to the "%s" admin.'\get_class($extension), self::class)
  1331.             );
  1332.         }
  1333.         unset($this->extensions[$key]);
  1334.     }
  1335.     final public function getExtensions(): array
  1336.     {
  1337.         return $this->extensions;
  1338.     }
  1339.     public function toString(object $object): string
  1340.     {
  1341.         if (method_exists($object'__toString') && null !== $object->__toString()) {
  1342.             return $object->__toString();
  1343.         }
  1344.         $modelManager $this->getModelManager();
  1345.         if ($modelManager instanceof ProxyResolverInterface) {
  1346.             $class $modelManager->getRealClass($object);
  1347.         } else {
  1348.             // NEXT_MAJOR: Change to `\get_class($object)`
  1349.             $class BCHelper::getClass($object);
  1350.         }
  1351.         return sprintf('%s:%s'$classspl_object_hash($object));
  1352.     }
  1353.     final public function supportsPreviewMode(): bool
  1354.     {
  1355.         return $this->supportsPreviewMode;
  1356.     }
  1357.     /**
  1358.      * Returns predefined per page options.
  1359.      *
  1360.      * @return array<int>
  1361.      */
  1362.     public function getPerPageOptions(): array
  1363.     {
  1364.         $perPageOptions self::DEFAULT_LIST_PER_PAGE_OPTIONS;
  1365.         $perPageOptions[] = $this->getMaxPerPage();
  1366.         $perPageOptions array_unique($perPageOptions);
  1367.         sort($perPageOptions);
  1368.         return $perPageOptions;
  1369.     }
  1370.     /**
  1371.      * Returns true if the per page value is allowed, false otherwise.
  1372.      */
  1373.     final public function determinedPerPageValue(int $perPage): bool
  1374.     {
  1375.         return \in_array($perPage$this->getPerPageOptions(), true);
  1376.     }
  1377.     final public function isAclEnabled(): bool
  1378.     {
  1379.         return $this->getSecurityHandler() instanceof AclSecurityHandlerInterface;
  1380.     }
  1381.     public function getObjectMetadata(object $object): MetadataInterface
  1382.     {
  1383.         return new Metadata($this->toString($object));
  1384.     }
  1385.     final public function setListMode(string $mode): void
  1386.     {
  1387.         $this->getRequest()->getSession()->set(sprintf('%s.list_mode'$this->getCode()), $mode);
  1388.     }
  1389.     final public function getListMode(): string
  1390.     {
  1391.         $defaultListMode array_keys($this->getListModes())[0];
  1392.         if (!$this->hasRequest() || !$this->getRequest()->hasSession()) {
  1393.             return $defaultListMode;
  1394.         }
  1395.         return $this->getRequest()->getSession()->get(sprintf('%s.list_mode'$this->getCode()), $defaultListMode);
  1396.     }
  1397.     final public function checkAccess(string $action, ?object $object null): void
  1398.     {
  1399.         $access $this->getAccess();
  1400.         if (!\array_key_exists($action$access)) {
  1401.             throw new \InvalidArgumentException(sprintf(
  1402.                 'Action "%s" could not be found in access mapping.'
  1403.                 .' Please make sure your action is defined into your admin class accessMapping property.',
  1404.                 $action
  1405.             ));
  1406.         }
  1407.         if (!\is_array($access[$action])) {
  1408.             $access[$action] = [$access[$action]];
  1409.         }
  1410.         foreach ($access[$action] as $role) {
  1411.             if (false === $this->isGranted($role$object)) {
  1412.                 throw new AccessDeniedException(sprintf('Access Denied to the action %s and role %s'$action$role));
  1413.             }
  1414.         }
  1415.     }
  1416.     final public function hasAccess(string $action, ?object $object null): bool
  1417.     {
  1418.         $access $this->getAccess();
  1419.         if (!\array_key_exists($action$access)) {
  1420.             return false;
  1421.         }
  1422.         if (!\is_array($access[$action])) {
  1423.             $access[$action] = [$access[$action]];
  1424.         }
  1425.         foreach ($access[$action] as $role) {
  1426.             if (false === $this->isGranted($role$object)) {
  1427.                 return false;
  1428.             }
  1429.         }
  1430.         return true;
  1431.     }
  1432.     /**
  1433.      * @return array<string, array<string, mixed>>
  1434.      *
  1435.      * @phpstan-param T|null $object
  1436.      */
  1437.     final public function getActionButtons(string $action, ?object $object null): array
  1438.     {
  1439.         $defaultButtonList $this->getDefaultActionButtons($action$object);
  1440.         $buttonList $this->configureActionButtons($defaultButtonList$action$object);
  1441.         foreach ($this->getExtensions() as $extension) {
  1442.             $buttonList $extension->configureActionButtons($this$buttonList$action$object);
  1443.         }
  1444.         return $buttonList;
  1445.     }
  1446.     /**
  1447.      * Get the list of actions that can be accessed directly from the dashboard.
  1448.      *
  1449.      * @return array<string, array<string, mixed>>
  1450.      */
  1451.     final public function getDashboardActions(): array
  1452.     {
  1453.         $actions = [];
  1454.         if ($this->hasRoute('create') && $this->hasAccess('create')) {
  1455.             $actions['create'] = [
  1456.                 'label' => 'link_add',
  1457.                 'translation_domain' => 'SonataAdminBundle',
  1458.                 'template' => $this->getTemplateRegistry()->getTemplate('action_create'),
  1459.                 'url' => $this->generateUrl('create'),
  1460.                 'icon' => 'fas fa-plus-circle',
  1461.             ];
  1462.         }
  1463.         if ($this->hasRoute('list') && $this->hasAccess('list')) {
  1464.             $actions['list'] = [
  1465.                 'label' => 'link_list',
  1466.                 'translation_domain' => 'SonataAdminBundle',
  1467.                 'url' => $this->generateUrl('list'),
  1468.                 'icon' => 'fas fa-list',
  1469.             ];
  1470.         }
  1471.         $actions $this->configureDashboardActions($actions);
  1472.         foreach ($this->getExtensions() as $extension) {
  1473.             $actions $extension->configureDashboardActions($this$actions);
  1474.         }
  1475.         return $actions;
  1476.     }
  1477.     final public function createFieldDescription(string $propertyName, array $options = []): FieldDescriptionInterface
  1478.     {
  1479.         $fieldDescriptionFactory $this->getFieldDescriptionFactory();
  1480.         $fieldDescription $fieldDescriptionFactory->create($this->getClass(), $propertyName$options);
  1481.         $fieldDescription->setAdmin($this);
  1482.         return $fieldDescription;
  1483.     }
  1484.     /**
  1485.      * Hook to run after initialization.
  1486.      */
  1487.     protected function configure(): void
  1488.     {
  1489.     }
  1490.     protected function generateBaseRoutePattern(bool $isChildAdmin false): string
  1491.     {
  1492.         // NEXT_MAJOR: Remove this code
  1493.         if (null !== $this->baseRoutePattern) {
  1494.             @trigger_error(sprintf(
  1495.                 'Overriding the baseRoutePattern property is deprecated since sonata-project/admin-bundle 4.15.'
  1496.                 .' You MUST override the method %s() instead.',
  1497.                 __METHOD__
  1498.             ), \E_USER_DEPRECATED);
  1499.             return $this->baseRoutePattern;
  1500.         }
  1501.         preg_match(self::CLASS_REGEX$this->getModelClass(), $matches);
  1502.         if ([] === $matches) {
  1503.             throw new \LogicException(sprintf(
  1504.                 'Please define a default `baseRoutePattern` value for the admin class `%s`',
  1505.                 static::class
  1506.             ));
  1507.         }
  1508.         if ($isChildAdmin) {
  1509.             return $this->urlize($matches[5], '-');
  1510.         }
  1511.         return sprintf(
  1512.             '/%s%s/%s',
  1513.             '' === $matches[1] ? '' $this->urlize($matches[1], '-').'/',
  1514.             $this->urlize($matches[3], '-'),
  1515.             $this->urlize($matches[5], '-')
  1516.         );
  1517.     }
  1518.     protected function generateBaseRouteName(bool $isChildAdmin false): string
  1519.     {
  1520.         // NEXT_MAJOR: Remove this code
  1521.         if (null !== $this->baseRouteName) {
  1522.             @trigger_error(sprintf(
  1523.                 'Overriding the baseRouteName property is deprecated since sonata-project/admin-bundle 4.15.'
  1524.                 .' You MUST override the method %s() instead.',
  1525.                 __METHOD__
  1526.             ), \E_USER_DEPRECATED);
  1527.             return $this->baseRouteName;
  1528.         }
  1529.         preg_match(self::CLASS_REGEX$this->getModelClass(), $matches);
  1530.         if ([] === $matches) {
  1531.             throw new \LogicException(sprintf(
  1532.                 'Cannot automatically determine base route name,'
  1533.                 .' please define a default `baseRouteName` value for the admin class `%s`',
  1534.                 static::class
  1535.             ));
  1536.         }
  1537.         if ($isChildAdmin) {
  1538.             return $this->urlize($matches[5]);
  1539.         }
  1540.         return sprintf(
  1541.             'admin_%s%s_%s',
  1542.             '' === $matches[1] ? '' $this->urlize($matches[1]).'_',
  1543.             $this->urlize($matches[3]),
  1544.             $this->urlize($matches[5])
  1545.         );
  1546.     }
  1547.     /**
  1548.      * @phpstan-return T
  1549.      */
  1550.     protected function createNewInstance(): object
  1551.     {
  1552.         $object Instantiator::instantiate($this->getClass());
  1553.         $this->appendParentObject($object);
  1554.         return $object;
  1555.     }
  1556.     /**
  1557.      * @phpstan-param T $object
  1558.      */
  1559.     protected function alterNewInstance(object $object): void
  1560.     {
  1561.     }
  1562.     /**
  1563.      * @phpstan-param T $object
  1564.      */
  1565.     protected function alterObject(object $object): void
  1566.     {
  1567.     }
  1568.     /**
  1569.      * @phpstan-param T $object
  1570.      */
  1571.     protected function preValidate(object $object): void
  1572.     {
  1573.     }
  1574.     /**
  1575.      * @phpstan-param T $object
  1576.      */
  1577.     protected function preUpdate(object $object): void
  1578.     {
  1579.     }
  1580.     /**
  1581.      * @phpstan-param T $object
  1582.      */
  1583.     protected function postUpdate(object $object): void
  1584.     {
  1585.     }
  1586.     /**
  1587.      * @phpstan-param T $object
  1588.      */
  1589.     protected function prePersist(object $object): void
  1590.     {
  1591.     }
  1592.     /**
  1593.      * @phpstan-param T $object
  1594.      */
  1595.     protected function postPersist(object $object): void
  1596.     {
  1597.     }
  1598.     /**
  1599.      * @phpstan-param T $object
  1600.      */
  1601.     protected function preRemove(object $object): void
  1602.     {
  1603.     }
  1604.     /**
  1605.      * @phpstan-param T $object
  1606.      */
  1607.     protected function postRemove(object $object): void
  1608.     {
  1609.     }
  1610.     /**
  1611.      * @return array<string, mixed>
  1612.      */
  1613.     protected function configurePersistentParameters(): array
  1614.     {
  1615.         return [];
  1616.     }
  1617.     /**
  1618.      * @return string[]
  1619.      */
  1620.     protected function configureExportFields(): array
  1621.     {
  1622.         return $this->getModelManager()->getExportFields($this->getClass());
  1623.     }
  1624.     /**
  1625.      * @param ProxyQueryInterface<T> $query
  1626.      *
  1627.      * @return ProxyQueryInterface<T>
  1628.      */
  1629.     protected function configureQuery(ProxyQueryInterface $query): ProxyQueryInterface
  1630.     {
  1631.         return $query;
  1632.     }
  1633.     /**
  1634.      * urlize the given word.
  1635.      *
  1636.      * @param string $sep the separator
  1637.      */
  1638.     final protected function urlize(string $wordstring $sep '_'): string
  1639.     {
  1640.         return strtolower(preg_replace('/[^a-z0-9_]/i'$sep.'$1'$word) ?? '');
  1641.     }
  1642.     /**
  1643.      * @param array<string, mixed> $parameters
  1644.      *
  1645.      * @return array<string, mixed>
  1646.      */
  1647.     protected function configureFilterParameters(array $parameters): array
  1648.     {
  1649.         return $parameters;
  1650.     }
  1651.     /**
  1652.      * Returns a list of default sort values.
  1653.      *
  1654.      * @phpstan-return array{
  1655.      *     _page?: int,
  1656.      *     _per_page?: int,
  1657.      *     _sort_by?: string,
  1658.      *     _sort_order?: string
  1659.      * }
  1660.      */
  1661.     final protected function getDefaultSortValues(): array
  1662.     {
  1663.         $defaultSortValues = [DatagridInterface::PAGE => 1DatagridInterface::PER_PAGE => self::DEFAULT_LIST_PER_PAGE_RESULTS];
  1664.         $this->configureDefaultSortValues($defaultSortValues);
  1665.         foreach ($this->getExtensions() as $extension) {
  1666.             $extension->configureDefaultSortValues($this$defaultSortValues);
  1667.         }
  1668.         return $defaultSortValues;
  1669.     }
  1670.     /**
  1671.      * Returns a list of default filters.
  1672.      *
  1673.      * @return array<string, array<string, mixed>>
  1674.      */
  1675.     final protected function getDefaultFilterValues(): array
  1676.     {
  1677.         $defaultFilterValues = [];
  1678.         $this->configureDefaultFilterValues($defaultFilterValues);
  1679.         foreach ($this->getExtensions() as $extension) {
  1680.             $extension->configureDefaultFilterValues($this$defaultFilterValues);
  1681.         }
  1682.         return $defaultFilterValues;
  1683.     }
  1684.     /**
  1685.      * @return array<string, mixed>
  1686.      */
  1687.     final protected function getFormOptions(): array
  1688.     {
  1689.         $formOptions = [];
  1690.         $this->configureFormOptions($formOptions);
  1691.         foreach ($this->getExtensions() as $extension) {
  1692.             $extension->configureFormOptions($this$formOptions);
  1693.         }
  1694.         return $formOptions;
  1695.     }
  1696.     /**
  1697.      * @phpstan-param FormMapper<T> $form
  1698.      */
  1699.     protected function configureFormFields(FormMapper $form): void
  1700.     {
  1701.     }
  1702.     /**
  1703.      * @phpstan-param ListMapper<T> $list
  1704.      */
  1705.     protected function configureListFields(ListMapper $list): void
  1706.     {
  1707.     }
  1708.     /**
  1709.      * @phpstan-param DatagridMapper<T> $filter
  1710.      */
  1711.     protected function configureDatagridFilters(DatagridMapper $filter): void
  1712.     {
  1713.     }
  1714.     /**
  1715.      * @phpstan-param ShowMapper<T> $show
  1716.      */
  1717.     protected function configureShowFields(ShowMapper $show): void
  1718.     {
  1719.     }
  1720.     protected function configureRoutes(RouteCollectionInterface $collection): void
  1721.     {
  1722.     }
  1723.     /**
  1724.      * @param array<string, array<string, mixed>> $buttonList
  1725.      *
  1726.      * @return array<string, array<string, mixed>>
  1727.      *
  1728.      * @phpstan-param T|null $object
  1729.      */
  1730.     protected function configureActionButtons(array $buttonListstring $action, ?object $object null): array
  1731.     {
  1732.         return $buttonList;
  1733.     }
  1734.     /**
  1735.      * @param array<string, array<string, mixed>> $actions
  1736.      *
  1737.      * @return array<string, array<string, mixed>>
  1738.      */
  1739.     protected function configureDashboardActions(array $actions): array
  1740.     {
  1741.         return $actions;
  1742.     }
  1743.     /**
  1744.      * Allows you to customize batch actions.
  1745.      *
  1746.      * @param array<string, array<string, mixed>> $actions
  1747.      *
  1748.      * @return array<string, array<string, mixed>>
  1749.      */
  1750.     protected function configureBatchActions(array $actions): array
  1751.     {
  1752.         return $actions;
  1753.     }
  1754.     /**
  1755.      * Configures the tab menu in your admin.
  1756.      *
  1757.      * @phpstan-template TChild of object
  1758.      * @phpstan-param AdminInterface<TChild>|null $childAdmin
  1759.      */
  1760.     protected function configureTabMenu(ItemInterface $menustring $action, ?AdminInterface $childAdmin null): void
  1761.     {
  1762.     }
  1763.     /**
  1764.      * Gets the subclass corresponding to the given name.
  1765.      *
  1766.      * @phpstan-return class-string<T>
  1767.      */
  1768.     protected function getSubClass(string $name): string
  1769.     {
  1770.         if ($this->hasSubClass($name)) {
  1771.             return $this->subClasses[$name];
  1772.         }
  1773.         throw new \LogicException(sprintf('Unable to find the subclass `%s` for admin `%s`'$name, static::class));
  1774.     }
  1775.     /**
  1776.      * Return list routes with permissions name.
  1777.      *
  1778.      * @return array<string, string|string[]>
  1779.      */
  1780.     final protected function getAccess(): array
  1781.     {
  1782.         $access array_merge([
  1783.             'acl' => AdminPermissionMap::PERMISSION_MASTER,
  1784.             'export' => AdminPermissionMap::PERMISSION_EXPORT,
  1785.             'historyCompareRevisions' => AdminPermissionMap::PERMISSION_HISTORY,
  1786.             'historyViewRevision' => AdminPermissionMap::PERMISSION_HISTORY,
  1787.             'history' => AdminPermissionMap::PERMISSION_HISTORY,
  1788.             'edit' => AdminPermissionMap::PERMISSION_EDIT,
  1789.             'show' => AdminPermissionMap::PERMISSION_VIEW,
  1790.             'create' => AdminPermissionMap::PERMISSION_CREATE,
  1791.             'delete' => AdminPermissionMap::PERMISSION_DELETE,
  1792.             'batchDelete' => AdminPermissionMap::PERMISSION_DELETE,
  1793.             'list' => AdminPermissionMap::PERMISSION_LIST,
  1794.         ], $this->getAccessMapping());
  1795.         foreach ($this->getExtensions() as $extension) {
  1796.             $access array_merge($access$extension->getAccessMapping($this));
  1797.         }
  1798.         return $access;
  1799.     }
  1800.     /**
  1801.      * @return array<string, string|string[]> [action1 => requiredRole1, action2 => [requiredRole2, requiredRole3]]
  1802.      */
  1803.     protected function getAccessMapping(): array
  1804.     {
  1805.         return [];
  1806.     }
  1807.     /**
  1808.      * Return the list of permissions the user should have in order to display the admin.
  1809.      *
  1810.      * NEXT_MAJOR: Remove this method.
  1811.      *
  1812.      * @deprecated since sonata-project/admin-bundle version 4.7
  1813.      *
  1814.      * @return string[]
  1815.      */
  1816.     protected function getPermissionsShow(string $context): array
  1817.     {
  1818.         if ('sonata_deprecation_mute' !== (\func_get_args()[1] ?? null)) {
  1819.             @trigger_error(sprintf(
  1820.                 'The "%s()" method is deprecated since sonata-project/admin-bundle version 4.7 and will be'
  1821.                 .' removed in 5.0 version.',
  1822.                 __METHOD__
  1823.             ), \E_USER_DEPRECATED);
  1824.         }
  1825.         return ['LIST'];
  1826.     }
  1827.     /**
  1828.      * Configures a list of default filters.
  1829.      *
  1830.      * @param array<string, array<string, mixed>> $filterValues
  1831.      */
  1832.     protected function configureDefaultFilterValues(array &$filterValues): void
  1833.     {
  1834.     }
  1835.     /**
  1836.      * Configures a list of form options.
  1837.      *
  1838.      * @param array<string, mixed> $formOptions
  1839.      */
  1840.     protected function configureFormOptions(array &$formOptions): void
  1841.     {
  1842.     }
  1843.     /**
  1844.      * Configures a list of default sort values.
  1845.      *
  1846.      * Example:
  1847.      *   $sortValues[DatagridInterface::SORT_BY] = 'foo'
  1848.      *   $sortValues[DatagridInterface::SORT_ORDER] = 'DESC'
  1849.      *
  1850.      * @param array<string, string|int> $sortValues
  1851.      *
  1852.      * @phpstan-param array{
  1853.      *     _page?: int,
  1854.      *     _per_page?: int,
  1855.      *     _sort_by?: string,
  1856.      *     _sort_order?: string
  1857.      * } $sortValues
  1858.      */
  1859.     protected function configureDefaultSortValues(array &$sortValues): void
  1860.     {
  1861.     }
  1862.     /**
  1863.      * Set the parent object, if any, to the provided object.
  1864.      *
  1865.      * @phpstan-param T $object
  1866.      */
  1867.     final protected function appendParentObject(object $object): void
  1868.     {
  1869.         if ($this->isChild()) {
  1870.             $parentAssociationMapping $this->getParentAssociationMapping();
  1871.             if (null !== $parentAssociationMapping) {
  1872.                 $parentAdmin $this->getParent();
  1873.                 $parentObject $parentAdmin->getObject($this->getRequest()->get($parentAdmin->getIdParameter()));
  1874.                 if (null !== $parentObject) {
  1875.                     $propertyAccessor PropertyAccess::createPropertyAccessor();
  1876.                     try {
  1877.                         $value $propertyAccessor->getValue($object$parentAssociationMapping);
  1878.                     } catch (AccessException $e) {
  1879.                         // @todo: Catching and checking AccessException here as BC for symfony/property-access < 5.1.
  1880.                         //        Catch UninitializedPropertyException and remove the check when dropping support < 5.1
  1881.                         if (AccessException::class !== \get_class($e) && !$e instanceof UninitializedPropertyException) {
  1882.                             throw $e// Re-throw. We only want to "ignore" pure AccessException (Sf < 5.1) and UninitializedPropertyException (Sf >= 5.1)
  1883.                         }
  1884.                         $value null;
  1885.                     }
  1886.                     if (\is_array($value) || $value instanceof \ArrayAccess) {
  1887.                         $value[] = $parentObject;
  1888.                         $propertyAccessor->setValue($object$parentAssociationMapping$value);
  1889.                     } else {
  1890.                         $propertyAccessor->setValue($object$parentAssociationMapping$parentObject);
  1891.                     }
  1892.                 }
  1893.                 return;
  1894.             }
  1895.         }
  1896.         if ($this->hasParentFieldDescription()) {
  1897.             $parentAdmin $this->getParentFieldDescription()->getAdmin();
  1898.             $parentObject $parentAdmin->getObject($this->getRequest()->get($parentAdmin->getIdParameter()));
  1899.             if (null !== $parentObject) {
  1900.                 ObjectManipulator::setObject($object$parentObject$this->getParentFieldDescription());
  1901.             }
  1902.         }
  1903.     }
  1904.     /**
  1905.      * @return array<string, array<string, mixed>>
  1906.      *
  1907.      * @phpstan-param T|null $object
  1908.      */
  1909.     private function getDefaultActionButtons(string $action, ?object $object null): array
  1910.     {
  1911.         // nothing to do for non-internal actions
  1912.         if (!isset(self::INTERNAL_ACTIONS[$action])) {
  1913.             return [];
  1914.         }
  1915.         $buttonList = [];
  1916.         $actionBit self::INTERNAL_ACTIONS[$action];
  1917.         if (!== (self::MASK_OF_ACTION_CREATE $actionBit)
  1918.             && $this->hasRoute('create')
  1919.             && $this->hasAccess('create')
  1920.         ) {
  1921.             $buttonList['create'] = [
  1922.                 'template' => $this->getTemplateRegistry()->getTemplate('button_create'),
  1923.             ];
  1924.         }
  1925.         $canAccessObject !== (self::MASK_OF_ACTIONS_USING_OBJECT $actionBit)
  1926.             && null !== $object
  1927.             && null !== $this->id($object);
  1928.         if ($canAccessObject
  1929.             && !== (self::MASK_OF_ACTION_EDIT $actionBit)
  1930.             && $this->hasRoute('edit')
  1931.             && $this->hasAccess('edit'$object)
  1932.         ) {
  1933.             $buttonList['edit'] = [
  1934.                 'template' => $this->getTemplateRegistry()->getTemplate('button_edit'),
  1935.             ];
  1936.         }
  1937.         if ($canAccessObject
  1938.             && !== (self::MASK_OF_ACTION_HISTORY $actionBit)
  1939.             && $this->hasRoute('history')
  1940.             && $this->hasAccess('history'$object)
  1941.         ) {
  1942.             $buttonList['history'] = [
  1943.                 'template' => $this->getTemplateRegistry()->getTemplate('button_history'),
  1944.             ];
  1945.         }
  1946.         if ($canAccessObject
  1947.             && !== (self::MASK_OF_ACTION_ACL $actionBit)
  1948.             && $this->isAclEnabled()
  1949.             && $this->hasRoute('acl')
  1950.             && $this->hasAccess('acl'$object)
  1951.         ) {
  1952.             $buttonList['acl'] = [
  1953.                 'template' => $this->getTemplateRegistry()->getTemplate('button_acl'),
  1954.             ];
  1955.         }
  1956.         if ($canAccessObject
  1957.             && !== (self::MASK_OF_ACTION_SHOW $actionBit)
  1958.             && $this->hasRoute('show')
  1959.             && $this->hasAccess('show'$object)
  1960.             && \count($this->getShow()) > 0
  1961.         ) {
  1962.             $buttonList['show'] = [
  1963.                 'template' => $this->getTemplateRegistry()->getTemplate('button_show'),
  1964.             ];
  1965.         }
  1966.         if (!== (self::MASK_OF_ACTION_LIST $actionBit)
  1967.             && $this->hasRoute('list')
  1968.             && $this->hasAccess('list')
  1969.         ) {
  1970.             $buttonList['list'] = [
  1971.                 'template' => $this->getTemplateRegistry()->getTemplate('button_list'),
  1972.             ];
  1973.         }
  1974.         return $buttonList;
  1975.     }
  1976.     /**
  1977.      * @return DatagridInterface<ProxyQueryInterface<T>>|null
  1978.      */
  1979.     private function buildDatagrid(): ?DatagridInterface
  1980.     {
  1981.         if ($this->loaded['datagrid']) {
  1982.             return $this->datagrid;
  1983.         }
  1984.         $this->loaded['datagrid'] = true;
  1985.         $filterParameters $this->getFilterParameters();
  1986.         // transform DatagridInterface::SORT_BY filter parameter from a string to a FieldDescriptionInterface for the datagrid.
  1987.         if (isset($filterParameters[DatagridInterface::SORT_BY]) && \is_string($filterParameters[DatagridInterface::SORT_BY])) {
  1988.             if ($this->hasListFieldDescription($filterParameters[DatagridInterface::SORT_BY])) {
  1989.                 $filterParameters[DatagridInterface::SORT_BY] = $this->getListFieldDescription($filterParameters[DatagridInterface::SORT_BY]);
  1990.             } else {
  1991.                 $filterParameters[DatagridInterface::SORT_BY] = $this->createFieldDescription(
  1992.                     $filterParameters[DatagridInterface::SORT_BY]
  1993.                 );
  1994.                 $this->getListBuilder()->buildField(null$filterParameters[DatagridInterface::SORT_BY]);
  1995.             }
  1996.         }
  1997.         // initialize the datagrid
  1998.         $this->datagrid $this->getDatagridBuilder()->getBaseDatagrid($this$filterParameters);
  1999.         $this->datagrid->getPager()->setMaxPageLinks($this->getMaxPageLinks());
  2000.         /** @psalm-suppress InvalidArgument https://github.com/vimeo/psalm/issues/8423 */
  2001.         $mapper = new DatagridMapper($this->getDatagridBuilder(), $this->datagrid$this);
  2002.         // build the datagrid filter
  2003.         $this->configureDatagridFilters($mapper);
  2004.         // ok, try to limit to add parent filter
  2005.         if ($this->isChild()) {
  2006.             $parentAssociationMapping $this->getParentAssociationMapping();
  2007.             if (null !== $parentAssociationMapping && !$mapper->has($parentAssociationMapping)) {
  2008.                 $mapper->add($parentAssociationMappingnull, [
  2009.                     'show_filter' => false,
  2010.                     'label' => false,
  2011.                     'field_type' => ModelHiddenType::class,
  2012.                     'field_options' => [
  2013.                         'model_manager' => $this->getParent()->getModelManager(),
  2014.                         'class' => $this->getParent()->getClass(),
  2015.                     ],
  2016.                     'operator_type' => HiddenType::class,
  2017.                 ], [
  2018.                     'admin_code' => $this->getParent()->getCode(),
  2019.                 ]);
  2020.             }
  2021.         }
  2022.         foreach ($this->getExtensions() as $extension) {
  2023.             $extension->configureDatagridFilters($mapper);
  2024.         }
  2025.         return $this->datagrid;
  2026.     }
  2027.     /**
  2028.      * @return FieldDescriptionCollection<FieldDescriptionInterface>|null
  2029.      */
  2030.     private function buildShow(): ?FieldDescriptionCollection
  2031.     {
  2032.         if ($this->loaded['show']) {
  2033.             return $this->show;
  2034.         }
  2035.         $this->loaded['show'] = true;
  2036.         $this->show $this->getShowBuilder()->getBaseList();
  2037.         $mapper = new ShowMapper($this->getShowBuilder(), $this->show$this);
  2038.         $this->configureShowFields($mapper);
  2039.         foreach ($this->getExtensions() as $extension) {
  2040.             $extension->configureShowFields($mapper);
  2041.         }
  2042.         return $this->show;
  2043.     }
  2044.     /**
  2045.      * @return FieldDescriptionCollection<FieldDescriptionInterface>|null
  2046.      */
  2047.     private function buildList(): ?FieldDescriptionCollection
  2048.     {
  2049.         if ($this->loaded['list']) {
  2050.             return $this->list;
  2051.         }
  2052.         $this->loaded['list'] = true;
  2053.         $this->list $this->getListBuilder()->getBaseList();
  2054.         $mapper = new ListMapper($this->getListBuilder(), $this->list$this);
  2055.         if (\count($this->getBatchActions()) > && $this->hasRequest() && !$this->getRequest()->isXmlHttpRequest()) {
  2056.             $mapper->add(ListMapper::NAME_BATCHListMapper::TYPE_BATCH, [
  2057.                 'label' => 'batch',
  2058.                 'sortable' => false,
  2059.                 'virtual_field' => true,
  2060.                 'template' => $this->getTemplateRegistry()->getTemplate('batch'),
  2061.             ]);
  2062.         }
  2063.         $this->configureListFields($mapper);
  2064.         foreach ($this->getExtensions() as $extension) {
  2065.             $extension->configureListFields($mapper);
  2066.         }
  2067.         if ($this->hasRequest()
  2068.             && $this->getRequest()->isXmlHttpRequest()
  2069.             && $this->getRequest()->query->getBoolean('select'true// NEXT_MAJOR: Change the default value to `false` in version 5
  2070.         ) {
  2071.             $mapper->add(ListMapper::NAME_SELECTListMapper::TYPE_SELECT, [
  2072.                 'label' => false,
  2073.                 'sortable' => false,
  2074.                 'virtual_field' => false,
  2075.                 'template' => $this->getTemplateRegistry()->getTemplate('select'),
  2076.             ]);
  2077.         }
  2078.         return $this->list;
  2079.     }
  2080.     private function buildForm(): ?FormInterface
  2081.     {
  2082.         if ($this->loaded['form']) {
  2083.             return $this->form;
  2084.         }
  2085.         $this->loaded['form'] = true;
  2086.         $formBuilder $this->getFormBuilder();
  2087.         $formBuilder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event): void {
  2088.             /** @phpstan-var T $data */
  2089.             $data $event->getData();
  2090.             $this->preValidate($data);
  2091.         }, 100);
  2092.         $this->form $formBuilder->getForm();
  2093.         return $this->form;
  2094.     }
  2095.     private function buildRoutes(): ?RouteCollectionInterface
  2096.     {
  2097.         if ($this->loaded['routes']) {
  2098.             return $this->routes;
  2099.         }
  2100.         $this->loaded['routes'] = true;
  2101.         $routes = new RouteCollection(
  2102.             $this->getBaseCodeRoute(),
  2103.             $this->getBaseRouteName(),
  2104.             $this->getBaseRoutePattern(),
  2105.             $this->getBaseControllerName()
  2106.         );
  2107.         $this->getRouteBuilder()->build($this$routes);
  2108.         $this->configureRoutes($routes);
  2109.         foreach ($this->getExtensions() as $extension) {
  2110.             $extension->configureRoutes($this$routes);
  2111.         }
  2112.         $this->routes $routes;
  2113.         return $this->routes;
  2114.     }
  2115.     /**
  2116.      * @phpstan-template TChild of object
  2117.      * @phpstan-param AdminInterface<TChild>|null $childAdmin
  2118.      */
  2119.     private function buildTabMenu(string $action, ?AdminInterface $childAdmin null): ?ItemInterface
  2120.     {
  2121.         if ($this->loaded['tab_menu']) {
  2122.             return $this->menu;
  2123.         }
  2124.         $this->loaded['tab_menu'] = true;
  2125.         $menu $this->getMenuFactory()->createItem('root');
  2126.         $menu->setChildrenAttribute('class''nav navbar-nav');
  2127.         $menu->setExtra('translation_domain'$this->getTranslationDomain());
  2128.         $this->configureTabMenu($menu$action$childAdmin);
  2129.         foreach ($this->getExtensions() as $extension) {
  2130.             $extension->configureTabMenu($this$menu$action$childAdmin);
  2131.         }
  2132.         $this->menu $menu;
  2133.         return $this->menu;
  2134.     }
  2135. }