diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 89c2965e63..89918cced9 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -2,7 +2,7 @@ includes: - phpstan-baseline.neon parameters: - level: 6 + level: 7 paths: - src/ excludePaths: diff --git a/src/Config/Actions.php b/src/Config/Actions.php index 7348f32f9d..3cfadda986 100644 --- a/src/Config/Actions.php +++ b/src/Config/Actions.php @@ -81,7 +81,7 @@ public function remove(string $pageName, string $actionName): self public function reorder(string $pageName, array $orderedActionNames): self { $newActionOrder = []; - $currentActions = $this->dto->getActions(); + $currentActions = $this->dto->getActionList(); foreach ($orderedActionNames as $actionName) { if (!\array_key_exists($actionName, $currentActions[$pageName])) { throw new \InvalidArgumentException(sprintf('The "%s" action does not exist in the "%s" page, so you cannot set its order.', $actionName, $pageName)); diff --git a/src/Config/Crud.php b/src/Config/Crud.php index 42a3ede4c8..02a24874e6 100644 --- a/src/Config/Crud.php +++ b/src/Config/Crud.php @@ -260,8 +260,9 @@ public function setDecimalSeparator(string $separator): self */ public function setDefaultSort(array $sortFieldsAndOrder): self { - $sortFieldsAndOrder = array_map('strtoupper', $sortFieldsAndOrder); + $defaultSort = []; foreach ($sortFieldsAndOrder as $sortField => $sortOrder) { + $sortOrder = strtoupper($sortOrder); if (!\in_array($sortOrder, [SortOrder::ASC, SortOrder::DESC], true)) { throw new \InvalidArgumentException(sprintf('The sort order can be only "%s" or "%s", "%s" given.', SortOrder::ASC, SortOrder::DESC, $sortOrder)); } @@ -269,9 +270,11 @@ public function setDefaultSort(array $sortFieldsAndOrder): self if (!\is_string($sortField)) { throw new \InvalidArgumentException(sprintf('The keys of the array that defines the default sort must be strings with the field names, but the given "%s" value is a "%s".', $sortField, \gettype($sortField))); } + + $defaultSort[$sortField] = $sortOrder; } - $this->dto->setDefaultSort($sortFieldsAndOrder); + $this->dto->setDefaultSort($defaultSort); return $this; } diff --git a/src/Dto/ActionConfigDto.php b/src/Dto/ActionConfigDto.php index bed0b2b0ca..35add9fa53 100644 --- a/src/Dto/ActionConfigDto.php +++ b/src/Dto/ActionConfigDto.php @@ -39,6 +39,9 @@ public function __clone() } } + /** + * @deprecated since 4.25.0 and it will be removed in EasyAdmin 5.0.0. + */ public function setPageName(?string $pageName): void { $this->pageName = $pageName; @@ -109,16 +112,55 @@ public function disableActions(array $actionNames): void /** * @return ActionCollection|array> + * + * @deprecated since 4.25.0 and it will be removed in EasyAdmin 5.0.0. Use `getPageActions` or `getActionList` instead. */ public function getActions(): ActionCollection|array { + trigger_deprecation( + 'easycorp/easyadmin-bundle', + '4.25.0', + 'Calling "%s" is deprecated and will be removed in 5.0.0. Use `getPageActions` or `getActionList` instead.', + __METHOD__, + ); + return null === $this->pageName ? $this->actions : ActionCollection::new($this->actions[$this->pageName]); } + public function getPageActions(string $pageName): ActionCollection + { + return ActionCollection::new($this->actions[$pageName]); + } + + /** + * @return array> + */ + public function getActionList(): array + { + return $this->actions; + } + /** * @param array $newActions + * + * @deprecated since 4.25.0 and it will be removed in EasyAdmin 5.0.0. Use `setPageActions` instead. */ public function setActions(string $pageName, array $newActions): void + { + trigger_deprecation( + 'easycorp/easyadmin-bundle', + '4.25.0', + 'Calling "%s" is deprecated and will be removed in 5.0.0. Use `setPageActions` instead.', + __METHOD__, + ); + + $this->actions[$pageName] = $newActions; + } + + /** + * @param array $newActions + */ + public function setPageActions(string $pageName, array $newActions): void { $this->actions[$pageName] = $newActions; } diff --git a/src/Dto/FieldLayoutDto.php b/src/Dto/FieldLayoutDto.php index 3c42b6d750..b981d39ab1 100644 --- a/src/Dto/FieldLayoutDto.php +++ b/src/Dto/FieldLayoutDto.php @@ -59,6 +59,7 @@ public function getFields(): array */ public function getFieldsInTab(string $tabUniqueId): array { + /** @phpstan-ignore-next-line return.type */ return $this->fields[$tabUniqueId] ?? []; } } diff --git a/src/EventListener/AdminRouterSubscriber.php b/src/EventListener/AdminRouterSubscriber.php index 0e9503836f..c75b701203 100644 --- a/src/EventListener/AdminRouterSubscriber.php +++ b/src/EventListener/AdminRouterSubscriber.php @@ -163,7 +163,9 @@ public function onKernelRequest(RequestEvent $event): void // if this is a ugly URL from legacy EasyAdmin versions and the application // uses pretty URLs, redirect to the equivalent pretty URL - if ($this->adminRouteGenerator->usesPrettyUrls() && null !== $entityFqcnOrCrudControllerFqcn = $request->query->get(EA::CRUD_CONTROLLER_FQCN)) { + if ($this->adminRouteGenerator->usesPrettyUrls()) { + /** @var class-string|null $entityFqcnOrCrudControllerFqcn */ + $entityFqcnOrCrudControllerFqcn = $request->query->get(EA::CRUD_CONTROLLER_FQCN); if (is_subclass_of($entityFqcnOrCrudControllerFqcn, CrudControllerInterface::class)) { $crudControllerFqcn = $entityFqcnOrCrudControllerFqcn; } else { @@ -209,7 +211,9 @@ public function onKernelController(ControllerEvent $event): void // if the request is related to a CRUD controller, change the controller to be executed if (null !== $crudControllerInstance = $this->getCrudControllerInstance($request)) { + /** @var callable $symfonyControllerFqcnCallable */ $symfonyControllerFqcnCallable = [$crudControllerInstance, $request->attributes->get(EA::CRUD_ACTION) ?? $request->query->get(EA::CRUD_ACTION)]; + /** @var callable $symfonyControllerStringCallable */ $symfonyControllerStringCallable = [$crudControllerInstance::class, $request->attributes->get(EA::CRUD_ACTION) ?? $request->query->get(EA::CRUD_ACTION)]; // this makes Symfony believe that another controller is being executed diff --git a/src/Factory/ActionFactory.php b/src/Factory/ActionFactory.php index bdf8ebcc9f..c71043f8b1 100644 --- a/src/Factory/ActionFactory.php +++ b/src/Factory/ActionFactory.php @@ -36,7 +36,7 @@ public function processEntityActions(EntityDto $entityDto, ActionConfigDto $acti { $currentPage = $this->adminContextProvider->getContext()->getCrud()->getCurrentPage(); $entityActions = []; - foreach ($actionsDto->getActions()->all() as $actionDto) { + foreach ($actionsDto->getPageActions($currentPage)->all() as $actionDto) { if (!$actionDto->isEntityAction()) { continue; } @@ -79,7 +79,7 @@ public function processGlobalActions(?ActionConfigDto $actionsDto = null): Actio $currentPage = $this->adminContextProvider->getContext()->getCrud()->getCurrentPage(); $globalActions = []; - foreach ($actionsDto->getActions()->all() as $actionDto) { + foreach ($actionsDto->getPageActions($currentPage)->all() as $actionDto) { if (!$actionDto->isGlobalAction() && !$actionDto->isBatchAction()) { continue; } @@ -171,7 +171,7 @@ private function processActionLabel(ActionDto $actionDto, ?EntityDto $entityDto, return; } - if (\is_callable($label) && $label instanceof \Closure) { + if (!\is_string($label) && !$label instanceof TranslatableInterface && \is_callable($label)) { $label = \call_user_func_array($label, array_filter([$entityDto?->getInstance()], static fn ($item): bool => null !== $item)); if (!\is_string($label) && !$label instanceof TranslatableInterface) { diff --git a/src/Factory/AdminContextFactory.php b/src/Factory/AdminContextFactory.php index f78d9fe0cd..bd91283f9f 100644 --- a/src/Factory/AdminContextFactory.php +++ b/src/Factory/AdminContextFactory.php @@ -132,7 +132,7 @@ private function getCrudDto(CrudControllerRegistry $crudControllers, DashboardCo return $crudDto; } - private function getActionConfig(DashboardControllerInterface $dashboardController, ?CrudControllerInterface $crudController, ?string $pageName): ActionConfigDto + private function getActionConfig(DashboardControllerInterface $dashboardController, ?CrudControllerInterface $crudController, ?string $pageName = null): ActionConfigDto { if (null === $crudController) { return new ActionConfigDto(); diff --git a/src/Factory/FormLayoutFactory.php b/src/Factory/FormLayoutFactory.php index a9a14cf1bf..9b685810fd 100644 --- a/src/Factory/FormLayoutFactory.php +++ b/src/Factory/FormLayoutFactory.php @@ -16,6 +16,7 @@ use EasyCorp\Bundle\EasyAdminBundle\Form\Type\Layout\EaFormTabPaneCloseType; use EasyCorp\Bundle\EasyAdminBundle\Form\Type\Layout\EaFormTabPaneGroupCloseType; use EasyCorp\Bundle\EasyAdminBundle\Form\Type\Layout\EaFormTabPaneGroupOpenType; +use Stringable; use Symfony\Component\String\Slugger\AsciiSlugger; use Symfony\Component\Uid\Ulid; @@ -78,7 +79,10 @@ private function validateLayoutConfiguration(FieldCollection $fields): void } if ($theFirstFieldWhichIsATabOrColumn->isFormColumn() && $fieldDto->isFormTab()) { - throw new \InvalidArgumentException(sprintf('When using form columns, you can\'t define tabs inside columns (but you can define columns inside tabs). Move the tab "%s" outside any column.', $fieldDto->getLabel())); + $label = $fieldDto->getLabel(); + $labelAsString = (\is_string($label) || $label instanceof \Stringable) ? (string) $label : ''; + + throw new \InvalidArgumentException(sprintf('When using form columns, you can\'t define tabs inside columns (but you can define columns inside tabs). Move the tab "%s" outside any column.', $labelAsString)); } } } @@ -181,7 +185,9 @@ private function linearizeLayoutConfiguration(FieldCollection $fields): void if ($fieldDto->isFormTab()) { $isTabActive = 0 === \count($tabs); - $tabId = sprintf('tab-%s', $fieldDto->getLabel() ? $slugger->slug(strip_tags($fieldDto->getLabel()))->lower()->toString() : ++$tabsWithoutLabelCounter); + $label = $fieldDto->getLabel(); + $labelAsString = (\is_string($label) || $label instanceof Stringable) ? (string) $label : ''; + $tabId = sprintf('tab-%s', '' !== $labelAsString ? $slugger->slug(strip_tags($labelAsString))->lower()->toString() : ++$tabsWithoutLabelCounter); $fieldDto->setCustomOption(FormField::OPTION_TAB_ID, $tabId); $fieldDto->setCustomOption(FormField::OPTION_TAB_IS_ACTIVE, $isTabActive); @@ -395,6 +401,7 @@ public static function createFromFieldDtos(?FieldCollection $fieldDtos): FieldLa $tabs[$fieldDto->getUniqueId()] = $fieldDto; } else { if ($hasTabs) { + /** @phpstan-ignore-next-line offsetAccess.nonOffsetAccessible */ $fields[$currentTab->getUniqueId()][] = $fieldDto; } else { $fields[] = $fieldDto; diff --git a/src/Factory/MenuFactory.php b/src/Factory/MenuFactory.php index 43c02214de..c74191812d 100644 --- a/src/Factory/MenuFactory.php +++ b/src/Factory/MenuFactory.php @@ -15,6 +15,7 @@ use EasyCorp\Bundle\EasyAdminBundle\Dto\UserMenuDto; use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGeneratorInterface; use EasyCorp\Bundle\EasyAdminBundle\Security\Permission; +use Stringable; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator; use function Symfony\Component\Translation\t; @@ -134,7 +135,10 @@ private function generateMenuItemUrl(MenuItemDto $menuItemDto): string $entityFqcn = $routeParameters[EA::ENTITY_FQCN] ?? null; $crudControllerFqcn = $routeParameters[EA::CRUD_CONTROLLER_FQCN] ?? null; if (null === $entityFqcn && null === $crudControllerFqcn) { - throw new \RuntimeException(sprintf('The CRUD menu item with label "%s" must define either the entity FQCN (using the third constructor argument) or the CRUD Controller FQCN (using the "setController()" method).', $menuItemDto->getLabel())); + $label = $menuItemDto->getLabel(); + $labelAsString = (\is_string($label) || $label instanceof \Stringable) ? (string) $label : ''; + + throw new \RuntimeException(sprintf('The CRUD menu item with label "%s" must define either the entity FQCN (using the third constructor argument) or the CRUD Controller FQCN (using the "setController()" method).', $labelAsString)); } // 1. if CRUD controller is defined, use it... diff --git a/src/Form/Type/CrudFormType.php b/src/Form/Type/CrudFormType.php index fd1384964d..ac9a667716 100644 --- a/src/Form/Type/CrudFormType.php +++ b/src/Form/Type/CrudFormType.php @@ -13,6 +13,7 @@ use EasyCorp\Bundle\EasyAdminBundle\Form\Type\Layout\EaFormRowType; use EasyCorp\Bundle\EasyAdminBundle\Form\Type\Layout\EaFormTabPaneCloseType; use EasyCorp\Bundle\EasyAdminBundle\Form\Type\Layout\EaFormTabPaneOpenType; +use Stringable; use Symfony\Bridge\Doctrine\Form\DoctrineOrmTypeGuesser; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; @@ -94,7 +95,9 @@ public function buildForm(FormBuilderInterface $builder, array $options): void $metadata['label'] = $fieldDto->getLabel(); $metadata['help'] = $fieldDto->getHelp(); $metadata[FormField::OPTION_ICON] = $fieldDto->getCustomOption(FormField::OPTION_ICON); - $currentFormTab = (string) $fieldDto->getLabel(); + + $label = $fieldDto->getLabel(); + $currentFormTab = (\is_string($label) || $label instanceof \Stringable) ? (string) $label : ''; // plain arrays are not enough for tabs because they are modified in the // lifecycle of a form (e.g. add info about form errors). Use an ArrayObject instead. diff --git a/src/Form/Type/FileUploadType.php b/src/Form/Type/FileUploadType.php index c9f28dbed3..612a67726f 100644 --- a/src/Form/Type/FileUploadType.php +++ b/src/Form/Type/FileUploadType.php @@ -96,7 +96,10 @@ public function configureOptions(OptionsResolver $resolver): void $index = 1; $pathInfo = pathinfo($filename); - while (file_exists($filename = sprintf('%s/%s_%d.%s', $pathInfo['dirname'], $pathInfo['filename'], $index, $pathInfo['extension']))) { + + $basePath = isset($pathInfo['dirname']) ? sprintf('%s/%s', $pathInfo['dirname'], $pathInfo['filename']) : $pathInfo['filename']; + $endPath = isset($pathInfo['extension']) ? '.'.$pathInfo['extension'] : ''; + while (file_exists($filename = sprintf('%s_%d%s', $basePath, $index, $endPath))) { ++$index; } diff --git a/src/Intl/IntlFormatter.php b/src/Intl/IntlFormatter.php index af727041f2..77871aff3a 100644 --- a/src/Intl/IntlFormatter.php +++ b/src/Intl/IntlFormatter.php @@ -141,7 +141,7 @@ public function formatNumber($number, array $attrs = [], string $style = 'decima $formatter = $this->createNumberFormatter($locale, $style, $attrs); $ret = $formatter->format($number, self::NUMBER_TYPES[$type]); - if (!\is_string($formatter->format($number, self::NUMBER_TYPES[$type]))) { + if (!\is_string($ret)) { throw new RuntimeError('Unable to format the given number.'); } @@ -261,14 +261,16 @@ private function createNumberFormatter(?string $locale, string $style, array $at $value = self::NUMBER_PADDING_ATTRIBUTES[$value]; } - + /** @var int|float $value */ $this->numberFormatters[$hash]->setAttribute(self::NUMBER_ATTRIBUTES[$name], $value); } + /** @var string $value */ foreach ($textAttrs as $name => $value) { $this->numberFormatters[$hash]->setTextAttribute(self::NUMBER_TEXT_ATTRIBUTES[$name], $value); } + /** @var string $value */ foreach ($symbols as $name => $value) { $this->numberFormatters[$hash]->setSymbol(self::NUMBER_SYMBOLS[$name], $value); } diff --git a/src/Maker/ClassMaker.php b/src/Maker/ClassMaker.php index a94088cb44..26e829e3ff 100644 --- a/src/Maker/ClassMaker.php +++ b/src/Maker/ClassMaker.php @@ -62,6 +62,6 @@ private function renderSkeleton(string $filePath, array $parameters): string extract($parameters, \EXTR_SKIP); include $filePath; - return ob_get_clean(); + return (string) ob_get_clean(); } } diff --git a/src/Menu/MenuItemMatcher.php b/src/Menu/MenuItemMatcher.php index 2bc6590752..1a3e912adf 100644 --- a/src/Menu/MenuItemMatcher.php +++ b/src/Menu/MenuItemMatcher.php @@ -97,6 +97,7 @@ private function doMarkSelectedLegacyMenuItem(array $menuItems, Request $request $menuItemQueryString = null === $menuItemDto->getLinkUrl() ? null : parse_url($menuItemDto->getLinkUrl(), \PHP_URL_QUERY); + /** @var array $menuItemQueryParameters */ $menuItemQueryParameters = []; if (\is_string($menuItemQueryString)) { parse_str($menuItemQueryString, $menuItemQueryParameters); @@ -114,7 +115,9 @@ private function doMarkSelectedLegacyMenuItem(array $menuItems, Request $request // match that menu item for all actions (EDIT, NEW, etc.) of the same controller; // this is not strictly correct, but backend users expect this behavior because it // makes the sidebar menu more predictable and easier to use + /** @var class-string|null $menuItemController */ $menuItemController = $menuItemQueryParameters[EA::CRUD_CONTROLLER_FQCN] ?? null; + /** @var class-string|null $currentPageController */ $currentPageController = $currentPageQueryParameters[EA::CRUD_CONTROLLER_FQCN] ?? null; $actionsLinkedInTheMenuForThisEntity = $controllersAndActionsLinkedInTheMenu[$currentPageController] ?? []; $menuOnlyLinksToIndexActionOfThisEntity = $actionsLinkedInTheMenuForThisEntity === [Crud::PAGE_INDEX]; @@ -190,18 +193,20 @@ private function doMarkSelectedPrettyUrlsMenuItem(array $menuItems, Request $req // remove host part from the menu item link URL $urlParts = parse_url($menuItemDto->getLinkUrl()); - $menuItemUrlWithoutHost = $urlParts['path'] ?? ''; - if (\array_key_exists('query', $urlParts)) { - $menuItemUrlWithoutHost .= '?'.$urlParts['query']; - } - if (\array_key_exists('fragment', $urlParts)) { - $menuItemUrlWithoutHost .= '#'.$urlParts['fragment']; - } + if (false !== $urlParts) { + $menuItemUrlWithoutHost = $urlParts['path'] ?? ''; + if (\array_key_exists('query', $urlParts)) { + $menuItemUrlWithoutHost .= '?'.$urlParts['query']; + } + if (\array_key_exists('fragment', $urlParts)) { + $menuItemUrlWithoutHost .= '#'.$urlParts['fragment']; + } - if ($menuItemUrlWithoutHost === $currentUrlWithoutHostAndWithNormalizedQueryString) { - $menuItemDto->setSelected(true); + if ($menuItemUrlWithoutHost === $currentUrlWithoutHostAndWithNormalizedQueryString) { + $menuItemDto->setSelected(true); - return $menuItems; + return $menuItems; + } } } diff --git a/src/Orm/EntityRepository.php b/src/Orm/EntityRepository.php index a647b8774a..2219379f82 100644 --- a/src/Orm/EntityRepository.php +++ b/src/Orm/EntityRepository.php @@ -230,6 +230,7 @@ private function addFilterClause(QueryBuilder $queryBuilder, SearchDto $searchDt 'value' => $submittedData, ]; } + /** @var array{comparison: string, value: mixed, value2?: mixed} $submittedData */ /** @var string $rootAlias */ $rootAlias = current($queryBuilder->getRootAliases()); @@ -328,7 +329,6 @@ private function getSearchablePropertiesConfig(QueryBuilder $queryBuilder, Searc : $entityDto->getFqcn() ; - /** @var \ReflectionNamedType|\ReflectionUnionType|null $idClassType */ $idClassType = null; $reflectionClass = new \ReflectionClass($entityFqcn); @@ -342,8 +342,7 @@ private function getSearchablePropertiesConfig(QueryBuilder $queryBuilder, Searc $reflectionClass = $reflectionClass->getParentClass(); } - if (null !== $idClassType) { - /** @var \ReflectionNamedType|\ReflectionUnionType $idClassType */ + if ($idClassType instanceof \ReflectionNamedType) { $idClassName = $idClassType->getName(); if (class_exists($idClassName)) { diff --git a/src/Orm/EntityUpdater.php b/src/Orm/EntityUpdater.php index 6c331a12bd..0f1b152e80 100644 --- a/src/Orm/EntityUpdater.php +++ b/src/Orm/EntityUpdater.php @@ -25,11 +25,15 @@ public function __construct(PropertyAccessorInterface $propertyAccessor, Validat public function updateProperty(EntityDto $entityDto, string $propertyName, mixed $value): void { - if (!$this->propertyAccessor->isWritable($entityDto->getInstance(), $propertyName)) { + $entityInstance = $entityDto->getInstance(); + if (null === $entityInstance) { + return; + } + + if (!$this->propertyAccessor->isWritable($entityInstance, $propertyName)) { throw new \RuntimeException(sprintf('The "%s" property of the "%s" entity is not writable.', $propertyName, $entityDto->getName())); } - $entityInstance = $entityDto->getInstance(); $this->propertyAccessor->setValue($entityInstance, $propertyName, $value); /** @var ConstraintViolationList $violations */ diff --git a/src/Router/UrlSigner.php b/src/Router/UrlSigner.php index 68d32e9100..d8ac5a90d1 100644 --- a/src/Router/UrlSigner.php +++ b/src/Router/UrlSigner.php @@ -33,10 +33,14 @@ public function __construct(string $kernelSecret) public function sign(string $url): string { $urlParts = parse_url($url); + if (false === $urlParts) { + return $url; + } + + /** @var array $queryParams */ + $queryParams = []; if (isset($urlParts['query'])) { parse_str($urlParts['query'], $queryParams); - } else { - $queryParams = []; } $queryParams[EA::URL_SIGNATURE] = $this->computeHash($this->getQueryParamsToSign($queryParams)); @@ -50,10 +54,10 @@ public function sign(string $url): string public function check(string $url): bool { $urlParts = parse_url($url); + /** @var array $queryParams */ + $queryParams = []; if (isset($urlParts['query'])) { parse_str($urlParts['query'], $queryParams); - } else { - $queryParams = []; } // this differs from Symfony's UriSigner behavior: if the URL doesn't contain any @@ -63,11 +67,11 @@ public function check(string $url): bool } $urlSignature = $queryParams[EA::URL_SIGNATURE] ?? null; - if (null === $urlSignature || '' === $urlSignature) { + if (!\is_string($urlSignature) || '' === $urlSignature) { return false; } - $expectedHash = $queryParams[EA::URL_SIGNATURE]; + $expectedHash = $urlSignature; $calculatedHash = $this->computeHash($this->getQueryParamsToSign($queryParams)); return hash_equals($calculatedHash, $expectedHash); diff --git a/tests/Controller/ActionsCrudControllerTest.php b/tests/Controller/ActionsCrudControllerTest.php index 7373db8880..775b8cbdd7 100644 --- a/tests/Controller/ActionsCrudControllerTest.php +++ b/tests/Controller/ActionsCrudControllerTest.php @@ -51,6 +51,7 @@ public function testDynamicLabels() static::assertSame('Action 6: Category 0', $crawler->filter('a.dropdown-item[data-action-name="action6"]')->text()); static::assertSame('Action 7: Category 0', $crawler->filter('a.dropdown-item[data-action-name="action7"]')->text()); static::assertSame('Reset', $crawler->filter('a.dropdown-item[data-action-name="action8"]')->text()); + static::assertSame('Action 10: Category 0', $crawler->filter('a.dropdown-item[data-action-name="action10"]')->text()); } public function testFormAction() diff --git a/tests/TestApplication/src/Controller/ActionsCrudController.php b/tests/TestApplication/src/Controller/ActionsCrudController.php index a359fde681..2a2395a405 100644 --- a/tests/TestApplication/src/Controller/ActionsCrudController.php +++ b/tests/TestApplication/src/Controller/ActionsCrudController.php @@ -47,6 +47,8 @@ public function configureActions(Actions $actions): Actions $action9 = Action::new('action9')->linkToCrudAction('')->createAsGlobalAction()->displayAsForm(); + $action10 = Action::new('action10')->linkToCrudAction('')->setLabel([new LabelGenerator2(), 'generateLabel']); + return $actions ->add(Crud::PAGE_INDEX, $action1) ->add(Crud::PAGE_INDEX, $action2) @@ -57,6 +59,7 @@ public function configureActions(Actions $actions): Actions ->add(Crud::PAGE_INDEX, $action7) ->add(Crud::PAGE_INDEX, $action8) ->add(Crud::PAGE_INDEX, $action9) + ->add(Crud::PAGE_INDEX, $action10) ->update(Crud::PAGE_INDEX, Action::NEW, function (Action $action) { return $action->setIcon('fa fa-fw fa-plus')->setLabel(false); }) @@ -71,3 +74,10 @@ public function generateLabel(Category $category): string return 'Action 6: '.$category->getName(); } } +final class LabelGenerator2 +{ + public function generateLabel(Category $category): string + { + return 'Action 10: '.$category->getName(); + } +}