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

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 Doctrine\Common\Util\ClassUtils;
  13. use Knp\Menu\ItemInterface;
  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\Exporter\DataSourceInterface;
  21. use Sonata\AdminBundle\Form\FormMapper;
  22. use Sonata\AdminBundle\Form\Type\ModelHiddenType;
  23. use Sonata\AdminBundle\Manipulator\ObjectManipulator;
  24. use Sonata\AdminBundle\Object\Metadata;
  25. use Sonata\AdminBundle\Route\RouteCollection;
  26. use Sonata\AdminBundle\Security\Handler\AclSecurityHandlerInterface;
  27. use Sonata\AdminBundle\Show\ShowMapper;
  28. use Sonata\AdminBundle\Templating\MutableTemplateRegistryInterface;
  29. // NEXT_MAJOR: Uncomment next line.
  30. // use Sonata\AdminBundle\Util\Instantiator;
  31. use Sonata\AdminBundle\Util\ParametersManipulator;
  32. use Sonata\Form\Validator\Constraints\InlineConstraint;
  33. use Sonata\Form\Validator\ErrorElement;
  34. use Symfony\Component\Form\Extension\Core\Type\HiddenType;
  35. use Symfony\Component\Form\FormBuilderInterface;
  36. use Symfony\Component\Form\FormEvent;
  37. use Symfony\Component\Form\FormEvents;
  38. use Symfony\Component\Form\FormInterface;
  39. use Symfony\Component\HttpFoundation\InputBag;
  40. use Symfony\Component\HttpFoundation\ParameterBag;
  41. use Symfony\Component\HttpFoundation\Request;
  42. use Symfony\Component\PropertyAccess\PropertyAccess;
  43. use Symfony\Component\PropertyAccess\PropertyPath;
  44. use Symfony\Component\Routing\Generator\UrlGeneratorInterface as RoutingUrlGeneratorInterface;
  45. use Symfony\Component\Security\Acl\Model\DomainObjectInterface;
  46. use Symfony\Component\Security\Core\Exception\AccessDeniedException;
  47. use Symfony\Component\Validator\Mapping\GenericMetadata;
  48. /**
  49.  * @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
  50.  *
  51.  * @phpstan-template T of object
  52.  * @phpstan-extends AbstractTaggedAdmin<T>
  53.  * @phpstan-implements AdminInterface<T>
  54.  */
  55. abstract class AbstractAdmin extends AbstractTaggedAdmin implements AdminInterfaceDomainObjectInterfaceAdminTreeInterface
  56. {
  57.     public const CONTEXT_MENU 'menu';
  58.     public const CONTEXT_DASHBOARD 'dashboard';
  59.     public const CLASS_REGEX =
  60.         '@
  61.         (?:([A-Za-z0-9]*)\\\)?        # vendor name / app name
  62.         (Bundle\\\)?                  # optional bundle directory
  63.         ([A-Za-z0-9]+?)(?:Bundle)?\\\ # bundle name, with optional suffix
  64.         (
  65.             Entity|Document|Model|PHPCR|CouchDocument|Phpcr|
  66.             Doctrine\\\Orm|Doctrine\\\Phpcr|Doctrine\\\MongoDB|Doctrine\\\CouchDB
  67.         )\\\(.*)@x';
  68.     /**
  69.      * The list FieldDescription constructed from the configureListField method.
  70.      *
  71.      * @var array<string, FieldDescriptionInterface>
  72.      */
  73.     protected $listFieldDescriptions = [];
  74.     /**
  75.      * The show FieldDescription constructed from the configureShowFields method.
  76.      *
  77.      * @var FieldDescriptionInterface[]
  78.      */
  79.     protected $showFieldDescriptions = [];
  80.     /**
  81.      * The list FieldDescription constructed from the configureFormField method.
  82.      *
  83.      * @var FieldDescriptionInterface[]
  84.      */
  85.     protected $formFieldDescriptions = [];
  86.     /**
  87.      * The filter FieldDescription constructed from the configureFilterField method.
  88.      *
  89.      * @var FieldDescriptionInterface[]
  90.      */
  91.     protected $filterFieldDescriptions = [];
  92.     /**
  93.      * NEXT_MAJOR: Remove this property.
  94.      *
  95.      * The number of result to display in the list.
  96.      *
  97.      * @deprecated since sonata-project/admin-bundle 3.67.
  98.      *
  99.      * @var int
  100.      */
  101.     protected $maxPerPage 32;
  102.     /**
  103.      * The maximum number of page numbers to display in the list.
  104.      *
  105.      * @var int
  106.      */
  107.     protected $maxPageLinks 25;
  108.     /**
  109.      * The base route name used to generate the routing information.
  110.      *
  111.      * @var string|null
  112.      */
  113.     protected $baseRouteName;
  114.     /**
  115.      * The base route pattern used to generate the routing information.
  116.      *
  117.      * @var string|null
  118.      */
  119.     protected $baseRoutePattern;
  120.     /**
  121.      * The label class name  (used in the title/breadcrumb ...).
  122.      *
  123.      * @var string|null
  124.      */
  125.     protected $classnameLabel;
  126.     /**
  127.      * The translation domain to be used to translate messages.
  128.      *
  129.      * @var string
  130.      */
  131.     protected $translationDomain 'messages';
  132.     /**
  133.      * Options to set to the form (ie, validation_groups).
  134.      *
  135.      * @deprecated since sonata-project/admin-bundle 3.89, use configureFormOptions() instead.
  136.      *
  137.      * @var array<string, mixed>
  138.      */
  139.     protected $formOptions = [];
  140.     /**
  141.      * NEXT_MAJOR: Remove this property.
  142.      *
  143.      * Default values to the datagrid.
  144.      *
  145.      * @deprecated since sonata-project/admin-bundle 3.67, use configureDefaultSortValues() instead.
  146.      *
  147.      * @var array
  148.      */
  149.     protected $datagridValues = [
  150.         '_page' => 1,
  151.         '_per_page' => 32,
  152.     ];
  153.     /**
  154.      * NEXT_MAJOR: Remove this property.
  155.      *
  156.      * Predefined per page options.
  157.      *
  158.      * @deprecated since sonata-project/admin-bundle 3.67.
  159.      *
  160.      * @var array
  161.      */
  162.     protected $perPageOptions = [163264128256];
  163.     /**
  164.      * Array of routes related to this admin.
  165.      *
  166.      * @var RouteCollection|null
  167.      */
  168.     protected $routes;
  169.     /**
  170.      * The subject only set in edit/update/create mode.
  171.      *
  172.      * @var object|null
  173.      *
  174.      * @phpstan-var T|null
  175.      */
  176.     protected $subject;
  177.     /**
  178.      * Define a Collection of child admin, ie /admin/order/{id}/order-element/{childId}.
  179.      *
  180.      * @var array<string, AdminInterface>
  181.      */
  182.     protected $children = [];
  183.     /**
  184.      * Reference the parent admin.
  185.      *
  186.      * @var AdminInterface|null
  187.      */
  188.     protected $parent;
  189.     /**
  190.      * The base code route refer to the prefix used to generate the route name.
  191.      *
  192.      * NEXT_MAJOR: remove this attribute.
  193.      *
  194.      * @deprecated This attribute is deprecated since sonata-project/admin-bundle 3.24 and will be removed in 4.0
  195.      *
  196.      * @var string
  197.      */
  198.     protected $baseCodeRoute '';
  199.     /**
  200.      * NEXT_MAJOR: should be default array and private.
  201.      *
  202.      * @var array<string, mixed>|string|null
  203.      */
  204.     protected $parentAssociationMapping;
  205.     /**
  206.      * Reference the parent FieldDescription related to this admin
  207.      * only set for FieldDescription which is associated to an Sub Admin instance.
  208.      *
  209.      * @var FieldDescriptionInterface|null
  210.      */
  211.     protected $parentFieldDescription;
  212.     /**
  213.      * If true then the current admin is part of the nested admin set (from the url).
  214.      *
  215.      * @var bool
  216.      */
  217.     protected $currentChild false;
  218.     /**
  219.      * The uniqid is used to avoid clashing with 2 admin related to the code
  220.      * ie: a Block linked to a Block.
  221.      *
  222.      * @var string|null
  223.      */
  224.     protected $uniqid;
  225.     /**
  226.      * The current request object.
  227.      *
  228.      * @var Request|null
  229.      */
  230.     protected $request;
  231.     /**
  232.      * The datagrid instance.
  233.      *
  234.      * @var DatagridInterface|null
  235.      */
  236.     protected $datagrid;
  237.     /**
  238.      * The generated breadcrumbs.
  239.      *
  240.      * NEXT_MAJOR : remove this property
  241.      *
  242.      * @var array<string, ItemInterface|null>
  243.      */
  244.     protected $breadcrumbs = [];
  245.     /**
  246.      * @var ItemInterface|null
  247.      */
  248.     protected $menu;
  249.     /**
  250.      * @var array<string, bool>
  251.      */
  252.     protected $loaded = [
  253.         'view_fields' => false// NEXT_MAJOR: Remove this unused value.
  254.         'view_groups' => false// NEXT_MAJOR: Remove this unused value.
  255.         'routes' => false,
  256.         'tab_menu' => false,
  257.         'show' => false,
  258.         'list' => false,
  259.         'form' => false,
  260.         'datagrid' => false,
  261.     ];
  262.     /**
  263.      * @var string[]
  264.      */
  265.     protected $formTheme = [];
  266.     /**
  267.      * @var string[]
  268.      */
  269.     protected $filterTheme = [];
  270.     /**
  271.      * @var array<string, string>
  272.      *
  273.      * @deprecated since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead
  274.      */
  275.     protected $templates = [];
  276.     /**
  277.      * @var AdminExtensionInterface[]
  278.      */
  279.     protected $extensions = [];
  280.     /**
  281.      * Setting to true will enable preview mode for
  282.      * the entity and show a preview button in the
  283.      * edit/create forms.
  284.      *
  285.      * @var bool
  286.      */
  287.     protected $supportsPreviewMode false;
  288.     /**
  289.      * @var array<string, bool>
  290.      */
  291.     protected $cacheIsGranted = [];
  292.     /**
  293.      * Action list for the search result.
  294.      *
  295.      * @var string[]
  296.      */
  297.     protected $searchResultActions = ['edit''show'];
  298.     /**
  299.      * The Access mapping.
  300.      *
  301.      * @var array<string, string|string[]> [action1 => requiredRole1, action2 => [requiredRole2, requiredRole3]]
  302.      */
  303.     protected $accessMapping = [];
  304.     /**
  305.      * @var MutableTemplateRegistryInterface|null
  306.      */
  307.     private $templateRegistry;
  308.     /**
  309.      * The subclasses supported by the admin class.
  310.      *
  311.      * @var array<string, string>
  312.      */
  313.     private $subClasses = [];
  314.     /**
  315.      * The list collection.
  316.      *
  317.      * @var FieldDescriptionCollection|null
  318.      */
  319.     private $list;
  320.     /**
  321.      * @var FieldDescriptionCollection|null
  322.      */
  323.     private $show;
  324.     /**
  325.      * @var FormInterface|null
  326.      */
  327.     private $form;
  328.     /**
  329.      * The cached base route name.
  330.      *
  331.      * @var string|null
  332.      */
  333.     private $cachedBaseRouteName;
  334.     /**
  335.      * The cached base route pattern.
  336.      *
  337.      * @var string|null
  338.      */
  339.     private $cachedBaseRoutePattern;
  340.     /**
  341.      * The form group disposition.
  342.      *
  343.      * NEXT_MAJOR: must have `[]` as default value and remove the possibility to
  344.      * hold boolean values.
  345.      *
  346.      * @var array|bool
  347.      */
  348.     private $formGroups false;
  349.     /**
  350.      * The form tabs disposition.
  351.      *
  352.      * NEXT_MAJOR: must have `[]` as default value and remove the possibility to
  353.      * hold boolean values.
  354.      *
  355.      * @var array|bool
  356.      */
  357.     private $formTabs false;
  358.     /**
  359.      * The view group disposition.
  360.      *
  361.      * NEXT_MAJOR: must have `[]` as default value and remove the possibility to
  362.      * hold boolean values.
  363.      *
  364.      * @var array|bool
  365.      */
  366.     private $showGroups false;
  367.     /**
  368.      * The view tab disposition.
  369.      *
  370.      * NEXT_MAJOR: must have `[]` as default value and remove the possibility to
  371.      * hold boolean values.
  372.      *
  373.      * @var array|bool
  374.      */
  375.     private $showTabs false;
  376.     /**
  377.      * The breadcrumbsBuilder component.
  378.      *
  379.      * @var BreadcrumbsBuilderInterface|null
  380.      */
  381.     private $breadcrumbsBuilder;
  382.     /**
  383.      * NEXT_MAJOR: Remove the construct override.
  384.      *
  385.      * @phpstan-param class-string<T> $class
  386.      */
  387.     public function __construct($code$class$baseControllerName null)
  388.     {
  389.         parent::__construct($code$class$baseControllerName);
  390.         // NEXT_MAJOR: Remove this line.
  391.         $this->predefinePerPageOptions();
  392.         // NEXT_MAJOR: Remove this line.
  393.         $this->datagridValues['_per_page'] = $this->maxPerPage;
  394.     }
  395.     /**
  396.      * {@inheritdoc}
  397.      */
  398.     public function getExportFormats()
  399.     {
  400.         return [
  401.             'json''xml''csv''xls',
  402.         ];
  403.     }
  404.     /**
  405.      * @final since sonata-project/admin-bundle 3.76
  406.      *
  407.      * @return string[]
  408.      */
  409.     public function getExportFields()
  410.     {
  411.         $fields $this->configureExportFields();
  412.         foreach ($this->getExtensions() as $extension) {
  413.             if (method_exists($extension'configureExportFields')) {
  414.                 $fields $extension->configureExportFields($this$fields);
  415.             }
  416.         }
  417.         return $fields;
  418.     }
  419.     public function getDataSourceIterator()
  420.     {
  421.         $datagrid $this->getDatagrid();
  422.         $datagrid->buildPager();
  423.         $fields = [];
  424.         foreach ($this->getExportFields() as $key => $field) {
  425.             // NEXT_MAJOR: Remove the following code in favor of the commented one.
  426.             $label $this->getTranslationLabel($field'export''label');
  427.             $transLabel $this->getTranslator()->trans($label, [], $this->getTranslationDomain());
  428.             if ($transLabel === $label) {
  429.                 $fields[$key] = $field;
  430.             } else {
  431.                 $fields[$transLabel] = $field;
  432.             }
  433. //            if (!\is_string($key)) {
  434. //                $label = $this->getTranslationLabel($field, 'export', 'label');
  435. //                $key = $this->getTranslator()->trans($label, [], $this->getTranslationDomain());
  436. //            }
  437. //
  438. //            $fields[$key] = $field;
  439.         }
  440.         if ($this->getDataSource()) {
  441.             $query $datagrid->getQuery();
  442.             return $this->getDataSource()->createIterator($query$fields);
  443.         }
  444.         @trigger_error(sprintf(
  445.             'Using "%s()" without setting a "%s" instance in the admin is deprecated since sonata-project/admin-bundle 3.79'
  446.             .' and won\'t be possible in 4.0.',
  447.             __METHOD__,
  448.             DataSourceInterface::class
  449.         ), \E_USER_DEPRECATED);
  450.         return $this->getModelManager()->getDataSourceIterator($datagrid$fields);
  451.     }
  452.     /**
  453.      * NEXT_MAJOR: Remove this method.
  454.      */
  455.     public function validate(ErrorElement $errorElement$object)
  456.     {
  457.         if ('sonata_deprecation_mute' !== (\func_get_args()[2] ?? null)) {
  458.             @trigger_error(sprintf(
  459.                 'The %s method is deprecated since version 3.82 and will be removed in 4.0.',
  460.                 __METHOD__
  461.             ), \E_USER_DEPRECATED);
  462.         }
  463.     }
  464.     /**
  465.      * @final since sonata-admin/admin-bundle 3.84
  466.      */
  467.     public function initialize()
  468.     {
  469.         if (!$this->classnameLabel) {
  470.             /* NEXT_MAJOR: remove cast to string, null is not supposed to be
  471.             supported but was documented as such */
  472.             $this->classnameLabel substr(
  473.                 (string) $this->getClass(),
  474.                 strrpos((string) $this->getClass(), '\\') + 1
  475.             );
  476.         }
  477.         // NEXT_MAJOR: Remove this line.
  478.         $this->baseCodeRoute $this->getCode();
  479.         $this->configure();
  480.     }
  481.     /**
  482.      * NEXT_MAJOR: Restrict visibility to protected.
  483.      */
  484.     public function configure()
  485.     {
  486.     }
  487.     public function update($object)
  488.     {
  489.         $this->preUpdate($object);
  490.         foreach ($this->getExtensions() as $extension) {
  491.             $extension->preUpdate($this$object);
  492.         }
  493.         $result $this->getModelManager()->update($object);
  494.         // BC compatibility
  495.         if (null !== $result) {
  496.             $object $result;
  497.         }
  498.         $this->postUpdate($object);
  499.         foreach ($this->getExtensions() as $extension) {
  500.             $extension->postUpdate($this$object);
  501.         }
  502.         return $object;
  503.     }
  504.     public function create($object)
  505.     {
  506.         $this->prePersist($object);
  507.         foreach ($this->getExtensions() as $extension) {
  508.             $extension->prePersist($this$object);
  509.         }
  510.         $result $this->getModelManager()->create($object);
  511.         // BC compatibility
  512.         if (null !== $result) {
  513.             $object $result;
  514.         }
  515.         $this->postPersist($object);
  516.         foreach ($this->getExtensions() as $extension) {
  517.             $extension->postPersist($this$object);
  518.         }
  519.         $this->createObjectSecurity($object);
  520.         return $object;
  521.     }
  522.     public function delete($object)
  523.     {
  524.         $this->preRemove($object);
  525.         foreach ($this->getExtensions() as $extension) {
  526.             $extension->preRemove($this$object);
  527.         }
  528.         $this->getSecurityHandler()->deleteObjectSecurity($this$object);
  529.         $this->getModelManager()->delete($object);
  530.         $this->postRemove($object);
  531.         foreach ($this->getExtensions() as $extension) {
  532.             $extension->postRemove($this$object);
  533.         }
  534.     }
  535.     /**
  536.      * NEXT_MAJOR: Remove this method.
  537.      *
  538.      * @deprecated since sonata-project/admin-bundle 3.82, will be removed in 4.0.
  539.      *
  540.      * @param object $object
  541.      *
  542.      * @phpstan-param T $object
  543.      */
  544.     public function preValidate($object)
  545.     {
  546.         if ('sonata_deprecation_mute' !== \func_get_args()[1] ?? null) {
  547.             @trigger_error(sprintf(
  548.                 'The %s method is deprecated since version 3.82 and will be removed in 4.0.',
  549.                 __METHOD__
  550.             ), \E_USER_DEPRECATED);
  551.         }
  552.     }
  553.     public function preUpdate($object)
  554.     {
  555.     }
  556.     public function postUpdate($object)
  557.     {
  558.     }
  559.     public function prePersist($object)
  560.     {
  561.     }
  562.     public function postPersist($object)
  563.     {
  564.     }
  565.     public function preRemove($object)
  566.     {
  567.     }
  568.     public function postRemove($object)
  569.     {
  570.     }
  571.     public function preBatchAction($actionNameProxyQueryInterface $query, array &$idx$allElements)
  572.     {
  573.     }
  574.     final public function getDefaultFilterParameters(): array
  575.     {
  576.         return array_merge(
  577.             $this->getModelManager()->getDefaultSortValues($this->getClass()), // NEXT_MAJOR: Remove this line.
  578.             $this->datagridValues// NEXT_MAJOR: Remove this line.
  579.             $this->getDefaultSortValues(),
  580.             $this->getDefaultFilterValues()
  581.         );
  582.     }
  583.     public function getFilterParameters()
  584.     {
  585.         $parameters $this->getDefaultFilterParameters();
  586.         // build the values array
  587.         if ($this->hasRequest()) {
  588.             /** @var InputBag|ParameterBag $bag */
  589.             $bag $this->getRequest()->query;
  590.             if ($bag instanceof InputBag) {
  591.                 // symfony 5.1+
  592.                 $filters $bag->all('filter');
  593.             } else {
  594.                 $filters $bag->get('filter', []);
  595.             }
  596.             if (isset($filters['_page'])) {
  597.                 $filters['_page'] = (int) $filters['_page'];
  598.             }
  599.             if (isset($filters['_per_page'])) {
  600.                 $filters['_per_page'] = (int) $filters['_per_page'];
  601.             }
  602.             // if filter persistence is configured
  603.             // NEXT_MAJOR: remove `$this->persistFilters !== false` from the condition
  604.             if (false !== $this->persistFilters && $this->hasFilterPersister()) {
  605.                 // if reset filters is asked, remove from storage
  606.                 if ('reset' === $this->getRequest()->query->get('filters')) {
  607.                     $this->getFilterPersister()->reset($this->getCode());
  608.                 }
  609.                 // if no filters, fetch from storage
  610.                 // otherwise save to storage
  611.                 if (empty($filters)) {
  612.                     $filters $this->getFilterPersister()->get($this->getCode());
  613.                 } else {
  614.                     $this->getFilterPersister()->set($this->getCode(), $filters);
  615.                 }
  616.             }
  617.             $parameters ParametersManipulator::merge($parameters$filters);
  618.             // always force the parent value
  619.             if ($this->isChild() && $this->getParentAssociationMapping()) {
  620.                 $name str_replace('.''__'$this->getParentAssociationMapping());
  621.                 $parameters[$name] = ['value' => $this->getRequest()->get($this->getParent()->getIdParameter())];
  622.             }
  623.         }
  624.         if (!isset($parameters['_per_page']) || !$this->determinedPerPageValue($parameters['_per_page'])) {
  625.             $parameters['_per_page'] = $this->getMaxPerPage();
  626.         }
  627.         return $parameters;
  628.     }
  629.     /**
  630.      * NEXT_MAJOR: Change the visibility to protected (similar to buildShow, buildForm, ...).
  631.      */
  632.     public function buildDatagrid()
  633.     {
  634.         if ($this->loaded['datagrid']) {
  635.             return;
  636.         }
  637.         $this->loaded['datagrid'] = true;
  638.         $filterParameters $this->getFilterParameters();
  639.         // transform _sort_by from a string to a FieldDescriptionInterface for the datagrid.
  640.         if (isset($filterParameters['_sort_by']) && \is_string($filterParameters['_sort_by'])) {
  641.             if ($this->hasListFieldDescription($filterParameters['_sort_by'])) {
  642.                 $filterParameters['_sort_by'] = $this->getListFieldDescription($filterParameters['_sort_by']);
  643.             } else {
  644.                 $filterParameters['_sort_by'] = $this->getModelManager()->getNewFieldDescriptionInstance(
  645.                     $this->getClass(),
  646.                     $filterParameters['_sort_by'],
  647.                     []
  648.                 );
  649.                 $this->getListBuilder()->buildField(null$filterParameters['_sort_by'], $this);
  650.             }
  651.         }
  652.         // initialize the datagrid
  653.         $this->datagrid $this->getDatagridBuilder()->getBaseDatagrid($this$filterParameters);
  654.         $this->datagrid->getPager()->setMaxPageLinks($this->getMaxPageLinks());
  655.         $mapper = new DatagridMapper($this->getDatagridBuilder(), $this->datagrid$this);
  656.         // build the datagrid filter
  657.         $this->configureDatagridFilters($mapper);
  658.         // ok, try to limit to add parent filter
  659.         if ($this->isChild() && $this->getParentAssociationMapping() && !$mapper->has($this->getParentAssociationMapping())) {
  660.             $mapper->add($this->getParentAssociationMapping(), null, [
  661.                 'show_filter' => false,
  662.                 'label' => false,
  663.                 'field_type' => ModelHiddenType::class,
  664.                 'field_options' => [
  665.                     'model_manager' => $this->getModelManager(),
  666.                 ],
  667.                 'operator_type' => HiddenType::class,
  668.             ], [
  669.                 'admin_code' => $this->getParent()->getCode(),
  670.             ]);
  671.         }
  672.         foreach ($this->getExtensions() as $extension) {
  673.             $extension->configureDatagridFilters($mapper);
  674.         }
  675.     }
  676.     /**
  677.      * Returns the name of the parent related field, so the field can be use to set the default
  678.      * value (ie the parent object) or to filter the object.
  679.      *
  680.      * @throws \InvalidArgumentException
  681.      *
  682.      * @return string|null
  683.      */
  684.     public function getParentAssociationMapping()
  685.     {
  686.         // NEXT_MAJOR: remove array check
  687.         if (\is_array($this->parentAssociationMapping) && $this->isChild()) {
  688.             $parent $this->getParent()->getCode();
  689.             if (\array_key_exists($parent$this->parentAssociationMapping)) {
  690.                 return $this->parentAssociationMapping[$parent];
  691.             }
  692.             throw new \InvalidArgumentException(sprintf(
  693.                 'There\'s no association between %s and %s.',
  694.                 $this->getCode(),
  695.                 $this->getParent()->getCode()
  696.             ));
  697.         }
  698.         // NEXT_MAJOR: remove this line
  699.         return $this->parentAssociationMapping;
  700.     }
  701.     /**
  702.      * @param string $code
  703.      * @param string $value
  704.      */
  705.     final public function addParentAssociationMapping($code$value)
  706.     {
  707.         if (\is_string($this->parentAssociationMapping)) {
  708.             @trigger_error(sprintf(
  709.                 'Calling "%s" when $this->parentAssociationMapping is string is deprecated since sonata-project/admin-bundle 3.75 and will be removed in 4.0.',
  710.                 __METHOD__
  711.             ), \E_USER_DEPRECATED);
  712.         }
  713.         $this->parentAssociationMapping[$code] = $value;
  714.     }
  715.     /**
  716.      * Returns the baseRoutePattern used to generate the routing information.
  717.      *
  718.      * @throws \RuntimeException // NEXT_MAJOR: Remove this tag
  719.      *
  720.      * @return string the baseRoutePattern used to generate the routing information
  721.      */
  722.     public function getBaseRoutePattern()
  723.     {
  724.         if (null !== $this->cachedBaseRoutePattern) {
  725.             return $this->cachedBaseRoutePattern;
  726.         }
  727.         if ($this->isChild()) { // the admin class is a child, prefix it with the parent route pattern
  728.             $baseRoutePattern $this->baseRoutePattern;
  729.             if (!$this->baseRoutePattern) {
  730.                 preg_match(self::CLASS_REGEX$this->class$matches);
  731.                 if (!$matches) {
  732.                     // NEXT_MAJOR: Throw \LogicException instead
  733.                     throw new \RuntimeException(sprintf(
  734.                         'Please define a default `baseRoutePattern` value for the admin class `%s`',
  735.                         static::class
  736.                     ));
  737.                 }
  738.                 $baseRoutePattern $this->urlize($matches[5], '-');
  739.             }
  740.             $this->cachedBaseRoutePattern sprintf(
  741.                 '%s/%s/%s',
  742.                 $this->getParent()->getBaseRoutePattern(),
  743.                 $this->getParent()->getRouterIdParameter(),
  744.                 $baseRoutePattern
  745.             );
  746.         } elseif ($this->baseRoutePattern) {
  747.             $this->cachedBaseRoutePattern $this->baseRoutePattern;
  748.         } else {
  749.             preg_match(self::CLASS_REGEX$this->class$matches);
  750.             if (!$matches) {
  751.                 // NEXT_MAJOR: Throw \LogicException instead
  752.                 throw new \RuntimeException(sprintf(
  753.                     'Please define a default `baseRoutePattern` value for the admin class `%s`',
  754.                     static::class
  755.                 ));
  756.             }
  757.             $this->cachedBaseRoutePattern sprintf(
  758.                 '/%s%s/%s',
  759.                 empty($matches[1]) ? '' $this->urlize($matches[1], '-').'/',
  760.                 $this->urlize($matches[3], '-'),
  761.                 $this->urlize($matches[5], '-')
  762.             );
  763.         }
  764.         return $this->cachedBaseRoutePattern;
  765.     }
  766.     /**
  767.      * Returns the baseRouteName used to generate the routing information.
  768.      *
  769.      * @throws \RuntimeException // NEXT_MAJOR: Remove this tag
  770.      *
  771.      * @return string the baseRouteName used to generate the routing information
  772.      */
  773.     public function getBaseRouteName()
  774.     {
  775.         if (null !== $this->cachedBaseRouteName) {
  776.             return $this->cachedBaseRouteName;
  777.         }
  778.         if ($this->isChild()) { // the admin class is a child, prefix it with the parent route name
  779.             $baseRouteName $this->baseRouteName;
  780.             if (!$this->baseRouteName) {
  781.                 preg_match(self::CLASS_REGEX$this->class$matches);
  782.                 if (!$matches) {
  783.                     // NEXT_MAJOR: Throw \LogicException instead
  784.                     throw new \RuntimeException(sprintf(
  785.                         'Cannot automatically determine base route name,'
  786.                         .' please define a default `baseRouteName` value for the admin class `%s`',
  787.                         static::class
  788.                     ));
  789.                 }
  790.                 $baseRouteName $this->urlize($matches[5]);
  791.             }
  792.             $this->cachedBaseRouteName sprintf(
  793.                 '%s_%s',
  794.                 $this->getParent()->getBaseRouteName(),
  795.                 $baseRouteName
  796.             );
  797.         } elseif ($this->baseRouteName) {
  798.             $this->cachedBaseRouteName $this->baseRouteName;
  799.         } else {
  800.             preg_match(self::CLASS_REGEX$this->class$matches);
  801.             if (!$matches) {
  802.                 // NEXT_MAJOR: Throw \LogicException instead
  803.                 throw new \RuntimeException(sprintf(
  804.                     'Cannot automatically determine base route name,'
  805.                     .' please define a default `baseRouteName` value for the admin class `%s`',
  806.                     static::class
  807.                 ));
  808.             }
  809.             $this->cachedBaseRouteName sprintf(
  810.                 'admin_%s%s_%s',
  811.                 empty($matches[1]) ? '' $this->urlize($matches[1]).'_',
  812.                 $this->urlize($matches[3]),
  813.                 $this->urlize($matches[5])
  814.             );
  815.         }
  816.         return $this->cachedBaseRouteName;
  817.     }
  818.     /**
  819.      * urlize the given word.
  820.      *
  821.      * @param string $word
  822.      * @param string $sep  the separator
  823.      *
  824.      * @return string
  825.      */
  826.     public function urlize($word$sep '_')
  827.     {
  828.         return strtolower(preg_replace('/[^a-z0-9_]/i'$sep.'$1'$word));
  829.     }
  830.     public function getClass()
  831.     {
  832.         if ($this->hasActiveSubClass()) {
  833.             if ($this->hasParentFieldDescription()) {
  834.                 // NEXT_MAJOR: Throw \LogicException instead
  835.                 throw new \RuntimeException('Feature not implemented: an embedded admin cannot have subclass');
  836.             }
  837.             $subClass $this->getRequest()->query->get('subclass');
  838.             if (!$this->hasSubClass($subClass)) {
  839.                 // NEXT_MAJOR: Throw \LogicException instead
  840.                 throw new \RuntimeException(sprintf('Subclass "%s" is not defined.'$subClass));
  841.             }
  842.             return $this->getSubClass($subClass);
  843.         }
  844.         // Do not use `$this->hasSubject()` and `$this->getSubject()` here to avoid infinite loop.
  845.         // `getSubject` use `hasSubject()` which use `getObject()` which use `getClass()`.
  846.         if (null !== $this->subject) {
  847.             return ClassUtils::getClass($this->subject);
  848.         }
  849.         return $this->class;
  850.     }
  851.     public function getSubClasses()
  852.     {
  853.         return $this->subClasses;
  854.     }
  855.     /**
  856.      * NEXT_MAJOR: remove this method.
  857.      */
  858.     public function addSubClass($subClass)
  859.     {
  860.         @trigger_error(sprintf(
  861.             'Method "%s" is deprecated since sonata-project/admin-bundle 3.30 and will be removed in 4.0.',
  862.             __METHOD__
  863.         ), \E_USER_DEPRECATED);
  864.         if (!\in_array($subClass$this->subClassestrue)) {
  865.             $this->subClasses[] = $subClass;
  866.         }
  867.     }
  868.     public function setSubClasses(array $subClasses)
  869.     {
  870.         $this->subClasses $subClasses;
  871.     }
  872.     public function hasSubClass($name)
  873.     {
  874.         return isset($this->subClasses[$name]);
  875.     }
  876.     public function hasActiveSubClass()
  877.     {
  878.         if (\count($this->subClasses) > && $this->hasRequest()) {
  879.             return null !== $this->getRequest()->query->get('subclass');
  880.         }
  881.         return false;
  882.     }
  883.     public function getActiveSubClass()
  884.     {
  885.         if (!$this->hasActiveSubClass()) {
  886.             @trigger_error(sprintf(
  887.                 'Calling %s() when there is no active subclass is deprecated since sonata-project/admin-bundle 3.52'
  888.                 .' and will throw an exception in 4.0.'
  889.                 .' Use %s::hasActiveSubClass() to know if there is an active subclass.',
  890.                 __METHOD__,
  891.                 __CLASS__
  892.             ), \E_USER_DEPRECATED);
  893.             // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare string as return type
  894.             // throw new \LogicException(sprintf(
  895.             //    'Admin "%s" has no active subclass.',
  896.             //    static::class
  897.             // ));
  898.             return null;
  899.         }
  900.         return $this->getSubClass($this->getActiveSubclassCode());
  901.     }
  902.     public function getActiveSubclassCode()
  903.     {
  904.         if (!$this->hasActiveSubClass()) {
  905.             @trigger_error(sprintf(
  906.                 'Calling %s() when there is no active subclass is deprecated since sonata-project/admin-bundle 3.52'
  907.                 .' and will throw an exception in 4.0.'
  908.                 .' Use %s::hasActiveSubClass() to know if there is an active subclass.',
  909.                 __METHOD__,
  910.                 __CLASS__
  911.             ), \E_USER_DEPRECATED);
  912.             // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare string as return type
  913.             // throw new \LogicException(sprintf(
  914.             //    'Admin "%s" has no active subclass.',
  915.             //    static::class
  916.             // ));
  917.             return null;
  918.         }
  919.         $subClass $this->getRequest()->query->get('subclass');
  920.         if (!$this->hasSubClass($subClass)) {
  921.             @trigger_error(sprintf(
  922.                 'Calling %s() when there is no active subclass is deprecated since sonata-project/admin-bundle 3.52'
  923.                 .' and will throw an exception in 4.0.'
  924.                 .' Use %s::hasActiveSubClass() to know if there is an active subclass.',
  925.                 __METHOD__,
  926.                 __CLASS__
  927.             ), \E_USER_DEPRECATED);
  928.             // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare string as return type
  929.             // throw new \LogicException(sprintf(
  930.             //    'Admin "%s" has no active subclass.',
  931.             //    static::class
  932.             // ));
  933.             return null;
  934.         }
  935.         return $subClass;
  936.     }
  937.     public function getBatchActions()
  938.     {
  939.         $actions = [];
  940.         if ($this->hasRoute('delete') && $this->hasAccess('delete')) {
  941.             $actions['delete'] = [
  942.                 'label' => 'action_delete',
  943.                 'translation_domain' => 'SonataAdminBundle',
  944.                 'ask_confirmation' => true// by default always true
  945.             ];
  946.         }
  947.         $actions $this->configureBatchActions($actions);
  948.         foreach ($this->getExtensions() as $extension) {
  949.             // NEXT_MAJOR: remove method check
  950.             if (method_exists($extension'configureBatchActions')) {
  951.                 $actions $extension->configureBatchActions($this$actions);
  952.             }
  953.         }
  954.         foreach ($actions  as $name => &$action) {
  955.             if (!\array_key_exists('label'$action)) {
  956.                 $action['label'] = $this->getTranslationLabel($name'batch''label');
  957.             }
  958.             if (!\array_key_exists('translation_domain'$action)) {
  959.                 $action['translation_domain'] = $this->getTranslationDomain();
  960.             }
  961.         }
  962.         return $actions;
  963.     }
  964.     public function getRoutes()
  965.     {
  966.         $this->buildRoutes();
  967.         return $this->routes;
  968.     }
  969.     public function getRouterIdParameter()
  970.     {
  971.         return sprintf('{%s}'$this->getIdParameter());
  972.     }
  973.     public function getIdParameter()
  974.     {
  975.         $parameter 'id';
  976.         for ($i 0$i $this->getChildDepth(); ++$i) {
  977.             $parameter sprintf('child%s'ucfirst($parameter));
  978.         }
  979.         return $parameter;
  980.     }
  981.     public function hasRoute($name)
  982.     {
  983.         // NEXT_MAJOR: Remove this check.
  984.         if (!$this->routeGenerator) {
  985.             throw new \RuntimeException('RouteGenerator cannot be null');
  986.         }
  987.         return $this->getRouteGenerator()->hasAdminRoute($this$name);
  988.     }
  989.     /**
  990.      * @param string      $name
  991.      * @param string|null $adminCode
  992.      *
  993.      * @return bool
  994.      */
  995.     public function isCurrentRoute($name$adminCode null)
  996.     {
  997.         if (!$this->hasRequest()) {
  998.             return false;
  999.         }
  1000.         $request $this->getRequest();
  1001.         $route $request->get('_route');
  1002.         if ($adminCode) {
  1003.             $admin $this->getConfigurationPool()->getAdminByAdminCode($adminCode);
  1004.         } else {
  1005.             $admin $this;
  1006.         }
  1007.         if (!$admin) {
  1008.             return false;
  1009.         }
  1010.         return $admin->getRoutes()->getRouteName($name) === $route;
  1011.     }
  1012.     public function generateObjectUrl($name$object, array $parameters = [], $referenceType RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
  1013.     {
  1014.         $parameters['id'] = $this->getUrlSafeIdentifier($object);
  1015.         return $this->generateUrl($name$parameters$referenceType);
  1016.     }
  1017.     public function generateUrl($name, array $parameters = [], $referenceType RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
  1018.     {
  1019.         return $this->getRouteGenerator()->generateUrl($this$name$parameters$referenceType);
  1020.     }
  1021.     public function generateMenuUrl($name, array $parameters = [], $referenceType RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
  1022.     {
  1023.         return $this->getRouteGenerator()->generateMenuUrl($this$name$parameters$referenceType);
  1024.     }
  1025.     final public function setTemplateRegistry(MutableTemplateRegistryInterface $templateRegistry): void
  1026.     {
  1027.         $this->templateRegistry $templateRegistry;
  1028.     }
  1029.     public function setTemplates(array $templates)
  1030.     {
  1031.         // NEXT_MAJOR: Remove this line
  1032.         $this->templates $templates;
  1033.         $this->getTemplateRegistry()->setTemplates($templates);
  1034.     }
  1035.     public function setTemplate($name$template)
  1036.     {
  1037.         // NEXT_MAJOR: Remove this line
  1038.         $this->templates[$name] = $template;
  1039.         $this->getTemplateRegistry()->setTemplate($name$template);
  1040.     }
  1041.     /**
  1042.      * @deprecated since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead
  1043.      *
  1044.      * @return array<string, string>
  1045.      */
  1046.     public function getTemplates()
  1047.     {
  1048.         return $this->getTemplateRegistry()->getTemplates();
  1049.     }
  1050.     /**
  1051.      * @deprecated since sonata-project/admin-bundle 3.34, will be dropped in 4.0. Use TemplateRegistry services instead
  1052.      *
  1053.      * @param string $name
  1054.      *
  1055.      * @return string|null
  1056.      */
  1057.     public function getTemplate($name)
  1058.     {
  1059.         return $this->getTemplateRegistry()->getTemplate($name);
  1060.     }
  1061.     /**
  1062.      * @final since sonata-project/admin-bundle 3.89
  1063.      */
  1064.     public function getNewInstance()
  1065.     {
  1066.         $object $this->createNewInstance();
  1067.         $this->appendParentObject($object);
  1068.         $this->alterNewInstance($object);
  1069.         foreach ($this->getExtensions() as $extension) {
  1070.             $extension->alterNewInstance($this$object);
  1071.         }
  1072.         return $object;
  1073.     }
  1074.     public function getFormBuilder()
  1075.     {
  1076.         $this->formOptions['data_class'] = $this->getClass();
  1077.         $formBuilder $this->getFormContractor()->getFormBuilder(
  1078.             $this->getUniqid(),
  1079.             // NEXT_MAJOR : remove the merge with $this->formOptions
  1080.             array_merge($this->getFormOptions(), $this->formOptions)
  1081.         );
  1082.         $this->defineFormBuilder($formBuilder);
  1083.         return $formBuilder;
  1084.     }
  1085.     /**
  1086.      * This method is being called by the main admin class and the child class,
  1087.      * the getFormBuilder is only call by the main admin class.
  1088.      */
  1089.     public function defineFormBuilder(FormBuilderInterface $formBuilder)
  1090.     {
  1091.         if (!$this->hasSubject()) {
  1092.             @trigger_error(sprintf(
  1093.                 'Calling %s() when there is no subject is deprecated since sonata-project/admin-bundle 3.65'
  1094.                 .' and will throw an exception in 4.0. Use %s::setSubject() to set the subject.',
  1095.                 __METHOD__,
  1096.                 __CLASS__
  1097.             ), \E_USER_DEPRECATED);
  1098.             // NEXT_MAJOR : remove the previous `trigger_error()` call and uncomment the following exception
  1099.             // throw new \LogicException(sprintf(
  1100.             //    'Admin "%s" has no subject.',
  1101.             //    static::class
  1102.             // ));
  1103.         }
  1104.         $mapper = new FormMapper($this->getFormContractor(), $formBuilder$this);
  1105.         $this->configureFormFields($mapper);
  1106.         foreach ($this->getExtensions() as $extension) {
  1107.             $extension->configureFormFields($mapper);
  1108.         }
  1109.         // NEXT_MAJOR: Remove this line.
  1110.         $this->attachInlineValidator('sonata_deprecation_mute');
  1111.     }
  1112.     public function attachAdminClass(FieldDescriptionInterface $fieldDescription)
  1113.     {
  1114.         $pool $this->getConfigurationPool();
  1115.         try {
  1116.             $admin $pool->getAdminByFieldDescription($fieldDescription);
  1117.         } catch (AdminClassNotFoundException $exception) {
  1118.             // Using a fieldDescription with no admin class for the target model is a valid case.
  1119.             // Since there is no easy way to check for this case, we catch the exception instead.
  1120.             return;
  1121.         }
  1122.         // NEXT_MAJOR: Remove this check
  1123.         if (!$admin) {
  1124.             return;
  1125.         }
  1126.         if ($this->hasRequest()) {
  1127.             $admin->setRequest($this->getRequest());
  1128.         }
  1129.         $fieldDescription->setAssociationAdmin($admin);
  1130.     }
  1131.     /**
  1132.      * @final since sonata-project/admin-bundle 3.90
  1133.      */
  1134.     public function getObject($id)
  1135.     {
  1136.         if (null === $id) {
  1137.             return null;
  1138.         }
  1139.         $object $this->getModelManager()->find($this->getClass(), $id);
  1140.         if (null === $object) {
  1141.             return null;
  1142.         }
  1143.         $this->alterObject($object);
  1144.         foreach ($this->getExtensions() as $extension) {
  1145.             $extension->alterObject($this$object);
  1146.         }
  1147.         return $object;
  1148.     }
  1149.     public function getForm()
  1150.     {
  1151.         $this->buildForm();
  1152.         return $this->form;
  1153.     }
  1154.     public function getList()
  1155.     {
  1156.         $this->buildList();
  1157.         return $this->list;
  1158.     }
  1159.     /**
  1160.      * @final since sonata-project/admin-bundle 3.63.0
  1161.      */
  1162.     public function createQuery($context 'list')
  1163.     {
  1164.         if (\func_num_args() > 0) {
  1165.             @trigger_error(sprintf(
  1166.                 'The $context argument of %s is deprecated since 3.3, to be removed in 4.0.',
  1167.                 __METHOD__
  1168.             ), \E_USER_DEPRECATED);
  1169.         }
  1170.         $query $this->getModelManager()->createQuery($this->getClass());
  1171.         $query $this->configureQuery($query);
  1172.         foreach ($this->getExtensions() as $extension) {
  1173.             $extension->configureQuery($this$query$context);
  1174.         }
  1175.         return $query;
  1176.     }
  1177.     public function getDatagrid()
  1178.     {
  1179.         $this->buildDatagrid();
  1180.         return $this->datagrid;
  1181.     }
  1182.     public function buildTabMenu($action, ?AdminInterface $childAdmin null)
  1183.     {
  1184.         if ($this->loaded['tab_menu']) {
  1185.             return $this->menu;
  1186.         }
  1187.         $this->loaded['tab_menu'] = true;
  1188.         $menu $this->getMenuFactory()->createItem('root');
  1189.         $menu->setChildrenAttribute('class''nav navbar-nav');
  1190.         $menu->setExtra('translation_domain'$this->getTranslationDomain());
  1191.         // Prevents BC break with KnpMenuBundle v1.x
  1192.         if (method_exists($menu'setCurrentUri')) {
  1193.             $menu->setCurrentUri($this->getRequest()->getBaseUrl().$this->getRequest()->getPathInfo());
  1194.         }
  1195.         $this->configureTabMenu($menu$action$childAdmin);
  1196.         foreach ($this->getExtensions() as $extension) {
  1197.             $extension->configureTabMenu($this$menu$action$childAdmin);
  1198.         }
  1199.         $this->menu $menu;
  1200.         return $this->menu;
  1201.     }
  1202.     public function buildSideMenu($action, ?AdminInterface $childAdmin null)
  1203.     {
  1204.         return $this->buildTabMenu($action$childAdmin);
  1205.     }
  1206.     /**
  1207.      * @param string $action
  1208.      *
  1209.      * @return ItemInterface
  1210.      *
  1211.      * @phpstan-param AdminInterface<object>|null $childAdmin
  1212.      */
  1213.     public function getSideMenu($action, ?AdminInterface $childAdmin null)
  1214.     {
  1215.         if ($this->isChild()) {
  1216.             return $this->getParent()->getSideMenu($action$this);
  1217.         }
  1218.         $this->buildSideMenu($action$childAdmin);
  1219.         return $this->menu;
  1220.     }
  1221.     /**
  1222.      * Returns the root code.
  1223.      *
  1224.      * @return string the root code
  1225.      */
  1226.     public function getRootCode()
  1227.     {
  1228.         return $this->getRoot()->getCode();
  1229.     }
  1230.     /**
  1231.      * Returns the master admin.
  1232.      *
  1233.      * @return AdminInterface the root admin class
  1234.      */
  1235.     public function getRoot()
  1236.     {
  1237.         if (!$this->hasParentFieldDescription()) {
  1238.             return $this;
  1239.         }
  1240.         return $this->getParentFieldDescription()->getAdmin()->getRoot();
  1241.     }
  1242.     public function setBaseControllerName($baseControllerName)
  1243.     {
  1244.         $this->baseControllerName $baseControllerName;
  1245.     }
  1246.     public function getBaseControllerName()
  1247.     {
  1248.         return $this->baseControllerName;
  1249.     }
  1250.     /**
  1251.      * @param bool $persist
  1252.      *
  1253.      * NEXT_MAJOR: remove this method
  1254.      *
  1255.      * @deprecated since sonata-project/admin-bundle 3.34, to be removed in 4.0.
  1256.      */
  1257.     public function setPersistFilters($persist)
  1258.     {
  1259.         @trigger_error(sprintf(
  1260.             'The %s method is deprecated since version 3.34 and will be removed in 4.0.',
  1261.             __METHOD__
  1262.         ), \E_USER_DEPRECATED);
  1263.         $this->persistFilters $persist;
  1264.     }
  1265.     /**
  1266.      * NEXT_MAJOR: Remove this method.
  1267.      *
  1268.      * @deprecated since sonata-project/admin-bundle 3.67, to be removed in 4.0.
  1269.      *
  1270.      * @param int $maxPerPage
  1271.      */
  1272.     public function setMaxPerPage($maxPerPage)
  1273.     {
  1274.         @trigger_error(sprintf(
  1275.             'The method %s is deprecated since sonata-project/admin-bundle 3.67 and will be removed in 4.0.',
  1276.             __METHOD__
  1277.         ), \E_USER_DEPRECATED);
  1278.         $this->maxPerPage $maxPerPage;
  1279.     }
  1280.     /**
  1281.      * @return int
  1282.      */
  1283.     public function getMaxPerPage()
  1284.     {
  1285.         // NEXT_MAJOR: Remove this line and uncomment the following.
  1286.         return $this->maxPerPage;
  1287.         // $sortValues = $this->getDefaultSortValues();
  1288.         // return $sortValues['_per_page'] ?? 25;
  1289.     }
  1290.     /**
  1291.      * @param int $maxPageLinks
  1292.      */
  1293.     public function setMaxPageLinks($maxPageLinks)
  1294.     {
  1295.         $this->maxPageLinks $maxPageLinks;
  1296.     }
  1297.     /**
  1298.      * @return int
  1299.      */
  1300.     public function getMaxPageLinks()
  1301.     {
  1302.         return $this->maxPageLinks;
  1303.     }
  1304.     public function getFormGroups()
  1305.     {
  1306.         if (!\is_array($this->formGroups) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
  1307.             @trigger_error(sprintf(
  1308.                 'Returning other type than array in method %s() is deprecated since sonata-project/admin-bundle 3.65.'
  1309.                 .' It will return only array in version 4.0.',
  1310.                 __METHOD__
  1311.             ), \E_USER_DEPRECATED);
  1312.         }
  1313.         return $this->formGroups;
  1314.     }
  1315.     public function setFormGroups(array $formGroups)
  1316.     {
  1317.         $this->formGroups $formGroups;
  1318.     }
  1319.     public function removeFieldFromFormGroup($key)
  1320.     {
  1321.         foreach ($this->formGroups as $name => $formGroup) {
  1322.             unset($this->formGroups[$name]['fields'][$key]);
  1323.             if (empty($this->formGroups[$name]['fields'])) {
  1324.                 unset($this->formGroups[$name]);
  1325.             }
  1326.         }
  1327.     }
  1328.     /**
  1329.      * @param string $group
  1330.      */
  1331.     public function reorderFormGroup($group, array $keys)
  1332.     {
  1333.         // NEXT_MAJOR: Remove the argument "sonata_deprecation_mute" in the following call.
  1334.         $formGroups $this->getFormGroups('sonata_deprecation_mute');
  1335.         $formGroups[$group]['fields'] = array_merge(array_flip($keys), $formGroups[$group]['fields']);
  1336.         $this->setFormGroups($formGroups);
  1337.     }
  1338.     public function getFormTabs()
  1339.     {
  1340.         if (!\is_array($this->formTabs) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
  1341.             @trigger_error(sprintf(
  1342.                 'Returning other type than array in method %s() is deprecated since sonata-project/admin-bundle 3.65.'
  1343.                 .' It will return only array in version 4.0.',
  1344.                 __METHOD__
  1345.             ), \E_USER_DEPRECATED);
  1346.         }
  1347.         return $this->formTabs;
  1348.     }
  1349.     public function setFormTabs(array $formTabs)
  1350.     {
  1351.         $this->formTabs $formTabs;
  1352.     }
  1353.     public function getShowTabs()
  1354.     {
  1355.         if (!\is_array($this->showTabs) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
  1356.             @trigger_error(sprintf(
  1357.                 'Returning other type than array in method %s() is deprecated since sonata-project/admin-bundle 3.65.'
  1358.                 .' It will return only array in version 4.0.',
  1359.                 __METHOD__
  1360.             ), \E_USER_DEPRECATED);
  1361.         }
  1362.         return $this->showTabs;
  1363.     }
  1364.     public function setShowTabs(array $showTabs)
  1365.     {
  1366.         $this->showTabs $showTabs;
  1367.     }
  1368.     public function getShowGroups()
  1369.     {
  1370.         if (!\is_array($this->showGroups) && 'sonata_deprecation_mute' !== (\func_get_args()[0] ?? null)) {
  1371.             @trigger_error(sprintf(
  1372.                 'Returning other type than array in method %s() is deprecated since sonata-project/admin-bundle 3.65.'
  1373.                 .' It will return only array in version 4.0.',
  1374.                 __METHOD__
  1375.             ), \E_USER_DEPRECATED);
  1376.         }
  1377.         return $this->showGroups;
  1378.     }
  1379.     public function setShowGroups(array $showGroups)
  1380.     {
  1381.         $this->showGroups $showGroups;
  1382.     }
  1383.     public function reorderShowGroup($group, array $keys)
  1384.     {
  1385.         // NEXT_MAJOR: Remove the argument "sonata_deprecation_mute" in the following call.
  1386.         $showGroups $this->getShowGroups('sonata_deprecation_mute');
  1387.         $showGroups[$group]['fields'] = array_merge(array_flip($keys), $showGroups[$group]['fields']);
  1388.         $this->setShowGroups($showGroups);
  1389.     }
  1390.     public function setParentFieldDescription(FieldDescriptionInterface $parentFieldDescription)
  1391.     {
  1392.         $this->parentFieldDescription $parentFieldDescription;
  1393.     }
  1394.     public function getParentFieldDescription()
  1395.     {
  1396.         if (!$this->hasParentFieldDescription()) {
  1397.             @trigger_error(sprintf(
  1398.                 'Calling %s() when there is no parent field description is deprecated since'
  1399.                 .' sonata-project/admin-bundle 3.66 and will throw an exception in 4.0.'
  1400.                 .' Use %s::hasParentFieldDescription() to know if there is a parent field description.',
  1401.                 __METHOD__,
  1402.                 __CLASS__
  1403.             ), \E_USER_DEPRECATED);
  1404.             // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
  1405.             // throw new \LogicException(sprintf(
  1406.             //    'Admin "%s" has no parent field description.',
  1407.             //    static::class
  1408.             // ));
  1409.             return null;
  1410.         }
  1411.         return $this->parentFieldDescription;
  1412.     }
  1413.     public function hasParentFieldDescription()
  1414.     {
  1415.         return $this->parentFieldDescription instanceof FieldDescriptionInterface;
  1416.     }
  1417.     public function setSubject($subject)
  1418.     {
  1419.         if (\is_object($subject) && !is_a($subject$this->getClass(), true)) {
  1420.             $message = <<<'EOT'
  1421. You are trying to set entity an instance of "%s",
  1422. which is not the one registered with this admin class ("%s").
  1423. This is deprecated since 3.5 and will no longer be supported in 4.0.
  1424. EOT;
  1425.             // NEXT_MAJOR : throw an exception instead
  1426.             @trigger_error(sprintf($message, \get_class($subject), $this->getClass()), \E_USER_DEPRECATED);
  1427.         }
  1428.         $this->subject $subject;
  1429.     }
  1430.     public function getSubject()
  1431.     {
  1432.         if (!$this->hasSubject()) {
  1433.             @trigger_error(sprintf(
  1434.                 'Calling %s() when there is no subject is deprecated since sonata-project/admin-bundle 3.66'
  1435.                 .' and will throw an exception in 4.0. Use %s::hasSubject() to know if there is a subject.',
  1436.                 __METHOD__,
  1437.                 __CLASS__
  1438.             ), \E_USER_DEPRECATED);
  1439.             // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and update the return type
  1440.             // throw new \LogicException(sprintf(
  1441.             //    'Admin "%s" has no subject.',
  1442.             //    static::class
  1443.             // ));
  1444.             return null;
  1445.         }
  1446.         return $this->subject;
  1447.     }
  1448.     public function hasSubject()
  1449.     {
  1450.         if (null === $this->subject && $this->hasRequest() && !$this->hasParentFieldDescription()) {
  1451.             $id $this->getRequest()->get($this->getIdParameter());
  1452.             if (null !== $id) {
  1453.                 $this->subject $this->getObject($id);
  1454.             }
  1455.         }
  1456.         return null !== $this->subject;
  1457.     }
  1458.     public function getFormFieldDescriptions()
  1459.     {
  1460.         $this->buildForm();
  1461.         return $this->formFieldDescriptions;
  1462.     }
  1463.     public function getFormFieldDescription($name)
  1464.     {
  1465.         $this->buildForm();
  1466.         if (!$this->hasFormFieldDescription($name)) {
  1467.             @trigger_error(sprintf(
  1468.                 'Calling %s() when there is no form field description is deprecated since'
  1469.                 .' sonata-project/admin-bundle 3.69 and will throw an exception in 4.0.'
  1470.                 .' Use %s::hasFormFieldDescription() to know if there is a form field description.',
  1471.                 __METHOD__,
  1472.                 __CLASS__
  1473.             ), \E_USER_DEPRECATED);
  1474.             // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
  1475.             // throw new \LogicException(sprintf(
  1476.             //    'Admin "%s" has no form field description for the field %s.',
  1477.             //    static::class,
  1478.             //    $name
  1479.             // ));
  1480.             return null;
  1481.         }
  1482.         return $this->formFieldDescriptions[$name];
  1483.     }
  1484.     /**
  1485.      * Returns true if the admin has a FieldDescription with the given $name.
  1486.      *
  1487.      * @param string $name
  1488.      *
  1489.      * @return bool
  1490.      */
  1491.     public function hasFormFieldDescription($name)
  1492.     {
  1493.         $this->buildForm();
  1494.         return \array_key_exists($name$this->formFieldDescriptions);
  1495.     }
  1496.     public function addFormFieldDescription($nameFieldDescriptionInterface $fieldDescription)
  1497.     {
  1498.         $this->formFieldDescriptions[$name] = $fieldDescription;
  1499.     }
  1500.     /**
  1501.      * remove a FieldDescription.
  1502.      *
  1503.      * @param string $name
  1504.      */
  1505.     public function removeFormFieldDescription($name)
  1506.     {
  1507.         unset($this->formFieldDescriptions[$name]);
  1508.     }
  1509.     /**
  1510.      * build and return the collection of form FieldDescription.
  1511.      *
  1512.      * @return FieldDescriptionInterface[] collection of form FieldDescription
  1513.      */
  1514.     public function getShowFieldDescriptions()
  1515.     {
  1516.         $this->buildShow();
  1517.         return $this->showFieldDescriptions;
  1518.     }
  1519.     /**
  1520.      * Returns the form FieldDescription with the given $name.
  1521.      *
  1522.      * @param string $name
  1523.      *
  1524.      * @return FieldDescriptionInterface
  1525.      */
  1526.     public function getShowFieldDescription($name)
  1527.     {
  1528.         $this->buildShow();
  1529.         if (!$this->hasShowFieldDescription($name)) {
  1530.             @trigger_error(sprintf(
  1531.                 'Calling %s() when there is no show field description is deprecated since'
  1532.                 .' sonata-project/admin-bundle 3.69 and will throw an exception in 4.0.'
  1533.                 .' Use %s::hasFormFieldDescription() to know if there is a show field description.',
  1534.                 __METHOD__,
  1535.                 __CLASS__
  1536.             ), \E_USER_DEPRECATED);
  1537.             // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
  1538.             // throw new \LogicException(sprintf(
  1539.             //    'Admin "%s" has no show field description for the field %s.',
  1540.             //    static::class,
  1541.             //    $name
  1542.             // ));
  1543.             return null;
  1544.         }
  1545.         return $this->showFieldDescriptions[$name];
  1546.     }
  1547.     public function hasShowFieldDescription($name)
  1548.     {
  1549.         $this->buildShow();
  1550.         return \array_key_exists($name$this->showFieldDescriptions);
  1551.     }
  1552.     public function addShowFieldDescription($nameFieldDescriptionInterface $fieldDescription)
  1553.     {
  1554.         $this->showFieldDescriptions[$name] = $fieldDescription;
  1555.     }
  1556.     public function removeShowFieldDescription($name)
  1557.     {
  1558.         unset($this->showFieldDescriptions[$name]);
  1559.     }
  1560.     public function getListFieldDescriptions()
  1561.     {
  1562.         $this->buildList();
  1563.         return $this->listFieldDescriptions;
  1564.     }
  1565.     public function getListFieldDescription($name)
  1566.     {
  1567.         $this->buildList();
  1568.         if (!$this->hasListFieldDescription($name)) {
  1569.             @trigger_error(sprintf(
  1570.                 'Calling %s() when there is no list field description is deprecated since'
  1571.                 .' sonata-project/admin-bundle 3.66 and will throw an exception in 4.0.'
  1572.                 .' Use %s::hasListFieldDescription(\'%s\') to know if there is a list field description.',
  1573.                 __METHOD__,
  1574.                 __CLASS__,
  1575.                 $name
  1576.             ), \E_USER_DEPRECATED);
  1577.             // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
  1578.             // throw new \LogicException(sprintf(
  1579.             //    'Admin "%s" has no list field description for %s.',
  1580.             //    static::class,
  1581.             //    $name
  1582.             // ));
  1583.             return null;
  1584.         }
  1585.         return $this->listFieldDescriptions[$name];
  1586.     }
  1587.     public function hasListFieldDescription($name)
  1588.     {
  1589.         $this->buildList();
  1590.         return \array_key_exists($name$this->listFieldDescriptions);
  1591.     }
  1592.     public function addListFieldDescription($nameFieldDescriptionInterface $fieldDescription)
  1593.     {
  1594.         $this->listFieldDescriptions[$name] = $fieldDescription;
  1595.     }
  1596.     public function removeListFieldDescription($name)
  1597.     {
  1598.         unset($this->listFieldDescriptions[$name]);
  1599.     }
  1600.     public function getFilterFieldDescription($name)
  1601.     {
  1602.         $this->buildDatagrid();
  1603.         if (!$this->hasFilterFieldDescription($name)) {
  1604.             @trigger_error(sprintf(
  1605.                 'Calling %s() when there is no filter field description is deprecated since'
  1606.                 .' sonata-project/admin-bundle 3.69 and will throw an exception in 4.0.'
  1607.                 .' Use %s::hasFilterFieldDescription() to know if there is a filter field description.',
  1608.                 __METHOD__,
  1609.                 __CLASS__
  1610.             ), \E_USER_DEPRECATED);
  1611.             // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare FieldDescriptionInterface as return type
  1612.             // throw new \LogicException(sprintf(
  1613.             //    'Admin "%s" has no filter field description for the field %s.',
  1614.             //    static::class,
  1615.             //    $name
  1616.             // ));
  1617.             return null;
  1618.         }
  1619.         return $this->filterFieldDescriptions[$name];
  1620.     }
  1621.     public function hasFilterFieldDescription($name)
  1622.     {
  1623.         $this->buildDatagrid();
  1624.         return \array_key_exists($name$this->filterFieldDescriptions);
  1625.     }
  1626.     public function addFilterFieldDescription($nameFieldDescriptionInterface $fieldDescription)
  1627.     {
  1628.         $this->filterFieldDescriptions[$name] = $fieldDescription;
  1629.     }
  1630.     public function removeFilterFieldDescription($name)
  1631.     {
  1632.         unset($this->filterFieldDescriptions[$name]);
  1633.     }
  1634.     public function getFilterFieldDescriptions()
  1635.     {
  1636.         $this->buildDatagrid();
  1637.         return $this->filterFieldDescriptions;
  1638.     }
  1639.     public function addChild(AdminInterface $child)
  1640.     {
  1641.         $parentAdmin $this;
  1642.         while ($parentAdmin->isChild() && $parentAdmin->getCode() !== $child->getCode()) {
  1643.             $parentAdmin $parentAdmin->getParent();
  1644.         }
  1645.         if ($parentAdmin->getCode() === $child->getCode()) {
  1646.             // NEXT_MAJOR: Throw \LogicException instead
  1647.             throw new \RuntimeException(sprintf(
  1648.                 'Circular reference detected! The child admin `%s` is already in the parent tree of the `%s` admin.',
  1649.                 $child->getCode(),
  1650.                 $this->getCode()
  1651.             ));
  1652.         }
  1653.         $this->children[$child->getCode()] = $child;
  1654.         $child->setParent($this);
  1655.         // NEXT_MAJOR: remove $args and add $field parameter to this function on next Major
  1656.         $args = \func_get_args();
  1657.         if (isset($args[1])) {
  1658.             $child->addParentAssociationMapping($this->getCode(), $args[1]);
  1659.         } else {
  1660.             @trigger_error(
  1661.                 'Calling "addChild" without second argument is deprecated since sonata-project/admin-bundle 3.35 and will not be allowed in 4.0.',
  1662.                 \E_USER_DEPRECATED
  1663.             );
  1664.         }
  1665.     }
  1666.     public function hasChild($code)
  1667.     {
  1668.         return isset($this->children[$code]);
  1669.     }
  1670.     public function getChildren()
  1671.     {
  1672.         return $this->children;
  1673.     }
  1674.     public function getChild($code)
  1675.     {
  1676.         if (!$this->hasChild($code)) {
  1677.             @trigger_error(sprintf(
  1678.                 'Calling %s() when there is no child is deprecated since sonata-project/admin-bundle 3.69'
  1679.                 .' and will throw an exception in 4.0. Use %s::hasChild() to know if the child exists.',
  1680.                 __METHOD__,
  1681.                 __CLASS__
  1682.             ), \E_USER_DEPRECATED);
  1683.             // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare AdminInterface as return type
  1684.             // throw new \LogicException(sprintf(
  1685.             //    'Admin "%s" has no child for the code %s.',
  1686.             //    static::class,
  1687.             //    $code
  1688.             // ));
  1689.             return null;
  1690.         }
  1691.         return $this->getChildren()[$code];
  1692.     }
  1693.     public function setParent(AdminInterface $parent)
  1694.     {
  1695.         $this->parent $parent;
  1696.     }
  1697.     public function getParent()
  1698.     {
  1699.         if (!$this->isChild()) {
  1700.             @trigger_error(sprintf(
  1701.                 'Calling %s() when there is no parent is deprecated since sonata-project/admin-bundle 3.66'
  1702.                 .' and will throw an exception in 4.0. Use %s::isChild() to know if there is a parent.',
  1703.                 __METHOD__,
  1704.                 __CLASS__
  1705.             ), \E_USER_DEPRECATED);
  1706.             // NEXT_MAJOR : remove the previous `trigger_error()` call, the `return null` statement, uncomment the following exception and declare AdminInterface as return type
  1707.             // throw new \LogicException(sprintf(
  1708.             //    'Admin "%s" has no parent.',
  1709.             //    static::class
  1710.             // ));
  1711.             return null;
  1712.         }
  1713.         return $this->parent;
  1714.     }
  1715.     final public function getRootAncestor()
  1716.     {
  1717.         $parent $this;
  1718.         while ($parent->isChild()) {
  1719.             $parent $parent->getParent();
  1720.         }
  1721.         return $parent;
  1722.     }
  1723.     final public function getChildDepth()
  1724.     {
  1725.         $parent $this;
  1726.         $depth 0;
  1727.         while ($parent->isChild()) {
  1728.             $parent $parent->getParent();
  1729.             ++$depth;
  1730.         }
  1731.         return $depth;
  1732.     }
  1733.     final public function getCurrentLeafChildAdmin()
  1734.     {
  1735.         $child $this->getCurrentChildAdmin();
  1736.         if (null === $child) {
  1737.             return null;
  1738.         }
  1739.         for ($c $childnull !== $c$c $child->getCurrentChildAdmin()) {
  1740.             $child $c;
  1741.         }
  1742.         return $child;
  1743.     }
  1744.     public function isChild()
  1745.     {
  1746.         return $this->parent instanceof AdminInterface;
  1747.     }
  1748.     /**
  1749.      * Returns true if the admin has children, false otherwise.
  1750.      *
  1751.      * @return bool if the admin has children
  1752.      */
  1753.     public function hasChildren()
  1754.     {
  1755.         return \count($this->children) > 0;
  1756.     }
  1757.     public function setUniqid($uniqid)
  1758.     {
  1759.         $this->uniqid $uniqid;
  1760.     }
  1761.     public function getUniqid()
  1762.     {
  1763.         if (!$this->uniqid) {
  1764.             $this->uniqid sprintf('s%s'uniqid());
  1765.         }
  1766.         return $this->uniqid;
  1767.     }
  1768.     /**
  1769.      * Returns the classname label.
  1770.      *
  1771.      * @return string the classname label
  1772.      */
  1773.     public function getClassnameLabel()
  1774.     {
  1775.         if (null === $this->classnameLabel) {
  1776.             // NEXT_MAJOR: Remove this deprecation and uncomment the following exception
  1777.             @trigger_error(sprintf(
  1778.                 'Calling %s() when no classname label is set is deprecated since sonata-project/admin-bundle 3.84'
  1779.                 .' and will throw a LogicException in 4.0',
  1780.                 __METHOD__,
  1781.             ), \E_USER_DEPRECATED);
  1782. //            throw new \LogicException(sprintf(
  1783. //                'Admin "%s" has no classname label. Did you forgot to initialize the admin ?',
  1784. //                static::class
  1785. //            ));
  1786.         }
  1787.         return $this->classnameLabel;
  1788.     }
  1789.     /**
  1790.      * @final since sonata-project/admin-bundle 3.90
  1791.      */
  1792.     public function getPersistentParameters()
  1793.     {
  1794.         $parameters $this->configurePersistentParameters();
  1795.         foreach ($this->getExtensions() as $extension) {
  1796.             // NEXT_MAJOR: Remove the check and the else part.
  1797.             if (method_exists($extension'configurePersistentParameters')) {
  1798.                 $parameters $extension->configurePersistentParameters($this$parameters);
  1799.             } else {
  1800.                 $params $extension->getPersistentParameters($this);
  1801.                 // NEXT_MAJOR: Remove this check, since return typehint is added
  1802.                 if (!\is_array($params)) {
  1803.                     throw new \RuntimeException(sprintf(
  1804.                         'Method "%s::getPersistentParameters()" must return an array.',
  1805.                         \get_class($extension)
  1806.                     ));
  1807.                 }
  1808.                 $parameters array_merge($parameters$params);
  1809.             }
  1810.         }
  1811.         return $parameters;
  1812.     }
  1813.     /**
  1814.      * @param string $name
  1815.      *
  1816.      * @return mixed|null
  1817.      */
  1818.     public function getPersistentParameter($name)
  1819.     {
  1820.         $parameters $this->getPersistentParameters();
  1821.         return $parameters[$name] ?? null;
  1822.     }
  1823.     public function getBreadcrumbs($action)
  1824.     {
  1825.         @trigger_error(sprintf(
  1826.             'The %s method is deprecated since version 3.2 and will be removed in 4.0.'
  1827.             .' Use %s::getBreadcrumbs instead.',
  1828.             __METHOD__,
  1829.             BreadcrumbsBuilder::class
  1830.         ), \E_USER_DEPRECATED);
  1831.         return $this->getBreadcrumbsBuilder()->getBreadcrumbs($this$action);
  1832.     }
  1833.     /**
  1834.      * Generates the breadcrumbs array.
  1835.      *
  1836.      * Note: the method will be called by the top admin instance (parent => child)
  1837.      *
  1838.      * @param string $action
  1839.      *
  1840.      * @return ItemInterface|null
  1841.      */
  1842.     public function buildBreadcrumbs($action, ?ItemInterface $menu null)
  1843.     {
  1844.         @trigger_error(sprintf(
  1845.             'The %s method is deprecated since version 3.2 and will be removed in 4.0.',
  1846.             __METHOD__
  1847.         ), \E_USER_DEPRECATED);
  1848.         if (isset($this->breadcrumbs[$action])) {
  1849.             return $this->breadcrumbs[$action];
  1850.         }
  1851.         return $this->breadcrumbs[$action] = $this->getBreadcrumbsBuilder()
  1852.             ->buildBreadcrumbs($this$action$menu);
  1853.     }
  1854.     /**
  1855.      * NEXT_MAJOR : remove this method.
  1856.      *
  1857.      * @return BreadcrumbsBuilderInterface
  1858.      */
  1859.     final public function getBreadcrumbsBuilder()
  1860.     {
  1861.         @trigger_error(sprintf(
  1862.             'The %s method is deprecated since version 3.2 and will be removed in 4.0.'
  1863.             .' Use the sonata.admin.breadcrumbs_builder service instead.',
  1864.             __METHOD__
  1865.         ), \E_USER_DEPRECATED);
  1866.         if (null === $this->breadcrumbsBuilder) {
  1867.             $this->breadcrumbsBuilder = new BreadcrumbsBuilder(
  1868.                 $this->getConfigurationPool()->getContainer('sonata_deprecation_mute')->getParameter('sonata.admin.configuration.breadcrumbs')
  1869.             );
  1870.         }
  1871.         return $this->breadcrumbsBuilder;
  1872.     }
  1873.     /**
  1874.      * NEXT_MAJOR : remove this method.
  1875.      *
  1876.      * @return AbstractAdmin
  1877.      */
  1878.     final public function setBreadcrumbsBuilder(BreadcrumbsBuilderInterface $value)
  1879.     {
  1880.         @trigger_error(sprintf(
  1881.             'The %s method is deprecated since version 3.2 and will be removed in 4.0.'
  1882.             .' Use the sonata.admin.breadcrumbs_builder service instead.',
  1883.             __METHOD__
  1884.         ), \E_USER_DEPRECATED);
  1885.         $this->breadcrumbsBuilder $value;
  1886.         return $this;
  1887.     }
  1888.     public function setCurrentChild($currentChild)
  1889.     {
  1890.         $this->currentChild $currentChild;
  1891.     }
  1892.     /**
  1893.      * NEXT_MAJOR: Remove this method.
  1894.      *
  1895.      * @deprecated since sonata-project/admin-bundle 3.65, to be removed in 4.0
  1896.      */
  1897.     public function getCurrentChild()
  1898.     {
  1899.         @trigger_error(sprintf(
  1900.             'The %s() method is deprecated since version 3.65 and will be removed in 4.0.'
  1901.             .' Use %s::isCurrentChild() instead.',
  1902.             __METHOD__,
  1903.             __CLASS__
  1904.         ), \E_USER_DEPRECATED);
  1905.         return $this->currentChild;
  1906.     }
  1907.     public function isCurrentChild(): bool
  1908.     {
  1909.         return $this->currentChild;
  1910.     }
  1911.     /**
  1912.      * Returns the current child admin instance.
  1913.      *
  1914.      * @return AdminInterface|null the current child admin instance
  1915.      */
  1916.     public function getCurrentChildAdmin()
  1917.     {
  1918.         foreach ($this->getChildren() as $child) {
  1919.             // NEXT_MAJOR: Remove method_exists check and delete elseif case
  1920.             if (method_exists($child'isCurrentChild')) {
  1921.                 if ($child->isCurrentChild()) {
  1922.                     return $child;
  1923.                 }
  1924.             } else {
  1925.                 if ($child->getCurrentChild()) {
  1926.                     return $child;
  1927.                 }
  1928.             }
  1929.         }
  1930.         return null;
  1931.     }
  1932.     public function trans($id, array $parameters = [], $domain null$locale null)
  1933.     {
  1934.         @trigger_error(sprintf(
  1935.             'The %s method is deprecated since version 3.9 and will be removed in 4.0.',
  1936.             __METHOD__
  1937.         ), \E_USER_DEPRECATED);
  1938.         $domain $domain ?: $this->getTranslationDomain();
  1939.         return $this->getTranslator()->trans($id$parameters$domain$locale);
  1940.     }
  1941.     /**
  1942.      * Translate a message id.
  1943.      *
  1944.      * NEXT_MAJOR: remove this method
  1945.      *
  1946.      * @param string      $id
  1947.      * @param int         $count
  1948.      * @param string|null $domain
  1949.      * @param string|null $locale
  1950.      *
  1951.      * @return string the translated string
  1952.      *
  1953.      * @deprecated since sonata-project/admin-bundle 3.9, to be removed with 4.0
  1954.      */
  1955.     public function transChoice($id$count, array $parameters = [], $domain null$locale null)
  1956.     {
  1957.         @trigger_error(sprintf(
  1958.             'The %s method is deprecated since version 3.9 and will be removed in 4.0.',
  1959.             __METHOD__
  1960.         ), \E_USER_DEPRECATED);
  1961.         $domain $domain ?: $this->getTranslationDomain();
  1962.         return $this->getTranslator()->transChoice($id$count$parameters$domain$locale);
  1963.     }
  1964.     public function setTranslationDomain($translationDomain)
  1965.     {
  1966.         $this->translationDomain $translationDomain;
  1967.     }
  1968.     public function getTranslationDomain()
  1969.     {
  1970.         return $this->translationDomain;
  1971.     }
  1972.     public function getTranslationLabel($label$context ''$type '')
  1973.     {
  1974.         return $this->getLabelTranslatorStrategy()->getLabel($label$context$type);
  1975.     }
  1976.     public function setRequest(Request $request)
  1977.     {
  1978.         $this->request $request;
  1979.         foreach ($this->getChildren() as $children) {
  1980.             $children->setRequest($request);
  1981.         }
  1982.     }
  1983.     public function getRequest()
  1984.     {
  1985.         if (!$this->request) {
  1986.             // NEXT_MAJOR: Throw \LogicException instead.
  1987.             throw new \RuntimeException('The Request object has not been set');
  1988.         }
  1989.         return $this->request;
  1990.     }
  1991.     public function hasRequest()
  1992.     {
  1993.         return null !== $this->request;
  1994.     }
  1995.     public function getCode()
  1996.     {
  1997.         return $this->code;
  1998.     }
  1999.     /**
  2000.      * NEXT_MAJOR: Remove this function.
  2001.      *
  2002.      * @deprecated This method is deprecated since sonata-project/admin-bundle 3.24 and will be removed in 4.0
  2003.      *
  2004.      * @param string $baseCodeRoute
  2005.      */
  2006.     public function setBaseCodeRoute($baseCodeRoute)
  2007.     {
  2008.         @trigger_error(sprintf(
  2009.             'The %s is deprecated since 3.24 and will be removed in 4.0.',
  2010.             __METHOD__
  2011.         ), \E_USER_DEPRECATED);
  2012.         $this->baseCodeRoute $baseCodeRoute;
  2013.     }
  2014.     public function getBaseCodeRoute()
  2015.     {
  2016.         // NEXT_MAJOR: Uncomment the following lines.
  2017.         // if ($this->isChild()) {
  2018.         //     return sprintf('%s|%s', $this->getParent()->getBaseCodeRoute(), $this->getCode());
  2019.         // }
  2020.         //
  2021.         // return $this->getCode();
  2022.         // NEXT_MAJOR: Remove all the code below.
  2023.         if ($this->isChild()) {
  2024.             $parentCode $this->getParent()->getCode();
  2025.             if ($this->getParent()->isChild()) {
  2026.                 $parentCode $this->getParent()->getBaseCodeRoute();
  2027.             }
  2028.             return sprintf('%s|%s'$parentCode$this->getCode());
  2029.         }
  2030.         return $this->baseCodeRoute;
  2031.     }
  2032.     public function getObjectIdentifier()
  2033.     {
  2034.         return $this->getCode();
  2035.     }
  2036.     /**
  2037.      * Return the list of permissions the user should have in order to display the admin.
  2038.      *
  2039.      * @param string $context
  2040.      *
  2041.      * @return string[]
  2042.      */
  2043.     public function getPermissionsShow($context)
  2044.     {
  2045.         return ['LIST'];
  2046.     }
  2047.     public function showIn($context)
  2048.     {
  2049.         return $this->isGranted($this->getPermissionsShow($context));
  2050.     }
  2051.     public function createObjectSecurity($object)
  2052.     {
  2053.         $this->getSecurityHandler()->createObjectSecurity($this$object);
  2054.     }
  2055.     public function isGranted($name$object null)
  2056.     {
  2057.         $objectRef $object sprintf('/%s#%s'spl_object_hash($object), $this->id($object)) : '';
  2058.         $key md5(json_encode($name).$objectRef);
  2059.         if (!\array_key_exists($key$this->cacheIsGranted)) {
  2060.             $this->cacheIsGranted[$key] = $this->getSecurityHandler()->isGranted($this$name$object ?: $this);
  2061.         }
  2062.         return $this->cacheIsGranted[$key];
  2063.     }
  2064.     public function getUrlSafeIdentifier($model)
  2065.     {
  2066.         return $this->getModelManager()->getUrlSafeIdentifier($model);
  2067.     }
  2068.     public function getNormalizedIdentifier($model)
  2069.     {
  2070.         return $this->getModelManager()->getNormalizedIdentifier($model);
  2071.     }
  2072.     public function id($model)
  2073.     {
  2074.         return $this->getNormalizedIdentifier($model);
  2075.     }
  2076.     public function getShow()
  2077.     {
  2078.         $this->buildShow();
  2079.         return $this->show;
  2080.     }
  2081.     public function setFormTheme(array $formTheme)
  2082.     {
  2083.         $this->formTheme $formTheme;
  2084.     }
  2085.     public function getFormTheme()
  2086.     {
  2087.         return $this->formTheme;
  2088.     }
  2089.     public function setFilterTheme(array $filterTheme)
  2090.     {
  2091.         $this->filterTheme $filterTheme;
  2092.     }
  2093.     public function getFilterTheme()
  2094.     {
  2095.         return $this->filterTheme;
  2096.     }
  2097.     public function addExtension(AdminExtensionInterface $extension)
  2098.     {
  2099.         $this->extensions[] = $extension;
  2100.     }
  2101.     public function getExtensions()
  2102.     {
  2103.         return $this->extensions;
  2104.     }
  2105.     public function toString($object)
  2106.     {
  2107.         // NEXT_MAJOR: Remove this check and use object as param typehint.
  2108.         if (!\is_object($object)) {
  2109.             @trigger_error(sprintf(
  2110.                 'Passing %s as argument 1 for %s() is deprecated since sonata-project/admin-bundle 3.76.'
  2111.                 .' Only object will be allowed in version 4.0.',
  2112.                 \gettype($object),
  2113.                 __METHOD__
  2114.             ), \E_USER_DEPRECATED);
  2115.             return '';
  2116.         }
  2117.         if (method_exists($object'__toString') && null !== $object->__toString()) {
  2118.             return $object->__toString();
  2119.         }
  2120.         return sprintf('%s:%s'ClassUtils::getClass($object), spl_object_hash($object));
  2121.     }
  2122.     public function supportsPreviewMode()
  2123.     {
  2124.         return $this->supportsPreviewMode;
  2125.     }
  2126.     /**
  2127.      * NEXT_MAJOR: Remove this.
  2128.      *
  2129.      * @deprecated since sonata-project/admin-bundle 3.67, to be removed in 4.0.
  2130.      *
  2131.      * Set custom per page options.
  2132.      */
  2133.     public function setPerPageOptions(array $options)
  2134.     {
  2135.         @trigger_error(sprintf(
  2136.             'The method %s is deprecated since sonata-project/admin-bundle 3.67 and will be removed in 4.0.',
  2137.             __METHOD__
  2138.         ), \E_USER_DEPRECATED);
  2139.         $this->perPageOptions $options;
  2140.     }
  2141.     /**
  2142.      * Returns predefined per page options.
  2143.      *
  2144.      * @return array<string, mixed>
  2145.      */
  2146.     public function getPerPageOptions()
  2147.     {
  2148.         // NEXT_MAJOR: Remove this line and uncomment the following
  2149.         return $this->perPageOptions;
  2150. //        $perPageOptions = [10, 25, 50, 100, 250];
  2151. //        $perPageOptions[] = $this->getMaxPerPage();
  2152. //
  2153. //        $perPageOptions = array_unique($perPageOptions);
  2154. //        sort($perPageOptions);
  2155. //
  2156. //        return $perPageOptions;
  2157.     }
  2158.     /**
  2159.      * Returns true if the per page value is allowed, false otherwise.
  2160.      *
  2161.      * @param int $perPage
  2162.      *
  2163.      * @return bool
  2164.      */
  2165.     public function determinedPerPageValue($perPage)
  2166.     {
  2167.         return \in_array($perPage$this->getPerPageOptions(), true);
  2168.     }
  2169.     public function isAclEnabled()
  2170.     {
  2171.         return $this->getSecurityHandler() instanceof AclSecurityHandlerInterface;
  2172.     }
  2173.     public function getObjectMetadata($object)
  2174.     {
  2175.         return new Metadata($this->toString($object));
  2176.     }
  2177.     public function setListMode($mode)
  2178.     {
  2179.         $this->getRequest()->getSession()->set(sprintf('%s.list_mode'$this->getCode()), $mode);
  2180.     }
  2181.     public function getListMode()
  2182.     {
  2183.         if (!$this->hasRequest()) {
  2184.             return 'list';
  2185.         }
  2186.         return $this->getRequest()->getSession()->get(sprintf('%s.list_mode'$this->getCode()), 'list');
  2187.     }
  2188.     public function getAccessMapping()
  2189.     {
  2190.         return $this->accessMapping;
  2191.     }
  2192.     public function checkAccess($action$object null)
  2193.     {
  2194.         $access $this->getAccess();
  2195.         if (!\array_key_exists($action$access)) {
  2196.             throw new \InvalidArgumentException(sprintf(
  2197.                 'Action "%s" could not be found in access mapping.'
  2198.                 .' Please make sure your action is defined into your admin class accessMapping property.',
  2199.                 $action
  2200.             ));
  2201.         }
  2202.         if (!\is_array($access[$action])) {
  2203.             $access[$action] = [$access[$action]];
  2204.         }
  2205.         foreach ($access[$action] as $role) {
  2206.             if (false === $this->isGranted($role$object)) {
  2207.                 throw new AccessDeniedException(sprintf('Access Denied to the action %s and role %s'$action$role));
  2208.             }
  2209.         }
  2210.     }
  2211.     /**
  2212.      * Hook to handle access authorization, without throw Exception.
  2213.      *
  2214.      * @param string      $action
  2215.      * @param object|null $object
  2216.      *
  2217.      * @return bool
  2218.      *
  2219.      * @phpstan-param T|null $object
  2220.      */
  2221.     public function hasAccess($action$object null)
  2222.     {
  2223.         $access $this->getAccess();
  2224.         if (!\array_key_exists($action$access)) {
  2225.             return false;
  2226.         }
  2227.         if (!\is_array($access[$action])) {
  2228.             $access[$action] = [$access[$action]];
  2229.         }
  2230.         foreach ($access[$action] as $role) {
  2231.             if (false === $this->isGranted($role$object)) {
  2232.                 return false;
  2233.             }
  2234.         }
  2235.         return true;
  2236.     }
  2237.     /**
  2238.      * @param string      $action
  2239.      * @param object|null $object
  2240.      *
  2241.      * @return array<string, array<string, mixed>>
  2242.      *
  2243.      * @phpstan-param T|null $object
  2244.      */
  2245.     public function configureActionButtons($action$object null)
  2246.     {
  2247.         $list = [];
  2248.         if (\in_array($action, ['tree''show''edit''delete''list''batch'], true)
  2249.             && $this->hasRoute('create')
  2250.             && $this->hasAccess('create')
  2251.         ) {
  2252.             $list['create'] = [
  2253.                 // NEXT_MAJOR: Remove this line and use commented line below it instead
  2254.                 'template' => $this->getTemplate('button_create'),
  2255. //                'template' => $this->getTemplateRegistry()->getTemplate('button_create'),
  2256.             ];
  2257.         }
  2258.         if (\in_array($action, ['show''delete''acl''history'], true)
  2259.             && $this->hasRoute('edit')
  2260.             && $this->canAccessObject('edit'$object)
  2261.         ) {
  2262.             $list['edit'] = [
  2263.                 // NEXT_MAJOR: Remove this line and use commented line below it instead
  2264.                 'template' => $this->getTemplate('button_edit'),
  2265.                 //'template' => $this->getTemplateRegistry()->getTemplate('button_edit'),
  2266.             ];
  2267.         }
  2268.         if (\in_array($action, ['show''edit''acl'], true)
  2269.             && $this->hasRoute('history')
  2270.             && $this->canAccessObject('history'$object)
  2271.         ) {
  2272.             $list['history'] = [
  2273.                 // NEXT_MAJOR: Remove this line and use commented line below it instead
  2274.                 'template' => $this->getTemplate('button_history'),
  2275.                 // 'template' => $this->getTemplateRegistry()->getTemplate('button_history'),
  2276.             ];
  2277.         }
  2278.         if (\in_array($action, ['edit''history'], true)
  2279.             && $this->isAclEnabled()
  2280.             && $this->hasRoute('acl')
  2281.             && $this->canAccessObject('acl'$object)
  2282.         ) {
  2283.             $list['acl'] = [
  2284.                 // NEXT_MAJOR: Remove this line and use commented line below it instead
  2285.                 'template' => $this->getTemplate('button_acl'),
  2286.                 // 'template' => $this->getTemplateRegistry()->getTemplate('button_acl'),
  2287.             ];
  2288.         }
  2289.         if (\in_array($action, ['edit''history''acl'], true)
  2290.             && $this->hasRoute('show')
  2291.             && $this->canAccessObject('show'$object)
  2292.             && \count($this->getShow()) > 0
  2293.         ) {
  2294.             $list['show'] = [
  2295.                 // NEXT_MAJOR: Remove this line and use commented line below it instead
  2296.                 'template' => $this->getTemplate('button_show'),
  2297.                 // 'template' => $this->getTemplateRegistry()->getTemplate('button_show'),
  2298.             ];
  2299.         }
  2300.         if (\in_array($action, ['show''edit''delete''acl''batch'], true)
  2301.             && $this->hasRoute('list')
  2302.             && $this->hasAccess('list')
  2303.         ) {
  2304.             $list['list'] = [
  2305.                 // NEXT_MAJOR: Remove this line and use commented line below it instead
  2306.                 'template' => $this->getTemplate('button_list'),
  2307.                 // 'template' => $this->getTemplateRegistry()->getTemplate('button_list'),
  2308.             ];
  2309.         }
  2310.         return $list;
  2311.     }
  2312.     /**
  2313.      * @param string      $action
  2314.      * @param object|null $object
  2315.      *
  2316.      * @return array<string, array<string, mixed>>
  2317.      *
  2318.      * @phpstan-param T|null $object
  2319.      */
  2320.     public function getActionButtons($action$object null)
  2321.     {
  2322.         $list $this->configureActionButtons($action$object);
  2323.         foreach ($this->getExtensions() as $extension) {
  2324.             // NEXT_MAJOR: remove method check
  2325.             if (method_exists($extension'configureActionButtons')) {
  2326.                 $list $extension->configureActionButtons($this$list$action$object);
  2327.             }
  2328.         }
  2329.         return $list;
  2330.     }
  2331.     /**
  2332.      * Get the list of actions that can be accessed directly from the dashboard.
  2333.      *
  2334.      * @return array<string, array<string, mixed>>
  2335.      */
  2336.     public function getDashboardActions()
  2337.     {
  2338.         $actions = [];
  2339.         if ($this->hasRoute('create') && $this->hasAccess('create')) {
  2340.             $actions['create'] = [
  2341.                 'label' => 'link_add',
  2342.                 'translation_domain' => 'SonataAdminBundle',
  2343.                 // NEXT_MAJOR: Remove this line and use commented line below it instead
  2344.                 'template' => $this->getTemplate('action_create'),
  2345.                 // 'template' => $this->getTemplateRegistry()->getTemplate('action_create'),
  2346.                 'url' => $this->generateUrl('create'),
  2347.                 'icon' => 'plus-circle',
  2348.             ];
  2349.         }
  2350.         if ($this->hasRoute('list') && $this->hasAccess('list')) {
  2351.             $actions['list'] = [
  2352.                 'label' => 'link_list',
  2353.                 'translation_domain' => 'SonataAdminBundle',
  2354.                 'url' => $this->generateUrl('list'),
  2355.                 'icon' => 'list',
  2356.             ];
  2357.         }
  2358.         return $actions;
  2359.     }
  2360.     /**
  2361.      * @param object $object
  2362.      *
  2363.      * @phpstan-param T $object
  2364.      */
  2365.     final public function getSearchResultLink($object)
  2366.     {
  2367.         foreach ($this->searchResultActions as $action) {
  2368.             if ($this->hasRoute($action) && $this->hasAccess($action$object)) {
  2369.                 return $this->generateObjectUrl($action$object);
  2370.             }
  2371.         }
  2372.         return null;
  2373.     }
  2374.     /**
  2375.      * NEXT_MAJOR: remove this method.
  2376.      *
  2377.      * Checks if a filter type is set to a default value.
  2378.      *
  2379.      * @param string $name
  2380.      *
  2381.      * @return bool
  2382.      */
  2383.     final public function isDefaultFilter($name)
  2384.     {
  2385.         @trigger_error(sprintf(
  2386.             'Method "%s" is deprecated since sonata-project/admin-bundle 3.76.',
  2387.             __METHOD__
  2388.         ), \E_USER_DEPRECATED);
  2389.         $filter $this->getFilterParameters();
  2390.         $default $this->getDefaultFilterValues();
  2391.         if (!\array_key_exists($name$filter) || !\array_key_exists($name$default)) {
  2392.             return false;
  2393.         }
  2394.         return $filter[$name] === $default[$name];
  2395.     }
  2396.     /**
  2397.      * Check object existence and access, without throw Exception.
  2398.      *
  2399.      * @param string $action
  2400.      * @param object $object
  2401.      *
  2402.      * @return bool
  2403.      *
  2404.      * @phpstan-param T $object
  2405.      */
  2406.     public function canAccessObject($action$object)
  2407.     {
  2408.         if (!\is_object($object)) {
  2409.             return false;
  2410.         }
  2411.         if (!$this->id($object)) {
  2412.             return false;
  2413.         }
  2414.         return $this->hasAccess($action$object);
  2415.     }
  2416.     final public function getTemplateRegistry(): ?MutableTemplateRegistryInterface
  2417.     {
  2418.         // NEXT_MAJOR: Remove the deprecation and uncomment the exception.
  2419.         if (!$this->hasTemplateRegistry()) {
  2420.             @trigger_error(sprintf(
  2421.                 'Calling %s() when there is no template registry is deprecated since sonata-project/admin-bundle 3.76'
  2422.                 .' and will throw an exception in 4.0.'
  2423.                 .' Use %s::hasTemplateRegistry() to know if the template registry is set.',
  2424.                 __METHOD__,
  2425.                 __CLASS__
  2426.             ), \E_USER_DEPRECATED);
  2427.         }
  2428.         //if (false === $this->hasTemplateRegistry()) {
  2429.         //    throw new \LogicException(sprintf('Unable to find the template registry for admin `%s`.', static::class));
  2430.         //}
  2431.         return $this->templateRegistry;
  2432.     }
  2433.     final public function hasTemplateRegistry(): bool
  2434.     {
  2435.         return null !== $this->templateRegistry;
  2436.     }
  2437.     /**
  2438.      * @phpstan-return T
  2439.      */
  2440.     protected function createNewInstance(): object
  2441.     {
  2442.         // NEXT_MAJOR: Uncomment next line and remove the other one.
  2443.         // return Instantiator::instantiate($this->getClass());
  2444.         /* @phpstan-ignore-next-line */
  2445.         return $this->getModelManager()->getModelInstance($this->getClass(), 'sonata_deprecation_mute');
  2446.     }
  2447.     /**
  2448.      * @phpstan-param T $object
  2449.      */
  2450.     protected function alterNewInstance(object $object): void
  2451.     {
  2452.     }
  2453.     /**
  2454.      * @phpstan-param T $object
  2455.      */
  2456.     protected function alterObject(object $object): void
  2457.     {
  2458.     }
  2459.     /**
  2460.      * @return array<string, mixed>
  2461.      */
  2462.     protected function configurePersistentParameters(): array
  2463.     {
  2464.         return [];
  2465.     }
  2466.     /**
  2467.      * @return string[]
  2468.      */
  2469.     protected function configureExportFields(): array
  2470.     {
  2471.         return $this->getModelManager()->getExportFields($this->getClass());
  2472.     }
  2473.     protected function configureQuery(ProxyQueryInterface $query): ProxyQueryInterface
  2474.     {
  2475.         return $query;
  2476.     }
  2477.     /**
  2478.      * Returns a list of default sort values.
  2479.      *
  2480.      * @return array{_page?: int, _per_page?: int, _sort_by?: string, _sort_order?: string}
  2481.      */
  2482.     final protected function getDefaultSortValues(): array
  2483.     {
  2484.         // NEXT_MAJOR: Use the next line instead.
  2485.         $defaultSortValues = [];
  2486.         // $defaultSortValues = ['_page' => 1, '_per_page' => 25];
  2487.         $this->configureDefaultSortValues($defaultSortValues);
  2488.         foreach ($this->getExtensions() as $extension) {
  2489.             // NEXT_MAJOR: remove method check
  2490.             if (method_exists($extension'configureDefaultSortValues')) {
  2491.                 $extension->configureDefaultSortValues($this$defaultSortValues);
  2492.             }
  2493.         }
  2494.         return $defaultSortValues;
  2495.     }
  2496.     /**
  2497.      * Returns a list of default filters.
  2498.      *
  2499.      * @return array<string, mixed>
  2500.      */
  2501.     final protected function getDefaultFilterValues()
  2502.     {
  2503.         $defaultFilterValues = [];
  2504.         $this->configureDefaultFilterValues($defaultFilterValues);
  2505.         foreach ($this->getExtensions() as $extension) {
  2506.             // NEXT_MAJOR: remove method check
  2507.             if (method_exists($extension'configureDefaultFilterValues')) {
  2508.                 $extension->configureDefaultFilterValues($this$defaultFilterValues);
  2509.             }
  2510.         }
  2511.         return $defaultFilterValues;
  2512.     }
  2513.     /**
  2514.      * Returns a list of form options.
  2515.      *
  2516.      * @return array<string, mixed>
  2517.      */
  2518.     final protected function getFormOptions()
  2519.     {
  2520.         $formOptions = [];
  2521.         $this->configureFormOptions($formOptions);
  2522.         foreach ($this->getExtensions() as $extension) {
  2523.             // NEXT_MAJOR: remove method check
  2524.             if (method_exists($extension'configureFormOptions')) {
  2525.                 $extension->configureFormOptions($this$formOptions);
  2526.             }
  2527.         }
  2528.         return $formOptions;
  2529.     }
  2530.     protected function configureFormFields(FormMapper $form)
  2531.     {
  2532.     }
  2533.     protected function configureListFields(ListMapper $list)
  2534.     {
  2535.     }
  2536.     protected function configureDatagridFilters(DatagridMapper $filter)
  2537.     {
  2538.     }
  2539.     protected function configureShowFields(ShowMapper $show)
  2540.     {
  2541.     }
  2542.     protected function configureRoutes(RouteCollection $collection)
  2543.     {
  2544.     }
  2545.     /**
  2546.      * Allows you to customize batch actions.
  2547.      *
  2548.      * @param array<string, mixed> $actions List of actions
  2549.      *
  2550.      * @return array<string, mixed>
  2551.      */
  2552.     protected function configureBatchActions($actions)
  2553.     {
  2554.         return $actions;
  2555.     }
  2556.     /**
  2557.      * NEXT_MAJOR: remove this method.
  2558.      *
  2559.      * @deprecated Use configureTabMenu instead
  2560.      *
  2561.      * @phpstan-param AdminInterface<object>|null $childAdmin
  2562.      */
  2563.     protected function configureSideMenu(ItemInterface $menustring $action, ?AdminInterface $childAdmin null)
  2564.     {
  2565.     }
  2566.     /**
  2567.      * Configures the tab menu in your admin.
  2568.      *
  2569.      * @param string $action
  2570.      *
  2571.      * @phpstan-param AdminInterface<object>|null $childAdmin
  2572.      */
  2573.     protected function configureTabMenu(ItemInterface $menu$action, ?AdminInterface $childAdmin null)
  2574.     {
  2575.         // Use configureSideMenu not to mess with previous overrides
  2576.         // NEXT_MAJOR: remove this line
  2577.         $this->configureSideMenu($menu$action$childAdmin);
  2578.     }
  2579.     /**
  2580.      * build the view FieldDescription array.
  2581.      */
  2582.     protected function buildShow()
  2583.     {
  2584.         if ($this->loaded['show']) {
  2585.             return;
  2586.         }
  2587.         $this->loaded['show'] = true;
  2588.         $this->show $this->getShowBuilder()->getBaseList();
  2589.         $mapper = new ShowMapper($this->getShowBuilder(), $this->show$this);
  2590.         $this->configureShowFields($mapper);
  2591.         foreach ($this->getExtensions() as $extension) {
  2592.             $extension->configureShowFields($mapper);
  2593.         }
  2594.     }
  2595.     /**
  2596.      * build the list FieldDescription array.
  2597.      */
  2598.     protected function buildList()
  2599.     {
  2600.         if ($this->loaded['list']) {
  2601.             return;
  2602.         }
  2603.         $this->loaded['list'] = true;
  2604.         $this->list $this->getListBuilder()->getBaseList();
  2605.         $mapper = new ListMapper($this->getListBuilder(), $this->list$this);
  2606.         if (\count($this->getBatchActions()) > && $this->hasRequest() && !$this->getRequest()->isXmlHttpRequest()) {
  2607.             $fieldDescription $this->getModelManager()->getNewFieldDescriptionInstance(
  2608.                 $this->getClass(),
  2609.                 'batch',
  2610.                 [
  2611.                     'label' => 'batch',
  2612.                     'code' => '_batch',
  2613.                     'sortable' => false,
  2614.                     'virtual_field' => true,
  2615.                 ]
  2616.             );
  2617.             $fieldDescription->setAdmin($this);
  2618.             // NEXT_MAJOR: Remove this line and use commented line below it instead
  2619.             $fieldDescription->setTemplate($this->getTemplate('batch'));
  2620.             // $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('batch'));
  2621.             $mapper->add($fieldDescriptionListMapper::TYPE_BATCH);
  2622.         }
  2623.         $this->configureListFields($mapper);
  2624.         foreach ($this->getExtensions() as $extension) {
  2625.             $extension->configureListFields($mapper);
  2626.         }
  2627.         if ($this->hasRequest() && $this->getRequest()->isXmlHttpRequest()) {
  2628.             $fieldDescription $this->getModelManager()->getNewFieldDescriptionInstance(
  2629.                 $this->getClass(),
  2630.                 'select',
  2631.                 [
  2632.                     'label' => false,
  2633.                     'code' => '_select',
  2634.                     'sortable' => false,
  2635.                     'virtual_field' => false,
  2636.                 ]
  2637.             );
  2638.             $fieldDescription->setAdmin($this);
  2639.             // NEXT_MAJOR: Remove this line and use commented line below it instead
  2640.             $fieldDescription->setTemplate($this->getTemplate('select'));
  2641.             // $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('select'));
  2642.             $mapper->add($fieldDescriptionListMapper::TYPE_SELECT);
  2643.         }
  2644.     }
  2645.     /**
  2646.      * Build the form FieldDescription collection.
  2647.      */
  2648.     protected function buildForm()
  2649.     {
  2650.         if ($this->loaded['form']) {
  2651.             return;
  2652.         }
  2653.         $this->loaded['form'] = true;
  2654.         $formBuilder $this->getFormBuilder();
  2655.         // NEXT_MAJOR: Remove this call.
  2656.         $formBuilder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
  2657.             $this->preValidate($event->getData(), 'sonata_deprecation_mute');
  2658.         }, 100);
  2659.         $this->form $formBuilder->getForm();
  2660.     }
  2661.     /**
  2662.      * Gets the subclass corresponding to the given name.
  2663.      *
  2664.      * @param string $name The name of the sub class
  2665.      *
  2666.      * @return string the subclass
  2667.      *
  2668.      * @phpstan-return class-string<T>
  2669.      */
  2670.     protected function getSubClass($name)
  2671.     {
  2672.         if ($this->hasSubClass($name)) {
  2673.             return $this->subClasses[$name];
  2674.         }
  2675.         // NEXT_MAJOR: Throw \LogicException instead.
  2676.         throw new \RuntimeException(sprintf('Unable to find the subclass `%s` for admin `%s`'$name, static::class));
  2677.     }
  2678.     /**
  2679.      * Attach the inline validator to the model metadata, this must be done once per admin.
  2680.      *
  2681.      * NEXT_MAJOR: Remove this method.
  2682.      *
  2683.      * @deprecated since sonata-project/admin-bundle 3.82.
  2684.      */
  2685.     protected function attachInlineValidator()
  2686.     {
  2687.         if ('sonata_deprecation_mute' !== \func_get_args()[0] ?? null) {
  2688.             @trigger_error(sprintf(
  2689.                 'The %s method is deprecated since version 3.82 and will be removed in 4.0.',
  2690.                 __METHOD__
  2691.             ), \E_USER_DEPRECATED);
  2692.         }
  2693.         $admin $this;
  2694.         // add the custom inline validation option
  2695.         $metadata $this->validator->getMetadataFor($this->getClass());
  2696.         if (!$metadata instanceof GenericMetadata) {
  2697.             throw new \UnexpectedValueException(
  2698.                 sprintf(
  2699.                     'Cannot add inline validator for %s because its metadata is an instance of %s instead of %s',
  2700.                     $this->getClass(),
  2701.                     \get_class($metadata),
  2702.                     GenericMetadata::class
  2703.                 )
  2704.             );
  2705.         }
  2706.         $metadata->addConstraint(new InlineConstraint([
  2707.             'service' => $this,
  2708.             'method' => static function (ErrorElement $errorElement$object) use ($admin) {
  2709.                 /* @var \Sonata\AdminBundle\Admin\AdminInterface $admin */
  2710.                 // This avoid the main validation to be cascaded to children
  2711.                 // The problem occurs when a model Page has a collection of Page as property
  2712.                 if ($admin->hasSubject() && spl_object_hash($object) !== spl_object_hash($admin->getSubject())) {
  2713.                     return;
  2714.                 }
  2715.                 $admin->validate($errorElement$object'sonata_deprecation_mute');
  2716.                 foreach ($admin->getExtensions() as $extension) {
  2717.                     /* @phpstan-ignore-next-line */
  2718.                     $extension->validate($admin$errorElement$object'sonata_deprecation_mute');
  2719.                 }
  2720.             },
  2721.             'serializingWarning' => true,
  2722.         ]));
  2723.     }
  2724.     /**
  2725.      * NEXT_MAJOR: Remove this function.
  2726.      *
  2727.      * @deprecated since sonata-project/admin-bundle 3.67, to be removed in 4.0.
  2728.      *
  2729.      * Predefine per page options.
  2730.      */
  2731.     protected function predefinePerPageOptions()
  2732.     {
  2733.         array_unshift($this->perPageOptions$this->maxPerPage);
  2734.         $this->perPageOptions array_unique($this->perPageOptions);
  2735.         sort($this->perPageOptions);
  2736.     }
  2737.     /**
  2738.      * Return list routes with permissions name.
  2739.      *
  2740.      * @return array<string, string|string[]>
  2741.      */
  2742.     protected function getAccess()
  2743.     {
  2744.         $access array_merge([
  2745.             'acl' => 'MASTER',
  2746.             'export' => 'EXPORT',
  2747.             'historyCompareRevisions' => 'EDIT',
  2748.             'historyViewRevision' => 'EDIT',
  2749.             'history' => 'EDIT',
  2750.             'edit' => 'EDIT',
  2751.             'show' => 'VIEW',
  2752.             'create' => 'CREATE',
  2753.             'delete' => 'DELETE',
  2754.             'batchDelete' => 'DELETE',
  2755.             'list' => 'LIST',
  2756.         ], $this->getAccessMapping());
  2757.         foreach ($this->getExtensions() as $extension) {
  2758.             // NEXT_MAJOR: remove method check
  2759.             if (method_exists($extension'getAccessMapping')) {
  2760.                 $access array_merge($access$extension->getAccessMapping($this));
  2761.             }
  2762.         }
  2763.         return $access;
  2764.     }
  2765.     /**
  2766.      * Configures a list of default filters.
  2767.      *
  2768.      * @param array<string, mixed> $filterValues
  2769.      */
  2770.     protected function configureDefaultFilterValues(array &$filterValues)
  2771.     {
  2772.     }
  2773.     /**
  2774.      * Configures a list of form options.
  2775.      *
  2776.      * @param array<string, mixed> $formOptions
  2777.      */
  2778.     protected function configureFormOptions(array &$formOptions)
  2779.     {
  2780.     }
  2781.     /**
  2782.      * Configures a list of default sort values.
  2783.      *
  2784.      * Example:
  2785.      *   $sortValues['_sort_by'] = 'foo'
  2786.      *   $sortValues['_sort_order'] = 'DESC'
  2787.      *
  2788.      * @phpstan-param array{_page?: int, _per_page?: int, _sort_by?: string, _sort_order?: string} $sortValues
  2789.      */
  2790.     protected function configureDefaultSortValues(array &$sortValues)
  2791.     {
  2792.     }
  2793.     /**
  2794.      * Set the parent object, if any, to the provided object.
  2795.      *
  2796.      * @phpstan-param T $object
  2797.      */
  2798.     final protected function appendParentObject(object $object): void
  2799.     {
  2800.         if ($this->isChild() && $this->getParentAssociationMapping()) {
  2801.             $parentAdmin $this->getParent();
  2802.             $parentObject $parentAdmin->getObject($this->getRequest()->get($parentAdmin->getIdParameter()));
  2803.             if (null !== $parentObject) {
  2804.                 $propertyAccessor PropertyAccess::createPropertyAccessor();
  2805.                 $propertyPath = new PropertyPath($this->getParentAssociationMapping());
  2806.                 $value $propertyAccessor->getValue($object$propertyPath);
  2807.                 if (\is_array($value) || $value instanceof \ArrayAccess) {
  2808.                     $value[] = $parentObject;
  2809.                     $propertyAccessor->setValue($object$propertyPath$value);
  2810.                 } else {
  2811.                     $propertyAccessor->setValue($object$propertyPath$parentObject);
  2812.                 }
  2813.             }
  2814.         } elseif ($this->hasParentFieldDescription()) {
  2815.             $parentAdmin $this->getParentFieldDescription()->getAdmin();
  2816.             $parentObject $parentAdmin->getObject($this->getRequest()->get($parentAdmin->getIdParameter()));
  2817.             if (null !== $parentObject) {
  2818.                 ObjectManipulator::setObject($object$parentObject$this->getParentFieldDescription());
  2819.             }
  2820.         }
  2821.     }
  2822.     /**
  2823.      * Build all the related urls to the current admin.
  2824.      */
  2825.     private function buildRoutes(): void
  2826.     {
  2827.         if ($this->loaded['routes']) {
  2828.             return;
  2829.         }
  2830.         $this->loaded['routes'] = true;
  2831.         $this->routes = new RouteCollection(
  2832.             $this->getBaseCodeRoute(),
  2833.             $this->getBaseRouteName(),
  2834.             $this->getBaseRoutePattern(),
  2835.             $this->getBaseControllerName()
  2836.         );
  2837.         $this->getRouteBuilder()->build($this$this->routes);
  2838.         $this->configureRoutes($this->routes);
  2839.         foreach ($this->getExtensions() as $extension) {
  2840.             $extension->configureRoutes($this$this->routes);
  2841.         }
  2842.     }
  2843. }
  2844. class_exists(\Sonata\Form\Validator\ErrorElement::class);