From eadd8ea98bcae7ec35ab1cf396eb703ee9b7e260 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Mon, 13 Apr 2026 13:03:01 +0200 Subject: [PATCH] Support intersection types in param fetcher --- EventListener/ParamFetcherListener.php | 14 ++++++---- .../ParamFetcherListenerTest.php | 20 ++++++++++++- .../ParamFetcherDnfTypeController.php | 28 +++++++++++++++++++ ...ParamFetcherIntersectionTypeController.php | 28 +++++++++++++++++++ ...FetcherIntersectionTypeMarkerInterface.php | 16 +++++++++++ 5 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 Tests/Fixtures/Controller/ParamFetcherDnfTypeController.php create mode 100644 Tests/Fixtures/Controller/ParamFetcherIntersectionTypeController.php create mode 100644 Tests/Fixtures/Controller/ParamFetcherIntersectionTypeMarkerInterface.php diff --git a/EventListener/ParamFetcherListener.php b/EventListener/ParamFetcherListener.php index e09d72bdb..a1962f443 100644 --- a/EventListener/ParamFetcherListener.php +++ b/EventListener/ParamFetcherListener.php @@ -86,14 +86,16 @@ private function isParamFetcherType(\ReflectionParameter $controllerParam): bool { $type = $controllerParam->getType(); foreach ($type instanceof \ReflectionUnionType ? $type->getTypes() : [$type] as $type) { - if (null === $type || $type->isBuiltin() || !$type instanceof \ReflectionNamedType) { - continue; - } + foreach ($type instanceof \ReflectionIntersectionType ? $type->getTypes() : [$type] as $type) { + if (!$type instanceof \ReflectionNamedType || $type->isBuiltin()) { + continue; + } - $class = new \ReflectionClass($type->getName()); + $class = new \ReflectionClass($type->getName()); - if ($class->implementsInterface(ParamFetcherInterface::class)) { - return true; + if ($class->implementsInterface(ParamFetcherInterface::class)) { + return true; + } } } diff --git a/Tests/EventListener/ParamFetcherListenerTest.php b/Tests/EventListener/ParamFetcherListenerTest.php index 6aae8e27b..6a6f02533 100644 --- a/Tests/EventListener/ParamFetcherListenerTest.php +++ b/Tests/EventListener/ParamFetcherListenerTest.php @@ -17,6 +17,8 @@ use FOS\RestBundle\Request\ParamFetcher; use FOS\RestBundle\Request\ParamReaderInterface; use FOS\RestBundle\Tests\Fixtures\Controller\ParamFetcherController; +use FOS\RestBundle\Tests\Fixtures\Controller\ParamFetcherDnfTypeController; +use FOS\RestBundle\Tests\Fixtures\Controller\ParamFetcherIntersectionTypeController; use FOS\RestBundle\Tests\Fixtures\Controller\ParamFetcherUnionTypeController; use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -141,6 +143,14 @@ public function setParamFetcherByTypehintProvider() $paramFetcher[] = ['byUnionTypeAction', 'pfu']; } + if (\PHP_VERSION_ID >= 80100) { + $paramFetcher[] = ['byIntersectionTypeAction', 'pfi']; + } + + if (\PHP_VERSION_ID >= 80200) { + $paramFetcher[] = ['byDnfTypeAction', 'pfd']; + } + return $paramFetcher; } @@ -148,7 +158,15 @@ protected function getEvent(Request $request, $actionMethod = 'byNameAction') { $this->requestStack->push($request); - $controller = \PHP_VERSION_ID < 80000 ? new ParamFetcherController() : new ParamFetcherUnionTypeController(); + if (\PHP_VERSION_ID >= 80200) { + $controller = new ParamFetcherDnfTypeController(); + } elseif (\PHP_VERSION_ID >= 80100) { + $controller = new ParamFetcherIntersectionTypeController(); + } elseif (\PHP_VERSION_ID >= 80000) { + $controller = new ParamFetcherUnionTypeController(); + } else { + $controller = new ParamFetcherController(); + } $callable = $actionMethod ? [$controller, $actionMethod] : $controller; $kernel = $this->createMock(HttpKernelInterface::class); diff --git a/Tests/Fixtures/Controller/ParamFetcherDnfTypeController.php b/Tests/Fixtures/Controller/ParamFetcherDnfTypeController.php new file mode 100644 index 000000000..f7f1ca18f --- /dev/null +++ b/Tests/Fixtures/Controller/ParamFetcherDnfTypeController.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 FOS\RestBundle\Tests\Fixtures\Controller; + +use FOS\RestBundle\Request\ParamFetcherInterface; + +/** + * Fixture for testing whether the ParamFetcher can be injected into + * a disjunctive normal form type-hinted controller method. + */ +class ParamFetcherDnfTypeController extends ParamFetcherIntersectionTypeController +{ + /** + * Make sure the ParamFetcher can be injected according to the DNF typehint. + */ + public function byDnfTypeAction((ParamFetcherInterface&ParamFetcherIntersectionTypeMarkerInterface)|null $pfd) + { + } +} diff --git a/Tests/Fixtures/Controller/ParamFetcherIntersectionTypeController.php b/Tests/Fixtures/Controller/ParamFetcherIntersectionTypeController.php new file mode 100644 index 000000000..aacecce83 --- /dev/null +++ b/Tests/Fixtures/Controller/ParamFetcherIntersectionTypeController.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 FOS\RestBundle\Tests\Fixtures\Controller; + +use FOS\RestBundle\Request\ParamFetcherInterface; + +/** + * Fixture for testing whether the ParamFetcher can be injected into + * an intersection type-hinted controller method. + */ +class ParamFetcherIntersectionTypeController extends ParamFetcherUnionTypeController +{ + /** + * Make sure the ParamFetcher can be injected according to the intersection typehint. + */ + public function byIntersectionTypeAction(ParamFetcherInterface&ParamFetcherIntersectionTypeMarkerInterface $pfi) + { + } +} diff --git a/Tests/Fixtures/Controller/ParamFetcherIntersectionTypeMarkerInterface.php b/Tests/Fixtures/Controller/ParamFetcherIntersectionTypeMarkerInterface.php new file mode 100644 index 000000000..0222af819 --- /dev/null +++ b/Tests/Fixtures/Controller/ParamFetcherIntersectionTypeMarkerInterface.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FOS\RestBundle\Tests\Fixtures\Controller; + +interface ParamFetcherIntersectionTypeMarkerInterface +{ +}