From e9663cca6bca3b38dbfdcac0f0d9fabb1f0d10bb Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 23 Aug 2022 10:46:45 +0200 Subject: [PATCH] [HttpKernel] Replace ArgumentValueResolverInterface by ValueResolverInterface --- UPGRADE-6.2.md | 5 + .../ArgumentResolver/EntityValueResolver.php | 101 +--- .../EntityValueResolverTest.php | 504 +++++------------- .../Tests/Fixtures/Attribute/UuidIdEntity.php | 2 +- .../Doctrine/Tests/Fixtures/BaseUser.php | 9 + .../CompositeObjectNoToStringIdEntity.php | 9 + .../SingleIntIdStringWrapperNameEntity.php | 1 - .../PropertyInfo/Fixtures/DoctrineEnum.php | 2 +- .../Fixtures/DoctrineRelation.php | 2 +- src/Symfony/Bridge/Doctrine/composer.json | 4 +- .../FrameworkExtension.php | 3 + src/Symfony/Component/HttpKernel/CHANGELOG.md | 1 + .../Controller/ArgumentResolver.php | 29 +- .../BackedEnumValueResolver.php | 42 +- .../DateTimeValueResolver.php | 23 +- .../ArgumentResolver/DefaultValueResolver.php | 19 +- .../NotTaggedControllerValueResolver.php | 22 +- .../RequestAttributeValueResolver.php | 11 +- .../ArgumentResolver/RequestValueResolver.php | 11 +- .../ArgumentResolver/ServiceValueResolver.php | 24 +- .../ArgumentResolver/SessionValueResolver.php | 20 +- .../TraceableValueResolver.php | 13 +- .../ArgumentResolver/UidValueResolver.php | 21 +- .../VariadicValueResolver.php | 15 +- .../ArgumentValueResolverInterface.php | 2 + .../Controller/ValueResolverInterface.php | 28 + .../BackedEnumValueResolverTest.php | 18 +- .../DateTimeValueResolverTest.php | 30 +- .../NotTaggedControllerValueResolverTest.php | 20 +- .../ServiceValueResolverTest.php | 28 +- .../TraceableValueResolverTest.php | 3 + .../ArgumentResolver/UidValueResolverTest.php | 7 + .../Tests/Controller/ArgumentResolverTest.php | 3 + .../Component/HttpKernel/composer.json | 1 + .../AccessToken/QueryAccessTokenExtractor.php | 3 +- .../Http/Controller/UserValueResolver.php | 32 +- .../Http/Firewall/SwitchUserListener.php | 2 +- .../LoginLink/LoginLinkHandlerInterface.php | 2 +- .../JsonLoginAuthenticatorTest.php | 1 - .../Controller/UserValueResolverTest.php | 36 +- .../Component/Security/Http/composer.json | 1 + 41 files changed, 500 insertions(+), 610 deletions(-) create mode 100644 src/Symfony/Component/HttpKernel/Controller/ValueResolverInterface.php diff --git a/UPGRADE-6.2.md b/UPGRADE-6.2.md index 8271d0843738a..682ad84ff89dc 100644 --- a/UPGRADE-6.2.md +++ b/UPGRADE-6.2.md @@ -14,6 +14,11 @@ HttpFoundation * Deprecate `Request::getContentType()`, use `Request::getContentTypeFormat()` instead +HttpKernel +---------- + + * Deprecate `ArgumentValueResolverInterface`, use `ValueResolverInterface` instead + Ldap ---- diff --git a/src/Symfony/Bridge/Doctrine/ArgumentResolver/EntityValueResolver.php b/src/Symfony/Bridge/Doctrine/ArgumentResolver/EntityValueResolver.php index e5f45039eef22..77649065f6f0b 100644 --- a/src/Symfony/Bridge/Doctrine/ArgumentResolver/EntityValueResolver.php +++ b/src/Symfony/Bridge/Doctrine/ArgumentResolver/EntityValueResolver.php @@ -19,7 +19,7 @@ use Symfony\Bridge\Doctrine\Attribute\MapEntity; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -29,7 +29,7 @@ * @author Fabien Potencier * @author Jérémy Derussé */ -final class EntityValueResolver implements ArgumentValueResolverInterface +final class EntityValueResolver implements ValueResolverInterface { public function __construct( private ManagerRegistry $registry, @@ -41,66 +41,36 @@ public function __construct( /** * {@inheritdoc} */ - public function supports(Request $request, ArgumentMetadata $argument): bool + public function resolve(Request $request, ArgumentMetadata $argument): array { - if (!$this->registry->getManagerNames()) { - return false; - } - $options = $this->getMapEntityAttribute($argument); if (!$options->class || $options->disabled) { - return false; - } - - if (null === $options->expr && !($options->mapping || $options->exclude) && null === $this->getIdentifier($request, $options, $argument->getName())) { - return false; - } - - // Doctrine Entity? - if (!$objectManager = $this->getManager($options->objectManager, $options->class)) { - return false; + return []; } - - if ($objectManager->getMetadataFactory()->isTransient($options->class)) { - return false; + if (!$manager = $this->getManager($options->objectManager, $options->class)) { + return []; } - return null !== $options->expr || $this->getCriteria($request, $options, $objectManager); - } - - /** - * {@inheritdoc} - */ - public function resolve(Request $request, ArgumentMetadata $argument): iterable - { - $options = $this->getMapEntityAttribute($argument); - $name = $argument->getName(); - $objectManager = $this->getManager($options->objectManager, $options->class); - - $errorMessage = null; + $message = ''; if (null !== $options->expr) { - if (null === $object = $this->findViaExpression($objectManager, $request, $options)) { - $errorMessage = sprintf('The expression "%s" returned null', $options->expr); + if (null === $object = $this->findViaExpression($manager, $request, $options)) { + $message = sprintf(' The expression "%s" returned null.', $options->expr); } // find by identifier? - } elseif (false === $object = $this->find($objectManager, $request, $options, $name)) { + } elseif (false === $object = $this->find($manager, $request, $options, $argument->getName())) { // find by criteria - if (false === $object = $this->findOneBy($objectManager, $request, $options)) { - if (!$argument->isNullable()) { - throw new \LogicException(sprintf('Unable to guess how to get a Doctrine instance from the request information for parameter "%s".', $name)); - } - + if (!$criteria = $this->getCriteria($request, $options, $manager)) { + return []; + } + try { + $object = $manager->getRepository($options->class)->findOneBy($criteria); + } catch (NoResultException|ConversionException) { $object = null; } } if (null === $object && !$argument->isNullable()) { - $message = sprintf('"%s" object not found by the "%s" Argument Resolver.', $options->class, self::class); - if ($errorMessage) { - $message .= ' '.$errorMessage; - } - - throw new NotFoundHttpException($message); + throw new NotFoundHttpException(sprintf('"%s" object not found by "%s".', $options->class, self::class).$message); } return [$object]; @@ -112,18 +82,16 @@ private function getManager(?string $name, string $class): ?ObjectManager return $this->registry->getManagerForClass($class); } - if (!isset($this->registry->getManagerNames()[$name])) { - return null; - } - try { - return $this->registry->getManager($name); + $manager = $this->registry->getManager($name); } catch (\InvalidArgumentException) { return null; } + + return $manager->getMetadataFactory()->isTransient($class) ? null : $manager; } - private function find(ObjectManager $objectManager, Request $request, MapEntity $options, string $name): false|object|null + private function find(ObjectManager $manager, Request $request, MapEntity $options, string $name): false|object|null { if ($options->mapping || $options->exclude) { return false; @@ -134,15 +102,15 @@ private function find(ObjectManager $objectManager, Request $request, MapEntity return $id; } - if ($options->evictCache && $objectManager instanceof EntityManagerInterface) { - $cacheProvider = $objectManager->getCache(); + if ($options->evictCache && $manager instanceof EntityManagerInterface) { + $cacheProvider = $manager->getCache(); if ($cacheProvider && $cacheProvider->containsEntity($options->class, $id)) { $cacheProvider->evictEntity($options->class, $id); } } try { - return $objectManager->getRepository($options->class)->find($id); + return $manager->getRepository($options->class)->find($id); } catch (NoResultException|ConversionException) { return null; } @@ -179,20 +147,7 @@ private function getIdentifier(Request $request, MapEntity $options, string $nam return false; } - private function findOneBy(ObjectManager $objectManager, Request $request, MapEntity $options): false|object|null - { - if (!$criteria = $this->getCriteria($request, $options, $objectManager)) { - return false; - } - - try { - return $objectManager->getRepository($options->class)->findOneBy($criteria); - } catch (NoResultException|ConversionException) { - return null; - } - } - - private function getCriteria(Request $request, MapEntity $options, ObjectManager $objectManager): array + private function getCriteria(Request $request, MapEntity $options, ObjectManager $manager): array { if (null === $mapping = $options->mapping) { $mapping = $request->attributes->keys(); @@ -217,7 +172,7 @@ private function getCriteria(Request $request, MapEntity $options, ObjectManager } $criteria = []; - $metadata = $objectManager->getClassMetadata($options->class); + $metadata = $manager->getClassMetadata($options->class); foreach ($mapping as $attribute => $field) { if (!$metadata->hasField($field) && (!$metadata->hasAssociation($field) || !$metadata->isSingleValuedAssociation($field))) { @@ -234,13 +189,13 @@ private function getCriteria(Request $request, MapEntity $options, ObjectManager return $criteria; } - private function findViaExpression(ObjectManager $objectManager, Request $request, MapEntity $options): ?object + private function findViaExpression(ObjectManager $manager, Request $request, MapEntity $options): ?object { if (!$this->expressionLanguage) { throw new \LogicException(sprintf('You cannot use the "%s" if the ExpressionLanguage component is not available. Try running "composer require symfony/expression-language".', __CLASS__)); } - $repository = $objectManager->getRepository($options->class); + $repository = $manager->getRepository($options->class); $variables = array_merge($request->attributes->all(), ['repository' => $repository]); try { diff --git a/src/Symfony/Bridge/Doctrine/Tests/ArgumentResolver/EntityValueResolverTest.php b/src/Symfony/Bridge/Doctrine/Tests/ArgumentResolver/EntityValueResolverTest.php index 8856f0b9121a8..629a37256c0f2 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/ArgumentResolver/EntityValueResolverTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/ArgumentResolver/EntityValueResolverTest.php @@ -14,9 +14,9 @@ use Doctrine\DBAL\Types\ConversionException; use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\Mapping\ClassMetadata; -use Doctrine\Persistence\Mapping\ClassMetadataFactory; use Doctrine\Persistence\ObjectManager; use Doctrine\Persistence\ObjectRepository; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\ArgumentResolver\EntityValueResolver; use Symfony\Bridge\Doctrine\Attribute\MapEntity; @@ -28,182 +28,78 @@ class EntityValueResolverTest extends TestCase { - public function testSupports() + public function testResolveWithoutClass() { - $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); - $metadataFactory = $this->getMockBuilder(ClassMetadataFactory::class)->getMock(); - $classMetadata = $this->getMockBuilder(ClassMetadata::class)->getMock(); - $converter = new EntityValueResolver($registry); - - $registry->expects($this->once()) - ->method('getManagerNames') - ->with() - ->willReturn(['default' => 'default']); - - $registry->expects($this->once()) - ->method('getManagerForClass') - ->with('stdClass') - ->willReturn($manager); - $manager->expects($this->once()) - ->method('getMetadataFactory') - ->with() - ->willReturn($metadataFactory); - $metadataFactory->expects($this->once()) - ->method('isTransient') - ->with('stdClass') - ->willReturn(false); - $classMetadata->expects($this->once()) - ->method('hasField') - ->with('foo') - ->willReturn(true); - $manager->expects($this->once()) - ->method('getClassMetadata') - ->with('stdClass') - ->willReturn($classMetadata); - - $request = new Request(); - $request->attributes->set('foo', 'bar'); - $argument = $this->createArgument(); - - $this->assertTrue($converter->supports($request, $argument)); - } - - public function testSupportWithoutRegistry() - { - $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); - $converter = new EntityValueResolver($registry); - - $registry->expects($this->once()) - ->method('getManagerNames') - ->with() - ->willReturn([]); - - $request = new Request(); - $argument = $this->createArgument(); - - $this->assertFalse($converter->supports($request, $argument)); - } - - public function testSupportWithoutClass() - { - $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); - $converter = new EntityValueResolver($registry); - - $registry->expects($this->once()) - ->method('getManagerNames') - ->with() - ->willReturn(['default' => 'default']); + $registry = $this->createRegistry($manager); + $resolver = new EntityValueResolver($registry); $request = new Request(); $argument = new ArgumentMetadata('arg', null, false, false, null); - $this->assertFalse($converter->supports($request, $argument)); + $this->assertSame([], $resolver->resolve($request, $argument)); } - public function testSupportWithoutAttribute() + public function testResolveWithoutAttribute() { - $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); - $converter = new EntityValueResolver($registry, null, new MapEntity(disabled: true)); - - $registry->expects($this->once()) - ->method('getManagerNames') - ->with() - ->willReturn(['default' => 'default']); + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); + $resolver = new EntityValueResolver($registry, null, new MapEntity(disabled: true)); $request = new Request(); $argument = $this->createArgument(); - $this->assertFalse($converter->supports($request, $argument)); + $this->assertSame([], $resolver->resolve($request, $argument)); } - public function testSupportWithoutManager() + public function testResolveWithoutManager() { - $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); - $converter = new EntityValueResolver($registry); - - $registry->expects($this->once()) - ->method('getManagerNames') - ->with() - ->willReturn(['default' => 'default']); - - $registry->expects($this->once()) - ->method('getManagerForClass') - ->with('stdClass') - ->willReturn(null); + $registry = $this->createRegistry(null); + $resolver = new EntityValueResolver($registry); $request = new Request(); $argument = $this->createArgument(); - $this->assertFalse($converter->supports($request, $argument)); - } - - public function testResolveWithNoIdAndData() - { - $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); - $registry->expects($this->once()) - ->method('getManagerForClass') - ->willReturn($this->getMockBuilder(ObjectManager::class)->getMock()); - - $converter = new EntityValueResolver($registry); - - $this->expectException(\LogicException::class); - - $request = new Request(); - $argument = $this->createArgument(null, new MapEntity()); - - $converter->resolve($request, $argument); + $this->assertSame([], $resolver->resolve($request, $argument)); } public function testResolveWithNoIdAndDataOptional() { - $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); - $registry->expects($this->once()) - ->method('getManagerForClass') - ->willReturn($this->getMockBuilder(ObjectManager::class)->getMock()); - - $converter = new EntityValueResolver($registry); + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); + $resolver = new EntityValueResolver($registry); $request = new Request(); $argument = $this->createArgument(null, new MapEntity(), 'arg', true); - $ret = $converter->resolve($request, $argument); - - $this->assertYieldEquals([null], $ret); + $this->assertSame([], $resolver->resolve($request, $argument)); } public function testResolveWithStripNulls() { - $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); - $converter = new EntityValueResolver($registry); + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); + $resolver = new EntityValueResolver($registry); $request = new Request(); $request->attributes->set('arg', null); $argument = $this->createArgument('stdClass', new MapEntity(stripNull: true), 'arg', true); - $classMetadata = $this->getMockBuilder(ClassMetadata::class)->getMock(); - $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $metadata = $this->getMockBuilder(ClassMetadata::class)->getMock(); + $metadata->expects($this->once()) + ->method('hasField') + ->with('arg') + ->willReturn(true); + $manager->expects($this->once()) ->method('getClassMetadata') ->with('stdClass') - ->willReturn($classMetadata); + ->willReturn($metadata); $manager->expects($this->never()) ->method('getRepository'); - $registry->expects($this->once()) - ->method('getManagerForClass') - ->with('stdClass') - ->willReturn($manager); - - $classMetadata->expects($this->once()) - ->method('hasField') - ->with('arg') - ->willReturn(true); - - $ret = $converter->resolve($request, $argument); - - $this->assertYieldEquals([null], $ret); + $this->assertSame([], $resolver->resolve($request, $argument)); } /** @@ -211,95 +107,75 @@ public function testResolveWithStripNulls() */ public function testResolveWithId(string|int $id) { - $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); - $converter = new EntityValueResolver($registry); + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); + $resolver = new EntityValueResolver($registry); $request = new Request(); $request->attributes->set('id', $id); $argument = $this->createArgument('stdClass', new MapEntity(id: 'id')); - $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); - $objectRepository = $this->getMockBuilder(ObjectRepository::class)->getMock(); - $registry->expects($this->once()) - ->method('getManagerForClass') - ->with('stdClass') - ->willReturn($manager); - - $manager->expects($this->once()) - ->method('getRepository') - ->with('stdClass') - ->willReturn($objectRepository); - - $objectRepository->expects($this->once()) + $repository = $this->getMockBuilder(ObjectRepository::class)->getMock(); + $repository->expects($this->once()) ->method('find') ->with($id) ->willReturn($object = new \stdClass()); - $ret = $converter->resolve($request, $argument); + $manager->expects($this->once()) + ->method('getRepository') + ->with('stdClass') + ->willReturn($repository); - $this->assertYieldEquals([$object], $ret); + $this->assertSame([$object], $resolver->resolve($request, $argument)); } public function testResolveWithNullId() { - $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); - $registry->expects($this->once()) - ->method('getManagerForClass') - ->willReturn($this->getMockBuilder(ObjectManager::class)->getMock()); - - $converter = new EntityValueResolver($registry); + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); + $resolver = new EntityValueResolver($registry); $request = new Request(); $request->attributes->set('id', null); $argument = $this->createArgument(isNullable: true); - $ret = $converter->resolve($request, $argument); - - $this->assertYieldEquals([null], $ret); + $this->assertSame([null], $resolver->resolve($request, $argument)); } public function testResolveWithConversionFailedException() { - $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); - $converter = new EntityValueResolver($registry); + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); + $resolver = new EntityValueResolver($registry); $request = new Request(); $request->attributes->set('id', 'test'); $argument = $this->createArgument('stdClass', new MapEntity(id: 'id')); - $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); - $objectRepository = $this->getMockBuilder(ObjectRepository::class)->getMock(); - $registry->expects($this->once()) - ->method('getManagerForClass') - ->with('stdClass') - ->willReturn($manager); + $repository = $this->getMockBuilder(ObjectRepository::class)->getMock(); + $repository->expects($this->once()) + ->method('find') + ->with('test') + ->will($this->throwException(new ConversionException())); $manager->expects($this->once()) ->method('getRepository') ->with('stdClass') - ->willReturn($objectRepository); - - $objectRepository->expects($this->once()) - ->method('find') - ->with('test') - ->will($this->throwException(new ConversionException())); + ->willReturn($repository); $this->expectException(NotFoundHttpException::class); - $converter->resolve($request, $argument); + $resolver->resolve($request, $argument); } public function testUsedProperIdentifier() { - $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); - $registry->expects($this->once()) - ->method('getManagerForClass') - ->willReturn($this->getMockBuilder(ObjectManager::class)->getMock()); - - $converter = new EntityValueResolver($registry); + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); + $resolver = new EntityValueResolver($registry); $request = new Request(); $request->attributes->set('id', 1); @@ -308,9 +184,7 @@ public function testUsedProperIdentifier() $argument = $this->createArgument('stdClass', new MapEntity(id: 'entity_id'), 'arg', true); - $ret = $converter->resolve($request, $argument); - - $this->assertYieldEquals([null], $ret); + $this->assertSame([null], $resolver->resolve($request, $argument)); } public function idsProvider(): iterable @@ -322,41 +196,31 @@ public function idsProvider(): iterable public function testResolveGuessOptional() { - $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); - $converter = new EntityValueResolver($registry); + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); + $resolver = new EntityValueResolver($registry); $request = new Request(); $request->attributes->set('guess', null); $argument = $this->createArgument('stdClass', new MapEntity(), 'arg', true); - $classMetadata = $this->getMockBuilder(ClassMetadata::class)->getMock(); - $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $metadata = $this->getMockBuilder(ClassMetadata::class)->getMock(); $manager->expects($this->once()) ->method('getClassMetadata') ->with('stdClass') - ->willReturn($classMetadata); - - $objectRepository = $this->getMockBuilder(ObjectRepository::class)->getMock(); - $registry->expects($this->once()) - ->method('getManagerForClass') - ->with('stdClass') - ->willReturn($manager); + ->willReturn($metadata); $manager->expects($this->never())->method('getRepository'); - $objectRepository->expects($this->never())->method('find'); - $objectRepository->expects($this->never())->method('findOneBy'); - - $ret = $converter->resolve($request, $argument); - - $this->assertYieldEquals([null], $ret); + $this->assertSame([], $resolver->resolve($request, $argument)); } public function testResolveWithMappingAndExclude() { - $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); - $converter = new EntityValueResolver($registry); + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); + $resolver = new EntityValueResolver($registry); $request = new Request(); $request->attributes->set('foo', 1); @@ -367,144 +231,36 @@ public function testResolveWithMappingAndExclude() new MapEntity(mapping: ['foo' => 'Foo'], exclude: ['bar']) ); - $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); $metadata = $this->getMockBuilder(ClassMetadata::class)->getMock(); - $repository = $this->getMockBuilder(ObjectRepository::class)->getMock(); - - $registry->expects($this->once()) - ->method('getManagerForClass') - ->with('stdClass') - ->willReturn($manager); + $metadata->expects($this->once()) + ->method('hasField') + ->with('Foo') + ->willReturn(true); $manager->expects($this->once()) ->method('getClassMetadata') - ->with('stdClass') - ->willReturn($metadata); - $manager->expects($this->once()) - ->method('getRepository') - ->with('stdClass') - ->willReturn($repository); - - $metadata->expects($this->once()) - ->method('hasField') - ->with('Foo') - ->willReturn(true); + ->with('stdClass') + ->willReturn($metadata); + $repository = $this->getMockBuilder(ObjectRepository::class)->getMock(); $repository->expects($this->once()) ->method('findOneBy') ->with(['Foo' => 1]) ->willReturn($object = new \stdClass()); - $ret = $converter->resolve($request, $argument); - - $this->assertYieldEquals([$object], $ret); - } - - public function testIgnoreMappingWhenAutoMappingDisabled() - { - $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); - $registry->expects($this->once()) - ->method('getManagerForClass') - ->willReturn($this->getMockBuilder(ObjectManager::class)->getMock()); - - $converter = new EntityValueResolver($registry, null, new MapEntity(mapping: [])); - - $request = new Request(); - $request->attributes->set('foo', 1); - - $argument = $this->createArgument( - 'stdClass', - new MapEntity() - ); - - $metadata = $this->getMockBuilder(ClassMetadata::class)->getMock(); - - $metadata->expects($this->never()) - ->method('hasField'); - - $this->expectException(\LogicException::class); - - $ret = $converter->resolve($request, $argument); - - $this->assertYieldEquals([], $ret); - } - - public function testSupportsWithConfiguredObjectManager() - { - $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); - $converter = new EntityValueResolver($registry); - - $argument = $this->createArgument('stdClass', new MapEntity(objectManager: 'foo')); - $metadataFactory = $this->getMockBuilder(ClassMetadataFactory::class)->getMock(); - $metadataFactory->expects($this->once()) - ->method('isTransient') - ->with('stdClass') - ->willReturn(false); - - $objectManager = $this->getMockBuilder(ObjectManager::class)->getMock(); - $objectManager->expects($this->once()) - ->method('getMetadataFactory') - ->willReturn($metadataFactory); - - $registry->expects($this->exactly(2)) - ->method('getManagerNames') - ->willReturn(['foo' => 'foo']); - - $classMetadata = $this->getMockBuilder(ClassMetadata::class)->getMock(); - $classMetadata->expects($this->once()) - ->method('hasField') - ->with('foo') - ->willReturn(true); - $objectManager->expects($this->once()) - ->method('getClassMetadata') + $manager->expects($this->once()) + ->method('getRepository') ->with('stdClass') - ->willReturn($classMetadata); + ->willReturn($repository); - $registry->expects($this->once()) - ->method('getManager') - ->with('foo') - ->willReturn($objectManager); - - $request = new Request(); - $request->attributes->set('foo', 'bar'); - $ret = $converter->supports($request, $argument); - - $this->assertTrue($ret, 'Should be supported'); - } - - public function testSupportsWithDifferentConfiguration() - { - $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); - $converter = new EntityValueResolver($registry); - - $argument = $this->createArgument('DateTime'); - - $objectManager = $this->getMockBuilder(ObjectManager::class)->getMock(); - $objectManager->expects($this->never()) - ->method('getMetadataFactory'); - - $registry->expects($this->any()) - ->method('getManagerNames') - ->willReturn(['default' => 'default']); - - $registry->expects($this->never()) - ->method('getManager'); - - $ret = $converter->supports(new Request(), $argument); - - $this->assertFalse($ret, 'Should not be supported'); + $this->assertSame([$object], $resolver->resolve($request, $argument)); } public function testExceptionWithExpressionIfNoLanguageAvailable() { - $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); - $registry->expects($this->once()) - ->method('getManagerForClass') - ->willReturn($this->getMockBuilder(ObjectManager::class)->getMock()); - - $converter = new EntityValueResolver($registry); - - $this->expectException(\LogicException::class); + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); + $resolver = new EntityValueResolver($registry); $request = new Request(); $argument = $this->createArgument( @@ -513,14 +269,17 @@ public function testExceptionWithExpressionIfNoLanguageAvailable() 'arg1' ); - $converter->resolve($request, $argument); + $this->expectException(\LogicException::class); + + $resolver->resolve($request, $argument); } public function testExpressionFailureReturns404() { - $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); $language = $this->getMockBuilder(ExpressionLanguage::class)->getMock(); - $converter = new EntityValueResolver($registry, $language); + $resolver = new EntityValueResolver($registry, $language); $this->expectException(NotFoundHttpException::class); @@ -531,33 +290,28 @@ public function testExpressionFailureReturns404() 'arg1' ); - $objectManager = $this->getMockBuilder(ObjectManager::class)->getMock(); - $objectRepository = $this->getMockBuilder(ObjectRepository::class)->getMock(); - - $objectManager->expects($this->once()) - ->method('getRepository') - ->willReturn($objectRepository); - + $repository = $this->getMockBuilder(ObjectRepository::class)->getMock(); // find should not be attempted on this repository as a fallback - $objectRepository->expects($this->never()) + $repository->expects($this->never()) ->method('find'); - $registry->expects($this->once()) - ->method('getManagerForClass') - ->willReturn($objectManager); + $manager->expects($this->once()) + ->method('getRepository') + ->willReturn($repository); $language->expects($this->once()) ->method('evaluate') ->willReturn(null); - $converter->resolve($request, $argument); + $resolver->resolve($request, $argument); } public function testExpressionMapsToArgument() { - $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); $language = $this->getMockBuilder(ExpressionLanguage::class)->getMock(); - $converter = new EntityValueResolver($registry, $language); + $resolver = new EntityValueResolver($registry, $language); $request = new Request(); $request->attributes->set('id', 5); @@ -567,41 +321,32 @@ public function testExpressionMapsToArgument() 'arg1' ); - $objectManager = $this->getMockBuilder(ObjectManager::class)->getMock(); - $objectRepository = $this->getMockBuilder(ObjectRepository::class)->getMock(); - - $objectManager->expects($this->once()) - ->method('getRepository') - ->willReturn($objectRepository); - + $repository = $this->getMockBuilder(ObjectRepository::class)->getMock(); // find should not be attempted on this repository as a fallback - $objectRepository->expects($this->never()) + $repository->expects($this->never()) ->method('find'); - $registry->expects($this->once()) - ->method('getManagerForClass') - ->willReturn($objectManager); + $manager->expects($this->once()) + ->method('getRepository') + ->willReturn($repository); $language->expects($this->once()) ->method('evaluate') ->with('repository.findOneByCustomMethod(id)', [ - 'repository' => $objectRepository, + 'repository' => $repository, 'id' => 5, ]) ->willReturn($object = new \stdClass()); - $ret = $converter->resolve($request, $argument); - $this->assertYieldEquals([$object], $ret); + $this->assertSame([$object], $resolver->resolve($request, $argument)); } public function testExpressionSyntaxErrorThrowsException() { - $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); + $manager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $registry = $this->createRegistry($manager); $language = $this->getMockBuilder(ExpressionLanguage::class)->getMock(); - $converter = new EntityValueResolver($registry, $language); - - $this->expectException(\LogicException::class); - $this->expectExceptionMessage('syntax error message around position 10'); + $resolver = new EntityValueResolver($registry, $language); $request = new Request(); $argument = $this->createArgument( @@ -610,28 +355,22 @@ public function testExpressionSyntaxErrorThrowsException() 'arg1' ); - $objectManager = $this->getMockBuilder(ObjectManager::class)->getMock(); - $objectRepository = $this->getMockBuilder(ObjectRepository::class)->getMock(); - - $objectManager->expects($this->once()) - ->method('getRepository') - ->willReturn($objectRepository); - + $repository = $this->getMockBuilder(ObjectRepository::class)->getMock(); // find should not be attempted on this repository as a fallback - $objectRepository->expects($this->never()) + $repository->expects($this->never()) ->method('find'); - $registry->expects($this->once()) - ->method('getManagerForClass') - ->willReturn($objectManager); + $manager->expects($this->once()) + ->method('getRepository') + ->willReturn($repository); $language->expects($this->once()) ->method('evaluate') ->will($this->throwException(new SyntaxError('syntax error message', 10))); - $ret = $converter->resolve($request, $argument); - - $this->assertYieldEquals([null], $ret); + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('syntax error message around position 10'); + $resolver->resolve($request, $argument); } private function createArgument(string $class = null, MapEntity $entity = null, string $name = 'arg', bool $isNullable = false): ArgumentMetadata @@ -639,13 +378,18 @@ private function createArgument(string $class = null, MapEntity $entity = null, return new ArgumentMetadata($name, $class ?? \stdClass::class, false, false, null, $isNullable, $entity ? [$entity] : []); } - private function assertYieldEquals(array $expected, iterable $generator) + private function createRegistry(ObjectManager $manager = null): ManagerRegistry&MockObject { - $args = []; - foreach ($generator as $arg) { - $args[] = $arg; - } + $registry = $this->getMockBuilder(ManagerRegistry::class)->getMock(); + + $registry->expects($this->any()) + ->method('getManagerForClass') + ->willReturn($manager); + + $registry->expects($this->any()) + ->method('getManager') + ->willReturn($manager); - $this->assertEquals($expected, $args); + return $registry; } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Attribute/UuidIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Attribute/UuidIdEntity.php index 3d28d4469c1fb..6d16ee75e54ae 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Attribute/UuidIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Attribute/UuidIdEntity.php @@ -19,7 +19,7 @@ class UuidIdEntity { #[Id] - #[Column("uuid")] + #[Column('uuid')] protected $id; public function __construct($id) diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/BaseUser.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/BaseUser.php index f03157637b256..c8be89cc760e0 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/BaseUser.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/BaseUser.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Bridge\Doctrine\Tests\Fixtures; /** diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeObjectNoToStringIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeObjectNoToStringIdEntity.php index 82811b89ed8c0..73a6419e2f0c0 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeObjectNoToStringIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeObjectNoToStringIdEntity.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Bridge\Doctrine\Tests\Fixtures; use Doctrine\ORM\Mapping as ORM; diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdStringWrapperNameEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdStringWrapperNameEntity.php index a06f4432a761a..8f4c3663d2148 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdStringWrapperNameEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdStringWrapperNameEntity.php @@ -14,7 +14,6 @@ use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\Id; -use Symfony\Bridge\Doctrine\Tests\Fixtures\Type\StringWrapper; /** @Entity */ class SingleIntIdStringWrapperNameEntity diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEnum.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEnum.php index fd5271fc47730..8a63a4e8db248 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEnum.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEnum.php @@ -11,9 +11,9 @@ namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures; -use Doctrine\ORM\Mapping\Id; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\Id; /** * @Entity diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php index 61a658096add0..91115abb719ec 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php @@ -14,8 +14,8 @@ use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\Id; -use Doctrine\ORM\Mapping\ManyToOne; use Doctrine\ORM\Mapping\JoinColumn; +use Doctrine\ORM\Mapping\ManyToOne; /** * @Entity diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index 8de4371499e8d..c68fe94ba5ac8 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -30,7 +30,7 @@ "symfony/config": "^5.4|^6.0", "symfony/dependency-injection": "^5.4|^6.0", "symfony/form": "^5.4.9|^6.0.9", - "symfony/http-kernel": "^5.4|^6.0", + "symfony/http-kernel": "^6.2", "symfony/messenger": "^5.4|^6.0", "symfony/doctrine-messenger": "^5.4|^6.0", "symfony/property-access": "^5.4|^6.0", @@ -57,7 +57,7 @@ "symfony/cache": "<5.4", "symfony/dependency-injection": "<5.4", "symfony/form": "<5.4", - "symfony/http-kernel": "<5.4", + "symfony/http-kernel": "<6.2", "symfony/messenger": "<5.4", "symfony/property-info": "<5.4", "symfony/security-bundle": "<5.4", diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index ea76107b01440..bfd53358618d9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -87,6 +87,7 @@ use Symfony\Component\HttpKernel\Controller\ArgumentResolver\BackedEnumValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\UidValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\Lock\LockFactory; @@ -590,6 +591,8 @@ public function load(array $configs, ContainerBuilder $container) ->addTag('container.service_subscriber'); $container->registerForAutoconfiguration(ArgumentValueResolverInterface::class) ->addTag('controller.argument_value_resolver'); + $container->registerForAutoconfiguration(ValueResolverInterface::class) + ->addTag('controller.argument_value_resolver'); $container->registerForAutoconfiguration(AbstractController::class) ->addTag('controller.service_arguments'); $container->registerForAutoconfiguration(DataCollectorInterface::class) diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index 1ed8f3a7108d1..991f9c217334a 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -8,6 +8,7 @@ CHANGELOG * Add `ControllerEvent::getAttributes()` to handle attributes on controllers * Add `#[Cache]` to describe the default HTTP cache headers on controllers * Add `absolute_uri` option to surrogate fragment renderers + * Add `ValueResolverInterface` and deprecate `ArgumentValueResolverInterface` 6.1 --- diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php index 6359611e5c0cd..6d961318ddb98 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php @@ -16,6 +16,7 @@ use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\SessionValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\VariadicValueResolver; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactoryInterface; @@ -31,7 +32,7 @@ final class ArgumentResolver implements ArgumentResolverInterface private iterable $argumentValueResolvers; /** - * @param iterable $argumentValueResolvers + * @param iterable $argumentValueResolvers */ public function __construct(ArgumentMetadataFactoryInterface $argumentMetadataFactory = null, iterable $argumentValueResolvers = []) { @@ -49,24 +50,28 @@ public function getArguments(Request $request, callable $controller): array foreach ($this->argumentMetadataFactory->createArgumentMetadata($controller, ...$reflectors) as $metadata) { foreach ($this->argumentValueResolvers as $resolver) { - if (!$resolver->supports($request, $metadata)) { + if ((!$resolver instanceof ValueResolverInterface || $resolver instanceof TraceableValueResolver) && !$resolver->supports($request, $metadata)) { continue; } - $resolved = $resolver->resolve($request, $metadata); + $count = 0; + foreach ($resolver->resolve($request, $metadata) as $argument) { + ++$count; + $arguments[] = $argument; + } - $atLeastOne = false; - foreach ($resolved as $append) { - $atLeastOne = true; - $arguments[] = $append; + if (1 < $count && !$metadata->isVariadic()) { + throw new \InvalidArgumentException(sprintf('"%s::resolve()" must yield at most one value for non-variadic arguments.', get_debug_type($resolver))); } - if (!$atLeastOne) { - throw new \InvalidArgumentException(sprintf('"%s::resolve()" must yield at least one value.', get_debug_type($resolver))); + if ($count) { + // continue to the next controller argument + continue 2; } - // continue to the next controller argument - continue 2; + if (!$resolver instanceof ValueResolverInterface) { + throw new \InvalidArgumentException(sprintf('"%s::resolve()" must yield at least one value.', get_debug_type($resolver))); + } } $representative = $controller; @@ -74,7 +79,7 @@ public function getArguments(Request $request, callable $controller): array if (\is_array($representative)) { $representative = sprintf('%s::%s()', \get_class($representative[0]), $representative[1]); } elseif (\is_object($representative)) { - $representative = \get_class($representative); + $representative = get_debug_type($representative); } throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one.', $representative, $metadata->getName())); diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/BackedEnumValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/BackedEnumValueResolver.php index 8a84ac130bdca..4f0ca76d30226 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/BackedEnumValueResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/BackedEnumValueResolver.php @@ -13,6 +13,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -21,11 +22,18 @@ * leading to a 404 Not Found if the attribute value isn't a valid backing value for the enum type. * * @author Maxime Steinhausser + * + * @final since Symfony 6.2 */ -class BackedEnumValueResolver implements ArgumentValueResolverInterface +class BackedEnumValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface { + /** + * @deprecated since Symfony 6.2, use resolve() instead + */ public function supports(Request $request, ArgumentMetadata $argument): bool { + @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); + if (!is_subclass_of($argument->getType(), \BackedEnum::class)) { return false; } @@ -43,31 +51,43 @@ public function supports(Request $request, ArgumentMetadata $argument): bool public function resolve(Request $request, ArgumentMetadata $argument): iterable { + if (!is_subclass_of($argument->getType(), \BackedEnum::class)) { + return []; + } + + if ($argument->isVariadic()) { + // only target route path parameters, which cannot be variadic. + return []; + } + + // do not support if no value can be resolved at all + // letting the \Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver be used + // or \Symfony\Component\HttpKernel\Controller\ArgumentResolver fail with a meaningful error. + if (!$request->attributes->has($argument->getName())) { + return []; + } + $value = $request->attributes->get($argument->getName()); if (null === $value) { - yield null; - - return; + return [null]; } if ($value instanceof \BackedEnum) { - yield $value; - - return; + return [$value]; } if (!\is_int($value) && !\is_string($value)) { - throw new \LogicException(sprintf('Could not resolve the "%s $%s" controller argument: expecting an int or string, got %s.', $argument->getType(), $argument->getName(), get_debug_type($value))); + throw new \LogicException(sprintf('Could not resolve the "%s $%s" controller argument: expecting an int or string, got "%s".', $argument->getType(), $argument->getName(), get_debug_type($value))); } /** @var class-string<\BackedEnum> $enumType */ $enumType = $argument->getType(); try { - yield $enumType::from($value); - } catch (\ValueError $error) { - throw new NotFoundHttpException(sprintf('Could not resolve the "%s $%s" controller argument: %s', $argument->getType(), $argument->getName(), $error->getMessage()), $error); + return [$enumType::from($value)]; + } catch (\ValueError $e) { + throw new NotFoundHttpException(sprintf('Could not resolve the "%s $%s" controller argument: ', $argument->getType(), $argument->getName()).$e->getMessage(), $e); } } } diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/DateTimeValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/DateTimeValueResolver.php index 55007bef7bc87..d310fee19aaa5 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/DateTimeValueResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/DateTimeValueResolver.php @@ -14,6 +14,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Attribute\MapDateTime; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -23,33 +24,35 @@ * @author Benjamin Eberlei * @author Tim Goudriaan */ -final class DateTimeValueResolver implements ArgumentValueResolverInterface +final class DateTimeValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface { /** - * {@inheritdoc} + * @deprecated since Symfony 6.2, use resolve() instead */ public function supports(Request $request, ArgumentMetadata $argument): bool { + @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); + return is_a($argument->getType(), \DateTimeInterface::class, true) && $request->attributes->has($argument->getName()); } /** * {@inheritdoc} */ - public function resolve(Request $request, ArgumentMetadata $argument): iterable + public function resolve(Request $request, ArgumentMetadata $argument): array { + if (!is_a($argument->getType(), \DateTimeInterface::class, true) || !$request->attributes->has($argument->getName())) { + return []; + } + $value = $request->attributes->get($argument->getName()); if ($value instanceof \DateTimeInterface) { - yield $value; - - return; + return [$value]; } if ($argument->isNullable() && !$value) { - yield null; - - return; + return [null]; } $class = \DateTimeInterface::class === $argument->getType() ? \DateTimeImmutable::class : $argument->getType(); @@ -83,6 +86,6 @@ public function resolve(Request $request, ArgumentMetadata $argument): iterable throw new NotFoundHttpException(sprintf('Invalid date given for parameter "%s".', $argument->getName())); } - yield $date; + return [$date]; } } diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/DefaultValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/DefaultValueResolver.php index 32a0e071d6884..01d8cf9255290 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/DefaultValueResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/DefaultValueResolver.php @@ -13,6 +13,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; /** @@ -20,21 +21,31 @@ * * @author Iltar van der Berg */ -final class DefaultValueResolver implements ArgumentValueResolverInterface +final class DefaultValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface { /** - * {@inheritdoc} + * @deprecated since Symfony 6.2, use resolve() instead */ public function supports(Request $request, ArgumentMetadata $argument): bool { + @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); + return $argument->hasDefaultValue() || (null !== $argument->getType() && $argument->isNullable() && !$argument->isVariadic()); } /** * {@inheritdoc} */ - public function resolve(Request $request, ArgumentMetadata $argument): iterable + public function resolve(Request $request, ArgumentMetadata $argument): array { - yield $argument->hasDefaultValue() ? $argument->getDefaultValue() : null; + if ($argument->hasDefaultValue()) { + return [$argument->getDefaultValue()]; + } + + if (null !== $argument->getType() && $argument->isNullable() && !$argument->isVariadic()) { + return [null]; + } + + return []; } } diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php index 22a4d2ae74498..7b8293ab4ff4b 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php @@ -15,6 +15,7 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; /** @@ -22,7 +23,7 @@ * * @author Simeon Kolev */ -final class NotTaggedControllerValueResolver implements ArgumentValueResolverInterface +final class NotTaggedControllerValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface { private ContainerInterface $container; @@ -32,10 +33,12 @@ public function __construct(ContainerInterface $container) } /** - * {@inheritdoc} + * @deprecated since Symfony 6.2, use resolve() instead */ public function supports(Request $request, ArgumentMetadata $argument): bool { + @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); + $controller = $request->attributes->get('_controller'); if (\is_array($controller) && \is_callable($controller, true) && \is_string($controller[0])) { @@ -58,21 +61,28 @@ public function supports(Request $request, ArgumentMetadata $argument): bool /** * {@inheritdoc} */ - public function resolve(Request $request, ArgumentMetadata $argument): iterable + public function resolve(Request $request, ArgumentMetadata $argument): array { - if (\is_array($controller = $request->attributes->get('_controller'))) { + $controller = $request->attributes->get('_controller'); + + if (\is_array($controller) && \is_callable($controller, true) && \is_string($controller[0])) { $controller = $controller[0].'::'.$controller[1]; + } elseif (!\is_string($controller) || '' === $controller) { + return []; } if ('\\' === $controller[0]) { $controller = ltrim($controller, '\\'); } - if (!$this->container->has($controller)) { - $i = strrpos($controller, ':'); + if (!$this->container->has($controller) && false !== $i = strrpos($controller, ':')) { $controller = substr($controller, 0, $i).strtolower(substr($controller, $i)); } + if ($this->container->has($controller)) { + return []; + } + $what = sprintf('argument $%s of "%s()"', $argument->getName(), $controller); $message = sprintf('Could not resolve %s, maybe you forgot to register the controller as a service or missed tagging it with the "controller.service_arguments"?', $what); diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php index c62d327b65a2c..2362e0b8c9110 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php @@ -13,6 +13,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; /** @@ -20,21 +21,23 @@ * * @author Iltar van der Berg */ -final class RequestAttributeValueResolver implements ArgumentValueResolverInterface +final class RequestAttributeValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface { /** - * {@inheritdoc} + * @deprecated since Symfony 6.2, use resolve() instead */ public function supports(Request $request, ArgumentMetadata $argument): bool { + @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); + return !$argument->isVariadic() && $request->attributes->has($argument->getName()); } /** * {@inheritdoc} */ - public function resolve(Request $request, ArgumentMetadata $argument): iterable + public function resolve(Request $request, ArgumentMetadata $argument): array { - yield $request->attributes->get($argument->getName()); + return !$argument->isVariadic() && $request->attributes->has($argument->getName()) ? [$request->attributes->get($argument->getName())] : []; } } diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestValueResolver.php index 75cbd97edbbfa..01781430b69d9 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestValueResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestValueResolver.php @@ -13,6 +13,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; /** @@ -20,21 +21,23 @@ * * @author Iltar van der Berg */ -final class RequestValueResolver implements ArgumentValueResolverInterface +final class RequestValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface { /** - * {@inheritdoc} + * @deprecated since Symfony 6.2, use resolve() instead */ public function supports(Request $request, ArgumentMetadata $argument): bool { + @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); + return Request::class === $argument->getType() || is_subclass_of($argument->getType(), Request::class); } /** * {@inheritdoc} */ - public function resolve(Request $request, ArgumentMetadata $argument): iterable + public function resolve(Request $request, ArgumentMetadata $argument): array { - yield $request; + return Request::class === $argument->getType() || is_subclass_of($argument->getType(), Request::class) ? [$request] : []; } } diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/ServiceValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/ServiceValueResolver.php index 7122a6204581f..a540f160f7554 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/ServiceValueResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/ServiceValueResolver.php @@ -15,6 +15,7 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; /** @@ -22,7 +23,7 @@ * * @author Nicolas Grekas */ -final class ServiceValueResolver implements ArgumentValueResolverInterface +final class ServiceValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface { private ContainerInterface $container; @@ -32,10 +33,12 @@ public function __construct(ContainerInterface $container) } /** - * {@inheritdoc} + * @deprecated since Symfony 6.2, use resolve() instead */ public function supports(Request $request, ArgumentMetadata $argument): bool { + @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); + $controller = $request->attributes->get('_controller'); if (\is_array($controller) && \is_callable($controller, true) && \is_string($controller[0])) { @@ -58,23 +61,30 @@ public function supports(Request $request, ArgumentMetadata $argument): bool /** * {@inheritdoc} */ - public function resolve(Request $request, ArgumentMetadata $argument): iterable + public function resolve(Request $request, ArgumentMetadata $argument): array { - if (\is_array($controller = $request->attributes->get('_controller'))) { + $controller = $request->attributes->get('_controller'); + + if (\is_array($controller) && \is_callable($controller, true) && \is_string($controller[0])) { $controller = $controller[0].'::'.$controller[1]; + } elseif (!\is_string($controller) || '' === $controller) { + return []; } if ('\\' === $controller[0]) { $controller = ltrim($controller, '\\'); } - if (!$this->container->has($controller)) { - $i = strrpos($controller, ':'); + if (!$this->container->has($controller) && false !== $i = strrpos($controller, ':')) { $controller = substr($controller, 0, $i).strtolower(substr($controller, $i)); } + if (!$this->container->has($controller) || !$this->container->get($controller)->has($argument->getName())) { + return []; + } + try { - yield $this->container->get($controller)->get($argument->getName()); + return [$this->container->get($controller)->get($argument->getName())]; } catch (RuntimeException $e) { $what = sprintf('argument $%s of "%s()"', $argument->getName(), $controller); $message = preg_replace('/service "\.service_locator\.[^"]++"/', $what, $e->getMessage()); diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/SessionValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/SessionValueResolver.php index a1e6b431595c3..d8b4fb5c00daa 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/SessionValueResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/SessionValueResolver.php @@ -14,6 +14,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; /** @@ -21,13 +22,15 @@ * * @author Iltar van der Berg */ -final class SessionValueResolver implements ArgumentValueResolverInterface +final class SessionValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface { /** - * {@inheritdoc} + * @deprecated since Symfony 6.2, use resolve() instead */ public function supports(Request $request, ArgumentMetadata $argument): bool { + @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); + if (!$request->hasSession()) { return false; } @@ -43,8 +46,17 @@ public function supports(Request $request, ArgumentMetadata $argument): bool /** * {@inheritdoc} */ - public function resolve(Request $request, ArgumentMetadata $argument): iterable + public function resolve(Request $request, ArgumentMetadata $argument): array { - yield $request->getSession(); + if (!$request->hasSession()) { + return []; + } + + $type = $argument->getType(); + if (SessionInterface::class !== $type && !is_subclass_of($type, SessionInterface::class)) { + return []; + } + + return $request->getSession() instanceof $type ? [$request->getSession()] : []; } } diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/TraceableValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/TraceableValueResolver.php index a84513223b347..b16f8efa5bb4a 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/TraceableValueResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/TraceableValueResolver.php @@ -13,6 +13,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; use Symfony\Component\Stopwatch\Stopwatch; @@ -21,22 +22,28 @@ * * @author Iltar van der Berg */ -final class TraceableValueResolver implements ArgumentValueResolverInterface +final class TraceableValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface { private ArgumentValueResolverInterface $inner; private Stopwatch $stopwatch; - public function __construct(ArgumentValueResolverInterface $inner, Stopwatch $stopwatch) + public function __construct(ArgumentValueResolverInterface|ValueResolverInterface $inner, Stopwatch $stopwatch) { $this->inner = $inner; $this->stopwatch = $stopwatch; } /** - * {@inheritdoc} + * @deprecated since Symfony 6.2, use resolve() instead */ public function supports(Request $request, ArgumentMetadata $argument): bool { + if ($this->inner instanceof ValueResolverInterface) { + return true; + } + + @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); + $method = \get_class($this->inner).'::'.__FUNCTION__; $this->stopwatch->start($method, 'controller.argument_value_resolver'); diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/UidValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/UidValueResolver.php index feafa56f2f65a..c7b1f8a43d266 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/UidValueResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/UidValueResolver.php @@ -13,17 +13,20 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\Uid\AbstractUid; -final class UidValueResolver implements ArgumentValueResolverInterface +final class UidValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface { /** - * {@inheritdoc} + * @deprecated since Symfony 6.2, use resolve() instead */ public function supports(Request $request, ArgumentMetadata $argument): bool { + @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); + return !$argument->isVariadic() && \is_string($request->attributes->get($argument->getName())) && null !== $argument->getType() @@ -33,13 +36,19 @@ public function supports(Request $request, ArgumentMetadata $argument): bool /** * {@inheritdoc} */ - public function resolve(Request $request, ArgumentMetadata $argument): iterable + public function resolve(Request $request, ArgumentMetadata $argument): array { - /** @var class-string $uidClass */ - $uidClass = $argument->getType(); + if ($argument->isVariadic() + || !\is_string($value = $request->attributes->get($argument->getName())) + || null === ($uidClass = $argument->getType()) + || !is_subclass_of($argument->getType(), AbstractUid::class, true) + ) { + return []; + } + /* @var class-string $uidClass */ try { - return [$uidClass::fromString($request->attributes->get($argument->getName()))]; + return [$uidClass::fromString($value)]; } catch (\InvalidArgumentException $e) { throw new NotFoundHttpException(sprintf('The uid for the "%s" parameter is invalid.', $argument->getName()), $e); } diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/VariadicValueResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/VariadicValueResolver.php index a8f7e0f44014e..cf89d4feaf00d 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/VariadicValueResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/VariadicValueResolver.php @@ -13,6 +13,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; /** @@ -20,27 +21,33 @@ * * @author Iltar van der Berg */ -final class VariadicValueResolver implements ArgumentValueResolverInterface +final class VariadicValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface { /** - * {@inheritdoc} + * @deprecated since Symfony 6.2, use resolve() instead */ public function supports(Request $request, ArgumentMetadata $argument): bool { + @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); + return $argument->isVariadic() && $request->attributes->has($argument->getName()); } /** * {@inheritdoc} */ - public function resolve(Request $request, ArgumentMetadata $argument): iterable + public function resolve(Request $request, ArgumentMetadata $argument): array { + if (!$argument->isVariadic() || !$request->attributes->has($argument->getName())) { + return []; + } + $values = $request->attributes->get($argument->getName()); if (!\is_array($values)) { throw new \InvalidArgumentException(sprintf('The action argument "...$%1$s" is required to be an array, the request attribute "%1$s" contains a type of "%2$s" instead.', $argument->getName(), get_debug_type($values))); } - yield from $values; + return $values; } } diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentValueResolverInterface.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentValueResolverInterface.php index 3570bebf3912e..9c3b1a016218a 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentValueResolverInterface.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentValueResolverInterface.php @@ -18,6 +18,8 @@ * Responsible for resolving the value of an argument based on its metadata. * * @author Iltar van der Berg + * + * @deprecated since Symfony 6.2, implement ValueResolverInterface instead */ interface ArgumentValueResolverInterface { diff --git a/src/Symfony/Component/HttpKernel/Controller/ValueResolverInterface.php b/src/Symfony/Component/HttpKernel/Controller/ValueResolverInterface.php new file mode 100644 index 0000000000000..a861705cb7bb6 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Controller/ValueResolverInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Responsible for resolving the value of an argument based on its metadata. + * + * @author Nicolas Grekas + */ +interface ValueResolverInterface +{ + /** + * Returns the possible value(s). + */ + public function resolve(Request $request, ArgumentMetadata $argument): iterable; +} diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/BackedEnumValueResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/BackedEnumValueResolverTest.php index 03fbe6b7678ea..dc0645440826c 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/BackedEnumValueResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/BackedEnumValueResolverTest.php @@ -21,12 +21,18 @@ class BackedEnumValueResolverTest extends TestCase { /** + * In Symfony 7, keep this test case but remove the call to supports(). + * + * @group legacy * @dataProvider provideTestSupportsData */ public function testSupports(Request $request, ArgumentMetadata $metadata, bool $expectedSupport) { $resolver = new BackedEnumValueResolver(); + if (!$expectedSupport) { + $this->assertSame([], $resolver->resolve($request, $metadata)); + } self::assertSame($expectedSupport, $resolver->supports($request, $metadata)); } @@ -76,7 +82,7 @@ public function testResolve(Request $request, ArgumentMetadata $metadata, $expec /** @var \Generator $results */ $results = $resolver->resolve($request, $metadata); - self::assertSame($expected, iterator_to_array($results)); + self::assertSame($expected, $results); } public function provideTestResolveData(): iterable @@ -112,9 +118,7 @@ public function testResolveThrowsNotFoundOnInvalidValue() $this->expectException(NotFoundHttpException::class); $this->expectExceptionMessage('Could not resolve the "Symfony\Component\HttpKernel\Tests\Fixtures\Suit $suit" controller argument: "foo" is not a valid backing value for enum'); - /** @var \Generator $results */ - $results = $resolver->resolve($request, $metadata); - iterator_to_array($results); + $resolver->resolve($request, $metadata); } public function testResolveThrowsOnUnexpectedType() @@ -124,11 +128,9 @@ public function testResolveThrowsOnUnexpectedType() $metadata = self::createArgumentMetadata('suit', Suit::class); $this->expectException(\LogicException::class); - $this->expectExceptionMessage('Could not resolve the "Symfony\Component\HttpKernel\Tests\Fixtures\Suit $suit" controller argument: expecting an int or string, got bool.'); + $this->expectExceptionMessage('Could not resolve the "Symfony\Component\HttpKernel\Tests\Fixtures\Suit $suit" controller argument: expecting an int or string, got "bool".'); - /** @var \Generator $results */ - $results = $resolver->resolve($request, $metadata); - iterator_to_array($results); + $resolver->resolve($request, $metadata); } private static function createRequest(array $attributes = []): Request diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/DateTimeValueResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/DateTimeValueResolverTest.php index e1c3d662c6ece..4d53ec6aff24f 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/DateTimeValueResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/DateTimeValueResolverTest.php @@ -39,6 +39,9 @@ public function getTimeZones() yield ['Etc/GMT-14']; } + /** + * @group legacy + */ public function testSupports() { $resolver = new DateTimeValueResolver(); @@ -56,6 +59,15 @@ public function testSupports() $this->assertFalse($resolver->supports($request, $argument)); } + public function testUnsupportedArgument() + { + $resolver = new DateTimeValueResolver(); + + $argument = new ArgumentMetadata('dummy', \stdClass::class, false, false, null); + $request = self::requestWithAttributes(['dummy' => 'now']); + $this->assertSame([], $resolver->resolve($request, $argument)); + } + /** * @dataProvider getTimeZones */ @@ -67,9 +79,7 @@ public function testFullDate(string $timezone) $argument = new ArgumentMetadata('dummy', \DateTime::class, false, false, null); $request = self::requestWithAttributes(['dummy' => '2012-07-21 00:00:00']); - /** @var \Generator $results */ $results = $resolver->resolve($request, $argument); - $results = iterator_to_array($results); $this->assertCount(1, $results); $this->assertInstanceOf(\DateTime::class, $results[0]); @@ -88,9 +98,7 @@ public function testUnixTimestamp(string $timezone) $argument = new ArgumentMetadata('dummy', \DateTime::class, false, false, null); $request = self::requestWithAttributes(['dummy' => '989541720']); - /** @var \Generator $results */ $results = $resolver->resolve($request, $argument); - $results = iterator_to_array($results); $this->assertCount(1, $results); $this->assertInstanceOf(\DateTime::class, $results[0]); @@ -105,9 +113,7 @@ public function testNullableWithEmptyAttribute() $argument = new ArgumentMetadata('dummy', \DateTime::class, false, false, null, true); $request = self::requestWithAttributes(['dummy' => '']); - /** @var \Generator $results */ $results = $resolver->resolve($request, $argument); - $results = iterator_to_array($results); $this->assertCount(1, $results); $this->assertNull($results[0]); @@ -120,9 +126,7 @@ public function testPreviouslyConvertedAttribute() $argument = new ArgumentMetadata('dummy', \DateTime::class, false, false, null, true); $request = self::requestWithAttributes(['dummy' => $datetime = new \DateTime()]); - /** @var \Generator $results */ $results = $resolver->resolve($request, $argument); - $results = iterator_to_array($results); $this->assertCount(1, $results); $this->assertSame($datetime, $results[0]); @@ -136,9 +140,7 @@ public function testCustomClass() $argument = new ArgumentMetadata('dummy', FooDateTime::class, false, false, null); $request = self::requestWithAttributes(['dummy' => '2016-09-08 00:00:00']); - /** @var \Generator $results */ $results = $resolver->resolve($request, $argument); - $results = iterator_to_array($results); $this->assertCount(1, $results); $this->assertInstanceOf(FooDateTime::class, $results[0]); @@ -156,9 +158,7 @@ public function testDateTimeImmutable(string $timezone) $argument = new ArgumentMetadata('dummy', \DateTimeImmutable::class, false, false, null); $request = self::requestWithAttributes(['dummy' => '2016-09-08 00:00:00 +05:00']); - /** @var \Generator $results */ $results = $resolver->resolve($request, $argument); - $results = iterator_to_array($results); $this->assertCount(1, $results); $this->assertInstanceOf(\DateTimeImmutable::class, $results[0]); @@ -179,9 +179,7 @@ public function testWithFormat(string $timezone) ]); $request = self::requestWithAttributes(['dummy' => '09-08-16 12:34:56']); - /** @var \Generator $results */ $results = $resolver->resolve($request, $argument); - $results = iterator_to_array($results); $this->assertCount(1, $results); $this->assertInstanceOf(\DateTimeImmutable::class, $results[0]); @@ -217,9 +215,7 @@ public function test404Exception(ArgumentMetadata $argument, Request $request) $this->expectException(NotFoundHttpException::class); $this->expectExceptionMessage('Invalid date given for parameter "dummy".'); - /** @var \Generator $results */ - $results = $resolver->resolve($request, $argument); - iterator_to_array($results); + $resolver->resolve($request, $argument); } private static function requestWithAttributes(array $attributes): Request diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/NotTaggedControllerValueResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/NotTaggedControllerValueResolverTest.php index 3cf2f0f18562e..25214afdfa48a 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/NotTaggedControllerValueResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/NotTaggedControllerValueResolverTest.php @@ -20,6 +20,9 @@ class NotTaggedControllerValueResolverTest extends TestCase { + /** + * @group legacy + */ public function testDoSupportWhenControllerDoNotExists() { $resolver = new NotTaggedControllerValueResolver(new ServiceLocator([])); @@ -29,6 +32,11 @@ public function testDoSupportWhenControllerDoNotExists() $this->assertTrue($resolver->supports($request, $argument)); } + /** + * In Symfony 7, keep this test case but remove the call to supports(). + * + * @group legacy + */ public function testDoNotSupportWhenControllerExists() { $resolver = new NotTaggedControllerValueResolver(new ServiceLocator([ @@ -42,15 +50,21 @@ public function testDoNotSupportWhenControllerExists() ])); $argument = new ArgumentMetadata('dummy', \stdClass::class, false, false, null); $request = $this->requestWithAttributes(['_controller' => 'App\\Controller\\Mine::method']); - + $this->assertSame([], $resolver->resolve($request, $argument)); $this->assertFalse($resolver->supports($request, $argument)); } + /** + * In Symfony 7, keep this test case but remove the call to supports(). + * + * @group legacy + */ public function testDoNotSupportEmptyController() { $resolver = new NotTaggedControllerValueResolver(new ServiceLocator([])); $argument = new ArgumentMetadata('dummy', \stdClass::class, false, false, null); $request = $this->requestWithAttributes(['_controller' => '']); + $this->assertSame([], $resolver->resolve($request, $argument)); $this->assertFalse($resolver->supports($request, $argument)); } @@ -61,7 +75,6 @@ public function testController() $resolver = new NotTaggedControllerValueResolver(new ServiceLocator([])); $argument = new ArgumentMetadata('dummy', \stdClass::class, false, false, null); $request = $this->requestWithAttributes(['_controller' => 'App\\Controller\\Mine::method']); - $this->assertTrue($resolver->supports($request, $argument)); $resolver->resolve($request, $argument); } @@ -72,7 +85,6 @@ public function testControllerWithATrailingBackSlash() $resolver = new NotTaggedControllerValueResolver(new ServiceLocator([])); $argument = new ArgumentMetadata('dummy', \stdClass::class, false, false, null); $request = $this->requestWithAttributes(['_controller' => '\\App\\Controller\\Mine::method']); - $this->assertTrue($resolver->supports($request, $argument)); $resolver->resolve($request, $argument); } @@ -83,7 +95,6 @@ public function testControllerWithMethodNameStartUppercase() $resolver = new NotTaggedControllerValueResolver(new ServiceLocator([])); $argument = new ArgumentMetadata('dummy', \stdClass::class, false, false, null); $request = $this->requestWithAttributes(['_controller' => 'App\\Controller\\Mine::Method']); - $this->assertTrue($resolver->supports($request, $argument)); $resolver->resolve($request, $argument); } @@ -94,7 +105,6 @@ public function testControllerNameIsAnArray() $resolver = new NotTaggedControllerValueResolver(new ServiceLocator([])); $argument = new ArgumentMetadata('dummy', \stdClass::class, false, false, null); $request = $this->requestWithAttributes(['_controller' => ['App\\Controller\\Mine', 'method']]); - $this->assertTrue($resolver->supports($request, $argument)); $resolver->resolve($request, $argument); } diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/ServiceValueResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/ServiceValueResolverTest.php index 69b9511c05230..7bc4259439e97 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/ServiceValueResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/ServiceValueResolverTest.php @@ -22,12 +22,18 @@ class ServiceValueResolverTest extends TestCase { + /** + * In Symfony 7, keep this test case but remove the call to supports(). + * + * @group legacy + */ public function testDoNotSupportWhenControllerDoNotExists() { $resolver = new ServiceValueResolver(new ServiceLocator([])); $argument = new ArgumentMetadata('dummy', DummyService::class, false, false, null); $request = $this->requestWithAttributes(['_controller' => 'my_controller']); + $this->assertSame([], $resolver->resolve($request, $argument)); $this->assertFalse($resolver->supports($request, $argument)); } @@ -46,8 +52,7 @@ public function testExistingController() $request = $this->requestWithAttributes(['_controller' => 'App\\Controller\\Mine::method']); $argument = new ArgumentMetadata('dummy', DummyService::class, false, false, null); - $this->assertTrue($resolver->supports($request, $argument)); - $this->assertYieldEquals([new DummyService()], $resolver->resolve($request, $argument)); + $this->assertEquals([new DummyService()], $resolver->resolve($request, $argument)); } public function testExistingControllerWithATrailingBackSlash() @@ -65,8 +70,7 @@ public function testExistingControllerWithATrailingBackSlash() $request = $this->requestWithAttributes(['_controller' => '\\App\\Controller\\Mine::method']); $argument = new ArgumentMetadata('dummy', DummyService::class, false, false, null); - $this->assertTrue($resolver->supports($request, $argument)); - $this->assertYieldEquals([new DummyService()], $resolver->resolve($request, $argument)); + $this->assertEquals([new DummyService()], $resolver->resolve($request, $argument)); } public function testExistingControllerWithMethodNameStartUppercase() @@ -83,8 +87,7 @@ public function testExistingControllerWithMethodNameStartUppercase() $request = $this->requestWithAttributes(['_controller' => 'App\\Controller\\Mine::Method']); $argument = new ArgumentMetadata('dummy', DummyService::class, false, false, null); - $this->assertTrue($resolver->supports($request, $argument)); - $this->assertYieldEquals([new DummyService()], $resolver->resolve($request, $argument)); + $this->assertEquals([new DummyService()], $resolver->resolve($request, $argument)); } public function testControllerNameIsAnArray() @@ -102,8 +105,7 @@ public function testControllerNameIsAnArray() $request = $this->requestWithAttributes(['_controller' => ['App\\Controller\\Mine', 'method']]); $argument = new ArgumentMetadata('dummy', DummyService::class, false, false, null); - $this->assertTrue($resolver->supports($request, $argument)); - $this->assertYieldEquals([new DummyService()], $resolver->resolve($request, $argument)); + $this->assertEquals([new DummyService()], $resolver->resolve($request, $argument)); } public function testErrorIsTruncated() @@ -133,16 +135,6 @@ private function requestWithAttributes(array $attributes) return $request; } - - private function assertYieldEquals(array $expected, \Generator $generator) - { - $args = []; - foreach ($generator as $arg) { - $args[] = $arg; - } - - $this->assertEquals($expected, $args); - } } class DummyService diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/TraceableValueResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/TraceableValueResolverTest.php index 91446c45ab73e..bf5c42f8c2cfa 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/TraceableValueResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/TraceableValueResolverTest.php @@ -20,6 +20,9 @@ class TraceableValueResolverTest extends TestCase { + /** + * @group legacy + */ public function testTimingsInSupports() { $stopwatch = new Stopwatch(); diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/UidValueResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/UidValueResolverTest.php index 64065699c17bc..766c028873fd1 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/UidValueResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/UidValueResolverTest.php @@ -25,10 +25,17 @@ class UidValueResolverTest extends TestCase { /** + * In Symfony 7, keep this test case but remove the call to supports() + * + * @group legacy * @dataProvider provideSupports */ public function testSupports(bool $expected, Request $request, ArgumentMetadata $argument) { + if (!$expected) { + $this->assertSame([], (new UidValueResolver())->resolve($request, $argument)); + } + $this->assertSame($expected, (new UidValueResolver())->supports($request, $argument)); } diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolverTest.php index 1f8b0d68076b5..65ff564f1a90b 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolverTest.php @@ -173,6 +173,9 @@ public function testGetVariadicArgumentsWithoutArrayInRequest() self::$resolver->getArguments($request, $controller); } + /** + * @group legacy + */ public function testGetArgumentWithoutArray() { $this->expectException(\InvalidArgumentException::class); diff --git a/src/Symfony/Component/HttpKernel/composer.json b/src/Symfony/Component/HttpKernel/composer.json index 30bc75c4e05f8..2c92557cbb6fb 100644 --- a/src/Symfony/Component/HttpKernel/composer.json +++ b/src/Symfony/Component/HttpKernel/composer.json @@ -17,6 +17,7 @@ ], "require": { "php": ">=8.1", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/error-handler": "^6.1", "symfony/event-dispatcher": "^5.4|^6.0", "symfony/http-foundation": "^5.4|^6.0", diff --git a/src/Symfony/Component/Security/Http/AccessToken/QueryAccessTokenExtractor.php b/src/Symfony/Component/Security/Http/AccessToken/QueryAccessTokenExtractor.php index 4e5b14aa735b7..ff558904dc2f0 100644 --- a/src/Symfony/Component/Security/Http/AccessToken/QueryAccessTokenExtractor.php +++ b/src/Symfony/Component/Security/Http/AccessToken/QueryAccessTokenExtractor.php @@ -32,8 +32,7 @@ final class QueryAccessTokenExtractor implements AccessTokenExtractorInterface public function __construct( private readonly string $parameter = self::PARAMETER, - ) - { + ) { } public function extractAccessToken(Request $request): ?string diff --git a/src/Symfony/Component/Security/Http/Controller/UserValueResolver.php b/src/Symfony/Component/Security/Http/Controller/UserValueResolver.php index 9b875f1d07b9b..a4c21f7719854 100644 --- a/src/Symfony/Component/Security/Http/Controller/UserValueResolver.php +++ b/src/Symfony/Component/Security/Http/Controller/UserValueResolver.php @@ -13,6 +13,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\Controller\ValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Exception\AccessDeniedException; @@ -24,7 +25,7 @@ * * @author Iltar van der Berg */ -final class UserValueResolver implements ArgumentValueResolverInterface +final class UserValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface { private TokenStorageInterface $tokenStorage; @@ -33,8 +34,13 @@ public function __construct(TokenStorageInterface $tokenStorage) $this->tokenStorage = $tokenStorage; } + /** + * @deprecated since Symfony 6.2, use resolve() instead + */ public function supports(Request $request, ArgumentMetadata $argument): bool { + @trigger_deprecation('symfony/http-kernel', '6.2', 'The "%s()" method is deprecated, use "resolve()" instead.', __METHOD__); + // with the attribute, the type can be any UserInterface implementation // otherwise, the type must be UserInterface if (UserInterface::class !== $argument->getType() && !$argument->getAttributesOfType(CurrentUser::class, ArgumentMetadata::IS_INSTANCEOF)) { @@ -49,19 +55,31 @@ public function supports(Request $request, ArgumentMetadata $argument): bool return true; } - public function resolve(Request $request, ArgumentMetadata $argument): iterable + public function resolve(Request $request, ArgumentMetadata $argument): array { + // with the attribute, the type can be any UserInterface implementation + // otherwise, the type must be UserInterface + if (UserInterface::class !== $argument->getType() && !$argument->getAttributesOfType(CurrentUser::class, ArgumentMetadata::IS_INSTANCEOF)) { + return []; + } $user = $this->tokenStorage->getToken()?->getUser(); + // if no user is present but a default value exists we delegate to DefaultValueResolver + if ($argument->hasDefaultValue() && null === $user) { + return []; + } + if (null === $user) { if (!$argument->isNullable()) { throw new AccessDeniedException(sprintf('There is no logged-in user to pass to $%s, make the argument nullable if you want to allow anonymous access to the action.', $argument->getName())); } - yield null; - } elseif (null === $argument->getType() || $user instanceof ($argument->getType())) { - yield $user; - } else { - throw new AccessDeniedException(sprintf('The logged-in user is an instance of "%s" but a user of type "%s" is expected.', $user::class, $argument->getType())); + + return [null]; } + if (null === $argument->getType() || $user instanceof ($argument->getType())) { + return [$user]; + } + + throw new AccessDeniedException(sprintf('The logged-in user is an instance of "%s" but a user of type "%s" is expected.', $user::class, $argument->getType())); } } diff --git a/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php b/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php index 07f684f467d99..1ce9276d43bc7 100644 --- a/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php @@ -53,7 +53,7 @@ class SwitchUserListener extends AbstractListener private bool $stateless; private ?string $targetUrl; - public function __construct(TokenStorageInterface $tokenStorage, UserProviderInterface $provider, UserCheckerInterface $userChecker, string $firewallName, AccessDecisionManagerInterface $accessDecisionManager, LoggerInterface $logger = null, string $usernameParameter = '_switch_user', string $role = 'ROLE_ALLOWED_TO_SWITCH', EventDispatcherInterface $dispatcher = null, bool $stateless = false, ?string $targetUrl = null) + public function __construct(TokenStorageInterface $tokenStorage, UserProviderInterface $provider, UserCheckerInterface $userChecker, string $firewallName, AccessDecisionManagerInterface $accessDecisionManager, LoggerInterface $logger = null, string $usernameParameter = '_switch_user', string $role = 'ROLE_ALLOWED_TO_SWITCH', EventDispatcherInterface $dispatcher = null, bool $stateless = false, string $targetUrl = null) { if ('' === $firewallName) { throw new \InvalidArgumentException('$firewallName must not be empty.'); diff --git a/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandlerInterface.php b/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandlerInterface.php index 16af51964842c..5edc9b6130b3f 100644 --- a/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandlerInterface.php +++ b/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandlerInterface.php @@ -26,7 +26,7 @@ interface LoginLinkHandlerInterface * * @param int|null $lifetime When not null, the argument overrides any default lifetime previously set */ - public function createLoginLink(UserInterface $user, Request $request = null /*, int $lifetime = null */): LoginLinkDetails; + public function createLoginLink(UserInterface $user, Request $request = null /* , int $lifetime = null */): LoginLinkDetails; /** * Validates if this request contains a login link and returns the associated User. diff --git a/src/Symfony/Component/Security/Http/Tests/Authenticator/JsonLoginAuthenticatorTest.php b/src/Symfony/Component/Security/Http/Tests/Authenticator/JsonLoginAuthenticatorTest.php index 150f54a8581ae..ad3f75353731a 100644 --- a/src/Symfony/Component/Security/Http/Tests/Authenticator/JsonLoginAuthenticatorTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Authenticator/JsonLoginAuthenticatorTest.php @@ -146,7 +146,6 @@ public function provideEmptyAuthenticateData() { $request = new Request([], [], [], [], [], ['HTTP_CONTENT_TYPE' => 'application/json'], '{"username": "", "password": "notempty"}'); yield [$request]; - } public function testAuthenticationFailureWithoutTranslator() diff --git a/src/Symfony/Component/Security/Http/Tests/Controller/UserValueResolverTest.php b/src/Symfony/Component/Security/Http/Tests/Controller/UserValueResolverTest.php index 09bbe637322fd..4ee4c0a07d46e 100644 --- a/src/Symfony/Component/Security/Http/Tests/Controller/UserValueResolverTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Controller/UserValueResolverTest.php @@ -27,21 +27,33 @@ class UserValueResolverTest extends TestCase { + /** + * In Symfony 7, keep this test case but remove the call to supports() + * + * @group legacy + */ public function testSupportsFailsWithNoType() { $tokenStorage = new TokenStorage(); $resolver = new UserValueResolver($tokenStorage); $metadata = new ArgumentMetadata('foo', null, false, false, null); + $this->assertSame([], $resolver->resolve(Request::create('/'), $metadata)); $this->assertFalse($resolver->supports(Request::create('/'), $metadata)); } + /** + * In Symfony 7, keep this test case but remove the call to supports() + * + * @group legacy + */ public function testSupportsFailsWhenDefaultValAndNoUser() { $tokenStorage = new TokenStorage(); $resolver = new UserValueResolver($tokenStorage); $metadata = new ArgumentMetadata('foo', UserInterface::class, false, true, new InMemoryUser('username', 'password')); + $this->assertSame([], $resolver->resolve(Request::create('/'), $metadata)); $this->assertFalse($resolver->supports(Request::create('/'), $metadata)); } @@ -55,8 +67,7 @@ public function testResolveSucceedsWithUserInterface() $resolver = new UserValueResolver($tokenStorage); $metadata = new ArgumentMetadata('foo', UserInterface::class, false, false, null); - $this->assertTrue($resolver->supports(Request::create('/'), $metadata)); - $this->assertSame([$user], iterator_to_array($resolver->resolve(Request::create('/'), $metadata))); + $this->assertSame([$user], $resolver->resolve(Request::create('/'), $metadata)); } public function testResolveSucceedsWithSubclassType() @@ -69,8 +80,7 @@ public function testResolveSucceedsWithSubclassType() $resolver = new UserValueResolver($tokenStorage); $metadata = new ArgumentMetadata('foo', InMemoryUser::class, false, false, null, false, [new CurrentUser()]); - $this->assertTrue($resolver->supports(Request::create('/'), $metadata)); - $this->assertSame([$user], iterator_to_array($resolver->resolve(Request::create('/'), $metadata))); + $this->assertSame([$user], $resolver->resolve(Request::create('/'), $metadata)); } public function testResolveSucceedsWithNullableParamAndNoUser() @@ -82,8 +92,7 @@ public function testResolveSucceedsWithNullableParamAndNoUser() $resolver = new UserValueResolver($tokenStorage); $metadata = new ArgumentMetadata('foo', InMemoryUser::class, false, false, null, true, [new CurrentUser()]); - $this->assertTrue($resolver->supports(Request::create('/'), $metadata)); - $this->assertSame([null], iterator_to_array($resolver->resolve(Request::create('/'), $metadata))); + $this->assertSame([null], $resolver->resolve(Request::create('/'), $metadata)); } public function testResolveSucceedsWithNullableAttribute() @@ -97,8 +106,7 @@ public function testResolveSucceedsWithNullableAttribute() $metadata = $this->createMock(ArgumentMetadata::class); $metadata = new ArgumentMetadata('foo', null, false, false, null, false, [new CurrentUser()]); - $this->assertTrue($resolver->supports(Request::create('/'), $metadata)); - $this->assertSame([$user], iterator_to_array($resolver->resolve(Request::create('/'), $metadata))); + $this->assertSame([$user], $resolver->resolve(Request::create('/'), $metadata)); } public function testResolveSucceedsWithTypedAttribute() @@ -112,8 +120,7 @@ public function testResolveSucceedsWithTypedAttribute() $metadata = $this->createMock(ArgumentMetadata::class); $metadata = new ArgumentMetadata('foo', InMemoryUser::class, false, false, null, false, [new CurrentUser()]); - $this->assertTrue($resolver->supports(Request::create('/'), $metadata)); - $this->assertSame([$user], iterator_to_array($resolver->resolve(Request::create('/'), $metadata))); + $this->assertSame([$user], $resolver->resolve(Request::create('/'), $metadata)); } public function testResolveThrowsAccessDeniedWithWrongUserClass() @@ -126,10 +133,9 @@ public function testResolveThrowsAccessDeniedWithWrongUserClass() $resolver = new UserValueResolver($tokenStorage); $metadata = new ArgumentMetadata('foo', InMemoryUser::class, false, false, null, false, [new CurrentUser()]); - $this->assertTrue($resolver->supports(Request::create('/'), $metadata)); $this->expectException(AccessDeniedException::class); $this->expectExceptionMessageMatches('/^The logged-in user is an instance of "Mock_UserInterface[^"]+" but a user of type "Symfony\\\\Component\\\\Security\\\\Core\\\\User\\\\InMemoryUser" is expected.$/'); - iterator_to_array($resolver->resolve(Request::create('/'), $metadata)); + $resolver->resolve(Request::create('/'), $metadata); } public function testResolveThrowsAccessDeniedWithAttributeAndNoUser() @@ -139,10 +145,9 @@ public function testResolveThrowsAccessDeniedWithAttributeAndNoUser() $resolver = new UserValueResolver($tokenStorage); $metadata = new ArgumentMetadata('foo', UserInterface::class, false, false, null, false, [new CurrentUser()]); - $this->assertTrue($resolver->supports(Request::create('/'), $metadata)); $this->expectException(AccessDeniedException::class); $this->expectExceptionMessage('There is no logged-in user to pass to $foo, make the argument nullable if you want to allow anonymous access to the action.'); - iterator_to_array($resolver->resolve(Request::create('/'), $metadata)); + $resolver->resolve(Request::create('/'), $metadata); } public function testResolveThrowsAcessDeniedWithNoToken() @@ -151,9 +156,8 @@ public function testResolveThrowsAcessDeniedWithNoToken() $resolver = new UserValueResolver($tokenStorage); $metadata = new ArgumentMetadata('foo', UserInterface::class, false, false, null); - $this->assertTrue($resolver->supports(Request::create('/'), $metadata)); $this->expectException(AccessDeniedException::class); - iterator_to_array($resolver->resolve(Request::create('/'), $metadata)); + $resolver->resolve(Request::create('/'), $metadata); } public function testIntegration() diff --git a/src/Symfony/Component/Security/Http/composer.json b/src/Symfony/Component/Security/Http/composer.json index 4d7a35f724947..66876a3e38e4b 100644 --- a/src/Symfony/Component/Security/Http/composer.json +++ b/src/Symfony/Component/Security/Http/composer.json @@ -17,6 +17,7 @@ ], "require": { "php": ">=8.1", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/security-core": "^6.0", "symfony/http-foundation": "^5.4|^6.0", "symfony/http-kernel": "^6.2",