diff --git a/CHANGELOG.md b/CHANGELOG.md index 8325f4dac0..b06b9acd8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [4.34.0](https://github.com/sonata-project/SonataAdminBundle/compare/4.33.0...4.34.0) - 2024-12-07 +### Changed +- [[#8225](https://github.com/sonata-project/SonataAdminBundle/pull/8225)] Drop Symfony < 6.4 ([@VincentLanglet](https://github.com/VincentLanglet)) +- [[#8225](https://github.com/sonata-project/SonataAdminBundle/pull/8225)] Drop PHP 8 ([@VincentLanglet](https://github.com/VincentLanglet)) + ## [4.33.0](https://github.com/sonata-project/SonataAdminBundle/compare/4.32.0...4.33.0) - 2024-12-04 ### Added - [[#8219](https://github.com/sonata-project/SonataAdminBundle/pull/8219)] Support editable mode on `FieldDescriptionInterface::TYPE_ENUM` ([@onEXHovia](https://github.com/onEXHovia)) diff --git a/composer.json b/composer.json index ba0943f39f..9fec2da779 100644 --- a/composer.json +++ b/composer.json @@ -24,11 +24,11 @@ ], "homepage": "https://docs.sonata-project.org/projects/SonataAdminBundle", "require": { - "php": "^8.0", + "php": "^8.1", "ext-json": "*", "doctrine/collections": "^1.6 || ^2.0", "doctrine/common": "^3.0", - "knplabs/knp-menu": "^3.1", + "knplabs/knp-menu": "^3.2", "knplabs/knp-menu-bundle": "^3.0", "psr/container": "^1.0 || ^2.0", "psr/log": "^2.0 || ^3.0", @@ -37,32 +37,32 @@ "sonata-project/exporter": "^2.14 || ^3.1.1", "sonata-project/form-extensions": "^1.15 || ^2.0", "sonata-project/twig-extensions": "^1.4.1 || ^2.0", - "symfony/asset": "^5.4 || ^6.2 || ^7.0", - "symfony/config": "^5.4 || ^6.2 || ^7.0", - "symfony/console": "^5.4 || ^6.2 || ^7.0", - "symfony/dependency-injection": "^5.4 || ^6.2 || ^7.0", - "symfony/doctrine-bridge": "^5.4 || ^6.2 || ^7.0", - "symfony/event-dispatcher": "^5.4 || ^6.2 || ^7.0", + "symfony/asset": "^6.4 || ^7.1", + "symfony/config": "^6.4 || ^7.1", + "symfony/console": "^6.4 || ^7.1", + "symfony/dependency-injection": "^6.4 || ^7.1", + "symfony/doctrine-bridge": "^6.4 || ^7.1", + "symfony/event-dispatcher": "^6.4 || ^7.1", "symfony/event-dispatcher-contracts": "^2.0 || ^3.0", - "symfony/expression-language": "^5.4 || ^6.2 || ^7.0", - "symfony/form": "^5.4 || ^6.2 || ^7.0", - "symfony/framework-bundle": "^5.4 || ^6.2 || ^7.0", - "symfony/http-foundation": "^5.4 || ^6.2 || ^7.0", - "symfony/http-kernel": "^5.4 || ^6.2 || ^7.0", - "symfony/options-resolver": "^5.4 || ^6.2 || ^7.0", - "symfony/property-access": "^5.4 || ^6.2 || ^7.0", - "symfony/routing": "^5.4 || ^6.2 || ^7.0", + "symfony/expression-language": "^6.4 || ^7.1", + "symfony/form": "^6.4 || ^7.1", + "symfony/framework-bundle": "^6.4 || ^7.1", + "symfony/http-foundation": "^6.4 || ^7.1", + "symfony/http-kernel": "^6.4 || ^7.1", + "symfony/options-resolver": "^6.4 || ^7.1", + "symfony/property-access": "^6.4 || ^7.1", + "symfony/routing": "^6.4 || ^7.1", "symfony/security-acl": "^3.1", - "symfony/security-bundle": "^5.4 || ^6.2 || ^7.0", - "symfony/security-core": "^5.4 || ^6.2 || ^7.0", - "symfony/security-csrf": "^5.4 || ^6.2 || ^7.0", - "symfony/serializer": "^5.4 || ^6.2 || ^7.0", - "symfony/string": "^5.4 || ^6.2 || ^7.0", - "symfony/translation": "^5.4 || ^6.2 || ^7.0", + "symfony/security-bundle": "^6.4 || ^7.1", + "symfony/security-core": "^6.4 || ^7.1", + "symfony/security-csrf": "^6.4 || ^7.1", + "symfony/serializer": "^6.4 || ^7.1", + "symfony/string": "^6.4 || ^7.1", + "symfony/translation": "^6.4 || ^7.1", "symfony/translation-contracts": "^2.3 || ^3.0", - "symfony/twig-bridge": "^5.4 || ^6.2 || ^7.0", - "symfony/twig-bundle": "^5.4 || ^6.2 || ^7.0", - "symfony/validator": "^5.4 || ^6.2 || ^7.0", + "symfony/twig-bridge": "^6.4 || ^7.1", + "symfony/twig-bundle": "^6.4 || ^7.1", + "symfony/validator": "^6.4 || ^7.1", "twig/string-extra": "^3.0", "twig/twig": "^3.0" }, @@ -76,17 +76,17 @@ "phpstan/phpstan-phpunit": "^1.0", "phpstan/phpstan-strict-rules": "^1.0", "phpstan/phpstan-symfony": "^1.0", - "phpunit/phpunit": "^9.5", + "phpunit/phpunit": "^9.6", "psalm/plugin-phpunit": "^0.18", "psalm/plugin-symfony": "^5.0", "psr/event-dispatcher": "^1.0", "rector/rector": "^1.1", - "symfony/browser-kit": "^5.4 || ^6.2 || ^7.0", - "symfony/css-selector": "^5.4 || ^6.2 || ^7.0", - "symfony/filesystem": "^5.4 || ^6.2 || ^7.0", + "symfony/browser-kit": "^6.4 || ^7.1", + "symfony/css-selector": "^6.4 || ^7.1", + "symfony/filesystem": "^6.4 || ^7.1", "symfony/maker-bundle": "^1.25", - "symfony/phpunit-bridge": "^6.2 || ^7.0", - "symfony/yaml": "^5.4 || ^6.2 || ^7.0", + "symfony/phpunit-bridge": "^6.4 || ^7.1", + "symfony/yaml": "^6.4 || ^7.1", "vimeo/psalm": "^5.8.0" }, "suggest": { diff --git a/rector.php b/rector.php index d57c8f4916..254341a3b4 100644 --- a/rector.php +++ b/rector.php @@ -19,6 +19,8 @@ use Rector\Config\RectorConfig; use Rector\Php70\Rector\FunctionLike\ExceptionHandlerTypehintRector; +use Rector\Php81\Rector\FuncCall\NullToStrictStringFuncCallArgRector; +use Rector\Php81\Rector\Property\ReadOnlyPropertyRector; use Rector\PHPUnit\CodeQuality\Rector\Class_\NarrowUnusedSetUpDefinedPropertyRector; use Rector\PHPUnit\CodeQuality\Rector\Class_\PreferPHPUnitThisCallRector; use Rector\PHPUnit\Set\PHPUnitSetList; @@ -40,6 +42,8 @@ $rectorConfig->importShortClasses(false); $rectorConfig->skip([ ExceptionHandlerTypehintRector::class, + ReadOnlyPropertyRector::class, + NullToStrictStringFuncCallArgRector::class, PreferPHPUnitThisCallRector::class, NarrowUnusedSetUpDefinedPropertyRector::class, ]); diff --git a/src/Command/GenerateObjectAclCommand.php b/src/Command/GenerateObjectAclCommand.php index 4ab81ce901..97512ea7ef 100644 --- a/src/Command/GenerateObjectAclCommand.php +++ b/src/Command/GenerateObjectAclCommand.php @@ -94,7 +94,7 @@ public function execute(InputInterface $input, OutputInterface $output): int $securityIdentity = null; if ($input->getOption('step') && $this->askConfirmation($input, $output, "Set an object owner?\n", 'no')) { - $username = $this->askAndValidate($input, $output, 'Please enter the username: ', '', [Validators::class, 'validateUsername']); + $username = $this->askAndValidate($input, $output, 'Please enter the username: ', '', Validators::validateUsername(...)); $securityIdentity = new UserSecurityIdentity($username, $this->getUserModelClass($input, $output)); } diff --git a/src/EventListener/ConfigureCRUDControllerListener.php b/src/EventListener/ConfigureCRUDControllerListener.php index cc3179abfc..651cf80245 100644 --- a/src/EventListener/ConfigureCRUDControllerListener.php +++ b/src/EventListener/ConfigureCRUDControllerListener.php @@ -29,6 +29,13 @@ public function onKernelController(ControllerEvent $event): void if (\is_array($controller)) { $controller = $controller[0]; + } else { + try { + $reflection = new \ReflectionFunction($controller(...)); + $controller = $reflection->getClosureThis(); + } catch (\ReflectionException) { + return; + } } if (!$controller instanceof CRUDController) { diff --git a/src/Maker/AdminMaker.php b/src/Maker/AdminMaker.php index 8447b9cfa0..401a8d0c54 100644 --- a/src/Maker/AdminMaker.php +++ b/src/Maker/AdminMaker.php @@ -122,14 +122,14 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma $this->modelClass = $io->ask( 'The fully qualified model class', $input->getArgument('model'), - [Validators::class, 'validateClass'] + Validators::validateClass(...) ); - $this->modelClassBasename = \array_slice(explode('\\', $this->modelClass), -1)[0]; + $this->modelClassBasename = \array_slice(explode('\\', (string) $this->modelClass), -1)[0]; $this->adminClassBasename = $io->ask( 'The admin class basename', $input->getOption('admin') ?? \sprintf('%sAdmin', $this->modelClassBasename), - [Validators::class, 'validateAdminClassBasename'] + Validators::validateAdminClassBasename(...) ); if (\count($this->availableModelManagers) > 1) { $managerTypes = array_keys($this->availableModelManagers); @@ -141,7 +141,7 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma $this->controllerClassBasename = $io->ask( 'The controller class basename', $input->getOption('controller') ?? \sprintf('%sAdminController', $this->modelClassBasename), - [Validators::class, 'validateControllerClassBasename'] + Validators::validateControllerClassBasename(...) ); $input->setOption('controller', $this->controllerClassBasename); } @@ -150,12 +150,12 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma $servicesFile = $io->ask( 'The services YAML configuration file', $input->getOption('services') ?? (is_file($path.'admin.yaml') ? 'admin.yaml' : 'services.yaml'), - [Validators::class, 'validateServicesFile'] + Validators::validateServicesFile(...) ); $id = $io->ask( 'The admin service ID', $this->getAdminServiceId($this->adminClassBasename), - [Validators::class, 'validateServiceId'] + Validators::validateServiceId(...) ); $input->setOption('services', $servicesFile); $input->setOption('id', $id); diff --git a/src/Util/AdminObjectAclData.php b/src/Util/AdminObjectAclData.php index 1417308e29..bcfd0304e4 100644 --- a/src/Util/AdminObjectAclData.php +++ b/src/Util/AdminObjectAclData.php @@ -31,11 +31,6 @@ final class AdminObjectAclData */ private static array $ownerPermissions = ['MASTER', 'OWNER']; - /** - * @var \Traversable Roles to set ACL for - */ - private \Traversable $aclRoles; - /** * @var array Cache of masks */ @@ -50,7 +45,7 @@ final class AdminObjectAclData /** * @param AdminInterface $admin * @param \Traversable $aclUsers - * @param \Traversable|null $aclRoles + * @param \Traversable $aclRoles * * @phpstan-param class-string $maskBuilderClass */ @@ -59,9 +54,8 @@ public function __construct( private object $object, private \Traversable $aclUsers, private string $maskBuilderClass, - ?\Traversable $aclRoles = null, + private \Traversable $aclRoles = new \ArrayIterator(), ) { - $this->aclRoles = $aclRoles ?? new \ArrayIterator(); if (!$admin->isAclEnabled()) { throw new \InvalidArgumentException('The admin must have ACL enabled.'); } diff --git a/tests/EventListener/ConfigureCRUDControllerListenerTest.php b/tests/EventListener/ConfigureCRUDControllerListenerTest.php index 19083d7f20..abf2021898 100644 --- a/tests/EventListener/ConfigureCRUDControllerListenerTest.php +++ b/tests/EventListener/ConfigureCRUDControllerListenerTest.php @@ -54,7 +54,7 @@ public function testItConfiguresCRUDController(): void $controllerEvent = new ControllerEvent( $this->createStub(HttpKernelInterface::class), - [$controller, 'listAction'], + $controller->listAction(...), $request, HttpKernelInterface::MAIN_REQUEST ); diff --git a/tests/Fixtures/FieldDescription/FieldDescription.php b/tests/Fixtures/FieldDescription/FieldDescription.php index b739e45af8..ccdd62cd0c 100644 --- a/tests/Fixtures/FieldDescription/FieldDescription.php +++ b/tests/Fixtures/FieldDescription/FieldDescription.php @@ -32,7 +32,7 @@ public function isIdentifier(): bool throw new \BadMethodCallException(\sprintf('Implement %s() method.', __METHOD__)); } - public function getValue(object $object): void + public function getValue(object $object): never { throw new \BadMethodCallException(\sprintf('Implement %s() method.', __METHOD__)); } diff --git a/tests/Form/DataTransformer/BackedEnumTransformerTest.php b/tests/Form/DataTransformer/BackedEnumTransformerTest.php index efb71b84a6..03540d708e 100644 --- a/tests/Form/DataTransformer/BackedEnumTransformerTest.php +++ b/tests/Form/DataTransformer/BackedEnumTransformerTest.php @@ -19,9 +19,6 @@ use Symfony\Component\Form\Exception\TransformationFailedException; use Symfony\Component\Form\Exception\UnexpectedTypeException; -/** - * @requires PHP 8.1 - */ final class BackedEnumTransformerTest extends TestCase { public function testReverseTransform(): void diff --git a/tests/Menu/Provider/GroupMenuProviderTest.php b/tests/Menu/Provider/GroupMenuProviderTest.php index 7a57622709..03c5498fc4 100644 --- a/tests/Menu/Provider/GroupMenuProviderTest.php +++ b/tests/Menu/Provider/GroupMenuProviderTest.php @@ -135,7 +135,7 @@ public function testGetMenuProviderWithCheckerGrantedMultipleGroupRoles( ): void { $this->checker ->method('isGranted') - ->willReturnCallback([$this, 'unanimousGrantCheckerMock']); + ->willReturnCallback($this->unanimousGrantCheckerMock(...)); $menu = $this->provider->get( 'providerFoo', @@ -162,7 +162,7 @@ public function testGetMenuProviderWithCheckerGrantedGroupAndItemRoles( ): void { $this->checker ->method('isGranted') - ->willReturnCallback([$this, 'unanimousGrantCheckerNoBazMock']); + ->willReturnCallback($this->unanimousGrantCheckerNoBazMock(...)); $menu = $this->provider->get( 'providerFoo', @@ -190,7 +190,7 @@ public function testGetMenuProviderWithCheckerGrantedMultipleGroupRolesOnTop( ): void { $this->checker ->method('isGranted') - ->willReturnCallback([$this, 'unanimousGrantCheckerMock']); + ->willReturnCallback($this->unanimousGrantCheckerMock(...)); $menu = $this->provider->get( 'providerFoo', diff --git a/tests/Twig/Extension/FakeTemplateRegistryExtension.php b/tests/Twig/Extension/FakeTemplateRegistryExtension.php index 176d7e53ee..b4ae69630e 100644 --- a/tests/Twig/Extension/FakeTemplateRegistryExtension.php +++ b/tests/Twig/Extension/FakeTemplateRegistryExtension.php @@ -21,7 +21,7 @@ final class FakeTemplateRegistryExtension extends AbstractExtension public function getFunctions(): array { return [ - new TwigFunction('get_admin_template', [$this, 'getAdminTemplate']), + new TwigFunction('get_admin_template', $this->getAdminTemplate(...)), ]; } diff --git a/tests/Twig/Extension/RenderElementExtensionTest.php b/tests/Twig/Extension/RenderElementExtensionTest.php index 9ccba3ee9b..8fb6fff197 100644 --- a/tests/Twig/Extension/RenderElementExtensionTest.php +++ b/tests/Twig/Extension/RenderElementExtensionTest.php @@ -1523,11 +1523,6 @@ class="x-editable" ], ]; - // TODO: Remove the "if" check when dropping support of PHP < 8.1 and add the case to the list - if (\PHP_VERSION_ID < 80100) { - return $elements; - } - $elements[] = [ '   ', FieldDescriptionInterface::TYPE_ENUM, @@ -2103,11 +2098,6 @@ class="sonata-readmore" ], ]; - // TODO: Remove the "if" check when dropping support of PHP < 8.1 and add the case to the list - if (\PHP_VERSION_ID < 80100) { - return $elements; - } - $elements[] = [ 'Data Hearts', FieldDescriptionInterface::TYPE_ENUM, diff --git a/tests/Twig/RenderElementRuntimeTest.php b/tests/Twig/RenderElementRuntimeTest.php index 8b62e56554..d55b8965c5 100644 --- a/tests/Twig/RenderElementRuntimeTest.php +++ b/tests/Twig/RenderElementRuntimeTest.php @@ -1473,11 +1473,6 @@ class="x-editable" ], ]; - // TODO: Remove the "if" check when dropping support of PHP < 8.1 and add the case to the list - if (\PHP_VERSION_ID < 80100) { - return $elements; - } - $elements[] = [ ' Hearts ', FieldDescriptionInterface::TYPE_ENUM, diff --git a/tests/Twig/XEditableRuntimeTest.php b/tests/Twig/XEditableRuntimeTest.php index e1665c2a60..3e6ed6bc09 100644 --- a/tests/Twig/XEditableRuntimeTest.php +++ b/tests/Twig/XEditableRuntimeTest.php @@ -93,19 +93,16 @@ public function provideGetXEditableChoicesIsIdempotentCases(): iterable ], ]; - // TODO: Remove the "if" check when dropping support of PHP < 8.1 and add the case to the list - if (\PHP_VERSION_ID >= 80100) { - yield 'enum cases' => [ - [ - 'required' => false, - 'multiple' => false, - 'choices' => [Suit::Hearts, Suit::Clubs], - ], - [ - ['value' => 'H', 'text' => 'Hearts'], - ['value' => 'C', 'text' => 'Clubs'], - ], - ]; - } + yield 'enum cases' => [ + [ + 'required' => false, + 'multiple' => false, + 'choices' => [Suit::Hearts, Suit::Clubs], + ], + [ + ['value' => 'H', 'text' => 'Hearts'], + ['value' => 'C', 'text' => 'Clubs'], + ], + ]; } }