diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 4180ef7d07..096e27786d 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -15157,12 +15157,12 @@ parameters: - message: "#^Cannot access offset int\\|string on Ibexa\\\\Contracts\\\\Core\\\\Persistence\\\\Content\\\\Field\\.$#" - count: 3 + count: 1 path: src/lib/Persistence/Legacy/Content/FieldHandler.php - message: "#^Cannot access offset string on Ibexa\\\\Contracts\\\\Core\\\\Persistence\\\\Content\\\\Field\\.$#" - count: 4 + count: 2 path: src/lib/Persistence/Legacy/Content/FieldHandler.php - @@ -20400,11 +20400,6 @@ parameters: count: 1 path: src/lib/Repository/Mapper/ContentDomainMapper.php - - - message: "#^Cannot access offset mixed on iterable\\\\.$#" - count: 1 - path: src/lib/Repository/Mapper/ContentDomainMapper.php - - message: "#^Cannot call method error\\(\\) on Psr\\\\Log\\\\LoggerInterface\\|null\\.$#" count: 1 @@ -58395,11 +58390,6 @@ parameters: count: 3 path: tests/lib/Repository/Service/Mock/ContentTest.php - - - message: "#^Cannot access offset mixed on Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Values\\\\Content\\\\Field\\.$#" - count: 1 - path: tests/lib/Repository/Service/Mock/ContentTest.php - - message: "#^Cannot access offset string on Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Values\\\\Content\\\\Field\\.$#" count: 1 diff --git a/src/bundle/Core/ControllerArgumentResolver/LocationArgumentResolver.php b/src/bundle/Core/ControllerArgumentResolver/LocationArgumentResolver.php new file mode 100644 index 0000000000..fe4dd8d226 --- /dev/null +++ b/src/bundle/Core/ControllerArgumentResolver/LocationArgumentResolver.php @@ -0,0 +1,59 @@ +locationService = $locationService; + } + + public function supports(Request $request, ArgumentMetadata $argument): bool + { + return + Location::class === $argument->getType() + && !$request->attributes->has(self::PARAMETER_LOCATION_ID) + && $request->query->has(self::PARAMETER_LOCATION_ID); + } + + /** + * @return iterable<\Ibexa\Contracts\Core\Repository\Values\Content\Location> + * + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException + */ + public function resolve(Request $request, ArgumentMetadata $argument): iterable + { + $locationId = $request->query->get(self::PARAMETER_LOCATION_ID); + if (!is_numeric($locationId)) { + throw new InvalidArgumentException( + 'locationId', + 'Expected numeric type, ' . get_debug_type($locationId) . ' given.' + ); + } + + yield $this->locationService->loadLocation((int)$locationId); + } +} diff --git a/src/bundle/Core/Resources/config/services.yml b/src/bundle/Core/Resources/config/services.yml index c3c6cf156c..e65c582af2 100644 --- a/src/bundle/Core/Resources/config/services.yml +++ b/src/bundle/Core/Resources/config/services.yml @@ -195,6 +195,12 @@ services: tags: - { name: request.param_converter, priority: '%ibexa.param_converter.location.priority%', converter: ez_location_converter } + Ibexa\Bundle\Core\ControllerArgumentResolver\LocationArgumentResolver: + autowire: true + autoconfigure: true + tags: + - { name: controller.argument_value_resolver, priority: 50 } + Ibexa\Bundle\Core\EventListener\ExceptionListener: class: Ibexa\Bundle\Core\EventListener\ExceptionListener arguments: ["@translator"] @@ -361,5 +367,5 @@ services: $repositoryConfigurationProvider: '@Ibexa\Bundle\Core\ApiLoader\RepositoryConfigurationProvider' $defaultConnection: '%doctrine.default_connection%' $entityManagers: '%doctrine.entity_managers%' - + Ibexa\Bundle\Core\Translation\Policy\PolicyTranslationDefinitionProvider: ~ diff --git a/tests/bundle/Core/ControllerArgumentResolver/LocationArgumentResolverTest.php b/tests/bundle/Core/ControllerArgumentResolver/LocationArgumentResolverTest.php new file mode 100644 index 0000000000..36a36c0bdc --- /dev/null +++ b/tests/bundle/Core/ControllerArgumentResolver/LocationArgumentResolverTest.php @@ -0,0 +1,153 @@ +createMock(LocationService::class); + $this->locationArgumentResolver = new LocationArgumentResolver($locationService); + } + + /** + * @dataProvider provideDataForTestSupports + */ + public function testSupports( + bool $expected, + Request $request, + ArgumentMetadata $argumentMetadata + ): void { + self::assertSame( + $expected, + $this->locationArgumentResolver->supports( + $request, + $argumentMetadata + ) + ); + } + + public function testResolveThrowsInvalidArgumentException(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Argument \'locationId\' is invalid: Expected numeric type, string given.'); + + $generator = $this->locationArgumentResolver->resolve( + new Request( + [ + 'locationId' => 'foo', + ] + ), + $this->createMock(ArgumentMetadata::class) + ); + + self::assertInstanceOf(Generator::class, $generator); + + $generator->getReturn(); + } + + public function testResolve(): void + { + $resolvedArgumentsGenerator = $this->locationArgumentResolver->resolve( + $this->createRequest(true, false, 1), + $this->createMock(ArgumentMetadata::class) + ); + + self::assertInstanceOf(Generator::class, $resolvedArgumentsGenerator); + $resolvedArguments = iterator_to_array($resolvedArgumentsGenerator); + + self::assertCount(1, $resolvedArguments); + + $value = current($resolvedArguments); + self::assertInstanceOf( + Location::class, + $value + ); + } + + /** + * @return iterable + */ + public function provideDataForTestSupports(): iterable + { + $locationBasedArgumentMetadata = $this->createArgumentMetadata(Location::class); + + yield 'Supported - locationId passed to request query' => [ + true, + $this->createRequest(true, false, 1), + $locationBasedArgumentMetadata, + ]; + + yield 'Not supported - type different than Ibexa\Contracts\Core\Repository\Values\Content\Location' => [ + false, + $this->createRequest(true, false, 1), + $this->createArgumentMetadata('foo'), + ]; + + yield 'Not supported - locationId passed to request attributes' => [ + false, + $this->createRequest(false, true, 1), + $locationBasedArgumentMetadata, + ]; + + yield 'Not supported - locationId passed to request attributes and query' => [ + false, + $this->createRequest(true, true, 1), + $locationBasedArgumentMetadata, + ]; + } + + private function createArgumentMetadata(string $type): ArgumentMetadata + { + $argumentMetadata = $this->createMock(ArgumentMetadata::class); + $argumentMetadata + ->method('getType') + ->willReturn($type); + + return $argumentMetadata; + } + + private function createRequest( + bool $addToQuery, + bool $addToAttributes, + ?int $locationId = null + ): Request { + $request = Request::create('/'); + + if ($addToQuery) { + $request->query->set(self::PARAMETER_LOCATION_ID, $locationId); + } + + if ($addToAttributes) { + $request->attributes->set(self::PARAMETER_LOCATION_ID, $locationId); + } + + return $request; + } +}