diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 864fc5115..7bf52337a 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -18,38 +18,54 @@ jobs: continue-on-error: ${{ matrix.can-fail }} strategy: + fail-fast: false matrix: include: - - php-version: 7.2 - composer-flags: "--prefer-lowest " + - php-version: "7.4" + composer-flags: "--prefer-lowest" + symfony-require: "5.4.*" can-fail: false - - php-version: 7.3 + - php-version: "7.4" composer-flags: "" can-fail: false - - php-version: 7.4 + - php-version: "7.4" composer-flags: "" symfony-require: "5.4.*" can-fail: false coverage: yes - - php-version: 8.0 + - php-version: "8.0" + composer-flags: "" + can-fail: false + symfony-require: "5.4.*" + - php-version: "8.1" + composer-flags: "" + can-fail: false + symfony-require: "6.4.*" + - php-version: "8.2" composer-flags: "" can-fail: false - symfony-require: "6.0.*" - - php-version: 8.1 + symfony-require: "6.4.*" + - php-version: "8.3" composer-flags: "" can-fail: false - symfony-require: "6.1.*" - - php-version: 8.2 + symfony-require: "6.4.*" + - php-version: "8.3" composer-flags: "" can-fail: false - symfony-require: "6.2.*" - - php-version: 8.2 + symfony-require: "6.4.*" + remove-sensio-bundle: yes # Smoke test with SensioFrameworkExtraBundle removed on latest Symfony LTS + - php-version: "8.2" composer-flags: "" can-fail: false - symfony-require: "6.3.*" - - php-version: 8.2 + symfony-require: "7.0.*" + remove-sensio-bundle: yes # SensioFrameworkExtraBundle is not compatible with Symfony 7.0 or later + - php-version: "8.3" composer-flags: "" can-fail: true # we don't want to fail the build if we are incompatible with the next (unstable) Symfony version + remove-sensio-bundle: yes # SensioFrameworkExtraBundle is not compatible with Symfony 7.0 or later + + env: + COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: "Checkout" @@ -78,11 +94,17 @@ jobs: key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}" restore-keys: "php-${{ matrix.php-version }}-composer-locked-" + - name: "Remove SensioFrameworkExtraBundle if required" + if: "${{ matrix.remove-sensio-bundle == 'yes' }}" + env: + SYMFONY_REQUIRE: "${{ matrix.symfony-require }}" + run: | + composer remove --no-update --dev sensio/framework-extra-bundle + - name: "Install dependencies with composer" env: SYMFONY_REQUIRE: "${{ matrix.symfony-require }}" run: | - composer remove friendsofphp/php-cs-fixer --dev --no-update composer update --no-interaction --no-progress ${{ matrix.composer-flags }} - name: "Run PHPUnit" diff --git a/Controller/Annotations/FileParam.php b/Controller/Annotations/FileParam.php index 33b7fe8c2..55e624db3 100644 --- a/Controller/Annotations/FileParam.php +++ b/Controller/Annotations/FileParam.php @@ -22,11 +22,11 @@ * * @Annotation * @NamedArgumentConstructor - * @Target("METHOD") + * @Target({"CLASS", "METHOD"}) * * @author Ener-Getick */ -#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)] +#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] class FileParam extends AbstractParam { /** @var bool */ diff --git a/Controller/Annotations/RequestParam.php b/Controller/Annotations/RequestParam.php index 34252149b..2d47e9073 100644 --- a/Controller/Annotations/RequestParam.php +++ b/Controller/Annotations/RequestParam.php @@ -18,12 +18,12 @@ * * @Annotation * @NamedArgumentConstructor - * @Target("METHOD") + * @Target({"CLASS", "METHOD"}) * * @author Jordi Boggiano * @author Boris Guéry */ -#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_METHOD)] +#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] class RequestParam extends AbstractScalarParam { /** @var bool */ diff --git a/Controller/Annotations/Route.php b/Controller/Annotations/Route.php index 677e0979c..3e52e72bd 100644 --- a/Controller/Annotations/Route.php +++ b/Controller/Annotations/Route.php @@ -11,7 +11,28 @@ namespace FOS\RestBundle\Controller\Annotations; -use Symfony\Component\Routing\Annotation\Route as BaseRoute; +use Symfony\Component\Routing\Annotation\Route as BaseAnnotationRoute; +use Symfony\Component\Routing\Attribute\Route as BaseAttributeRoute; + +if (class_exists(BaseAttributeRoute::class)) { + /** + * Compatibility layer for Symfony 6.4 and later. + * + * @internal + */ + class CompatRoute extends BaseAttributeRoute + { + } +} else { + /** + * Compatibility layer for Symfony 6.3 and earlier. + * + * @internal + */ + class CompatRoute extends BaseAnnotationRoute + { + } +} /** * Route annotation class. @@ -21,8 +42,17 @@ * @Target({"CLASS", "METHOD"}) */ #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] -class Route extends BaseRoute +class Route extends CompatRoute { + /** + * @param array|string $data + * @param array|string|null $path + * @param string[] $requirements + * @param string[]|string $methods + * @param string[]|string $schemes + * + * @throws \TypeError if the $data argument is an unsupported type + */ public function __construct( $data = [], $path = null, @@ -41,78 +71,56 @@ public function __construct( bool $stateless = null, string $env = null ) { - // BC layer for symfony < 5.2 - // Before symfony/routing 5.2 the constructor only had one parameter - $method = new \ReflectionMethod(BaseRoute::class, '__construct'); - if (1 === $method->getNumberOfParameters()) { + // Use Reflection to get the constructor from the parent class two levels up (accounting for our compat definition) + $method = (new \ReflectionClass($this))->getParentClass()->getParentClass()->getMethod('__construct'); + + // The $data constructor parameter was removed in Symfony 6.0 in favor of named arguments + if ('data' === $method->getParameters()[0]->getName()) { + parent::__construct( + $data, + $path, + $name, + $requirements, + $options, + $defaults, + $host, + $methods, + $schemes, + $condition, + $priority, + $locale, + $format, + $utf8, + $stateless, + $env + ); + } else { if (\is_string($data)) { - $path = $data; - $data = []; + $data = ['path' => $data]; } elseif (!\is_array($data)) { throw new \TypeError(sprintf('"%s": Argument $data is expected to be a string or array, got "%s".', __METHOD__, get_debug_type($data))); + } elseif (0 !== count($data) && [] === \array_intersect(\array_keys($data), ['path', 'name', 'requirements', 'options', 'defaults', 'host', 'methods', 'schemes', 'condition', 'priority', 'locale', 'format', 'utf8', 'stateless', 'env'])) { + $localizedPaths = $data; + $data = ['path' => $localizedPaths]; } - $data['path'] = $path; - $data['name'] = $name; - $data['requirements'] = $requirements; - $data['options'] = $options; - $data['defaults'] = $defaults; - $data['host'] = $host; - $data['methods'] = $methods; - $data['schemes'] = $schemes; - $data['condition'] = $condition; - - parent::__construct($data); - } else { - // BC layer for symfony < 6.0 - // The constructor parameter $data has been removed since symfony 6.0 - if ('data' === $method->getParameters()[0]->getName()) { - parent::__construct( - $data, - $path, - $name, - $requirements, - $options, - $defaults, - $host, - $methods, - $schemes, - $condition, - $priority, - $locale, - $format, - $utf8, - $stateless, - $env - ); - } else { - if (\is_string($data)) { - $data = ['path' => $data]; - } elseif (!\is_array($data)) { - throw new \TypeError(sprintf('"%s": Argument $data is expected to be a string or array, got "%s".', __METHOD__, get_debug_type($data))); - } elseif (0 !== count($data) && [] === \array_intersect(\array_keys($data), ['path', 'name', 'requirements', 'options', 'defaults', 'host', 'methods', 'schemes', 'condition', 'priority', 'locale', 'format', 'utf8', 'stateless', 'env'])) { - $localizedPaths = $data; - $data = ['path' => $localizedPaths]; - } - - parent::__construct( - $data['path'] ?? $path, - $data['name'] ?? $name, - $data['requirements'] ?? $requirements, - $data['options'] ?? $options, - $data['defaults'] ?? $defaults, - $data['host'] ?? $host, - $data['methods'] ?? $methods, - $data['schemes'] ?? $schemes, - $data['condition'] ?? $condition, - $data['priority'] ?? $priority, - $data['locale'] ?? $locale, - $data['format'] ?? $format, - $data['utf8'] ?? $utf8, - $data['stateless'] ?? $stateless, - $data['env'] ?? $env - ); - } + parent::__construct( + $data['path'] ?? $path, + $data['name'] ?? $name, + $data['requirements'] ?? $requirements, + $data['options'] ?? $options, + $data['defaults'] ?? $defaults, + $data['host'] ?? $host, + $data['methods'] ?? $methods, + $data['schemes'] ?? $schemes, + $data['condition'] ?? $condition, + $data['priority'] ?? $priority, + $data['locale'] ?? $locale, + $data['format'] ?? $format, + $data['utf8'] ?? $utf8, + $data['stateless'] ?? $stateless, + $data['env'] ?? $env + ); } if (!$this->getMethods()) { diff --git a/DependencyInjection/FOSRestExtension.php b/DependencyInjection/FOSRestExtension.php index 8d54fbcad..4b6ff5cad 100644 --- a/DependencyInjection/FOSRestExtension.php +++ b/DependencyInjection/FOSRestExtension.php @@ -14,6 +14,7 @@ use FOS\RestBundle\ErrorRenderer\SerializerErrorRenderer; use FOS\RestBundle\EventListener\ResponseStatusCodeListener; use FOS\RestBundle\View\ViewHandler; +use Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\ChildDefinition; @@ -21,6 +22,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\Form\Extension\Core\Type\FormType; @@ -31,13 +33,13 @@ use Symfony\Component\HttpFoundation\RequestMatcher\MethodRequestMatcher; use Symfony\Component\HttpFoundation\RequestMatcher\PathRequestMatcher; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\DependencyInjection\Extension; +use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension; use Symfony\Component\Validator\Constraint; /** * @internal */ -class FOSRestExtension extends Extension +class FOSRestExtension extends ConfigurableExtension { /** * {@inheritdoc} @@ -47,18 +49,15 @@ public function getConfiguration(array $config, ContainerBuilder $container): Co return new Configuration($container->getParameter('kernel.debug')); } - public function load(array $configs, ContainerBuilder $container): void + protected function loadInternal(array $mergedConfig, ContainerBuilder $container): void { - $configuration = new Configuration($container->getParameter('kernel.debug')); - $config = $this->processConfiguration($configuration, $configs); - $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('view.xml'); $loader->load('request.xml'); $loader->load('serializer.xml'); - foreach ($config['service'] as $key => $service) { - if ('validator' === $service && empty($config['body_converter']['validate'])) { + foreach ($mergedConfig['service'] as $key => $service) { + if ('validator' === $service && empty($mergedConfig['body_converter']['validate'])) { continue; } @@ -71,20 +70,20 @@ public function load(array $configs, ContainerBuilder $container): void } } - $this->loadForm($config, $loader, $container); - $this->loadException($config, $loader, $container); - $this->loadBodyConverter($config, $loader, $container); - $this->loadView($config, $loader, $container); + $this->loadForm($mergedConfig, $loader, $container); + $this->loadException($mergedConfig, $loader, $container); + $this->loadBodyConverter($mergedConfig, $loader, $container); + $this->loadView($mergedConfig, $loader, $container); - $this->loadBodyListener($config, $loader, $container); - $this->loadFormatListener($config, $loader, $container); - $this->loadVersioning($config, $loader, $container); - $this->loadParamFetcherListener($config, $loader, $container); - $this->loadAllowedMethodsListener($config, $loader, $container); - $this->loadZoneMatcherListener($config, $loader, $container); + $this->loadBodyListener($mergedConfig, $loader, $container); + $this->loadFormatListener($mergedConfig, $loader, $container); + $this->loadVersioning($mergedConfig, $loader, $container); + $this->loadParamFetcherListener($mergedConfig, $loader, $container); + $this->loadAllowedMethodsListener($mergedConfig, $loader, $container); + $this->loadZoneMatcherListener($mergedConfig, $loader, $container); // Needs RequestBodyParamConverter and View Handler loaded. - $this->loadSerializer($config, $container); + $this->loadSerializer($mergedConfig, $container); } private function loadForm(array $config, XmlFileLoader $loader, ContainerBuilder $container): void @@ -100,7 +99,7 @@ private function loadForm(array $config, XmlFileLoader $loader, ContainerBuilder private function loadAllowedMethodsListener(array $config, XmlFileLoader $loader, ContainerBuilder $container): void { - if ($config['allowed_methods_listener']['enabled']) { + if ($this->isConfigEnabled($container, $config['allowed_methods_listener'])) { if (!empty($config['allowed_methods_listener']['service'])) { $service = $container->getDefinition('fos_rest.allowed_methods_listener'); $service->clearTag('kernel.event_listener'); @@ -114,7 +113,7 @@ private function loadAllowedMethodsListener(array $config, XmlFileLoader $loader private function loadBodyListener(array $config, XmlFileLoader $loader, ContainerBuilder $container): void { - if ($config['body_listener']['enabled']) { + if ($this->isConfigEnabled($container, $config['body_listener'])) { $loader->load('body_listener.xml'); $service = $container->getDefinition('fos_rest.body_listener'); @@ -149,7 +148,7 @@ private function loadBodyListener(array $config, XmlFileLoader $loader, Containe private function loadFormatListener(array $config, XmlFileLoader $loader, ContainerBuilder $container): void { - if ($config['format_listener']['enabled'] && !empty($config['format_listener']['rules'])) { + if ($this->isConfigEnabled($container, $config['format_listener']) && !empty($config['format_listener']['rules'])) { $loader->load('format_listener.xml'); if (!empty($config['format_listener']['service'])) { @@ -166,22 +165,22 @@ private function loadFormatListener(array $config, XmlFileLoader $loader, Contai private function loadVersioning(array $config, XmlFileLoader $loader, ContainerBuilder $container): void { - if (!empty($config['versioning']['enabled'])) { + if ($this->isConfigEnabled($container, $config['versioning'])) { $loader->load('versioning.xml'); $versionListener = $container->getDefinition('fos_rest.versioning.listener'); $versionListener->replaceArgument(1, $config['versioning']['default_version']); $resolvers = []; - if ($config['versioning']['resolvers']['query']['enabled']) { + if ($this->isConfigEnabled($container, $config['versioning']['resolvers']['query'])) { $resolvers['query'] = $container->getDefinition('fos_rest.versioning.query_parameter_resolver'); $resolvers['query']->replaceArgument(0, $config['versioning']['resolvers']['query']['parameter_name']); } - if ($config['versioning']['resolvers']['custom_header']['enabled']) { + if ($this->isConfigEnabled($container, $config['versioning']['resolvers']['custom_header'])) { $resolvers['custom_header'] = $container->getDefinition('fos_rest.versioning.header_resolver'); $resolvers['custom_header']->replaceArgument(0, $config['versioning']['resolvers']['custom_header']['header_name']); } - if ($config['versioning']['resolvers']['media_type']['enabled']) { + if ($this->isConfigEnabled($container, $config['versioning']['resolvers']['media_type'])) { $resolvers['media_type'] = $container->getDefinition('fos_rest.versioning.media_type_resolver'); $resolvers['media_type']->replaceArgument(0, $config['versioning']['resolvers']['media_type']['regex']); } @@ -197,7 +196,7 @@ private function loadVersioning(array $config, XmlFileLoader $loader, ContainerB private function loadParamFetcherListener(array $config, XmlFileLoader $loader, ContainerBuilder $container): void { - if ($config['param_fetcher_listener']['enabled']) { + if ($this->isConfigEnabled($container, $config['param_fetcher_listener'])) { if (!class_exists(Constraint::class)) { throw new \LogicException('Enabling the fos_rest.param_fetcher_listener option when the Symfony Validator component is not installed is not supported. Try installing the symfony/validator package.'); } @@ -221,6 +220,10 @@ private function loadBodyConverter(array $config, XmlFileLoader $loader, Contain return; } + if (!class_exists(SensioFrameworkExtraBundle::class)) { + throw new LogicException('To use the request body param converter, the "sensio/framework-extra-bundle" package is required.'); + } + $loader->load('request_body_param_converter.xml'); if (!empty($config['body_converter']['validation_errors_argument'])) { @@ -245,7 +248,7 @@ private function loadView(array $config, XmlFileLoader $loader, ContainerBuilder } } - if ($config['view']['mime_types']['enabled']) { + if ($this->isConfigEnabled($container, $config['view']['mime_types'])) { $loader->load('mime_type_listener.xml'); if (!empty($config['mime_type_listener']['service'])) { @@ -256,7 +259,7 @@ private function loadView(array $config, XmlFileLoader $loader, ContainerBuilder $container->getDefinition('fos_rest.mime_type_listener')->replaceArgument(0, $config['view']['mime_types']['formats']); } - if ($config['view']['view_response_listener']['enabled']) { + if ($this->isConfigEnabled($container, $config['view']['view_response_listener'])) { $loader->load('view_response_listener.xml'); $service = $container->getDefinition('fos_rest.view_response_listener'); @@ -297,7 +300,7 @@ private function loadView(array $config, XmlFileLoader $loader, ContainerBuilder private function loadException(array $config, XmlFileLoader $loader, ContainerBuilder $container): void { - if ($config['exception']['enabled']) { + if ($this->isConfigEnabled($container, $config['exception'])) { $loader->load('exception.xml'); if ($config['exception']['map_exception_codes']) { diff --git a/EventListener/FormatListener.php b/EventListener/FormatListener.php index cff578c71..66af1df72 100644 --- a/EventListener/FormatListener.php +++ b/EventListener/FormatListener.php @@ -16,7 +16,6 @@ use FOS\RestBundle\Negotiation\FormatNegotiator; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException; -use Symfony\Component\HttpKernel\HttpKernelInterface; /** * This listener handles Accept header format negotiations. @@ -55,7 +54,7 @@ public function onKernelRequest(RequestEvent $event): void } if (null === $format) { - if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) { + if ($event->isMainRequest()) { throw new NotAcceptableHttpException('No matching accepted Response format could be determined'); } diff --git a/EventListener/MimeTypeListener.php b/EventListener/MimeTypeListener.php index 98d758ae1..dbb235adc 100644 --- a/EventListener/MimeTypeListener.php +++ b/EventListener/MimeTypeListener.php @@ -14,7 +14,6 @@ use FOS\RestBundle\FOSRestBundle; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; -use Symfony\Component\HttpKernel\HttpKernelInterface; /** * This listener handles registering custom mime types. @@ -44,7 +43,7 @@ public function onKernelRequest(RequestEvent $event): void return; } - if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) { + if ($event->isMainRequest()) { foreach ($this->mimeTypes as $format => $mimeTypes) { $mimeTypes = array_merge($mimeTypes, Request::getMimeTypes($format)); diff --git a/Response/AllowedMethodsLoader/AllowedMethodsRouterLoader.php b/Response/AllowedMethodsLoader/AllowedMethodsRouterLoader.php index 3702c9e8e..c6d4339dd 100644 --- a/Response/AllowedMethodsLoader/AllowedMethodsRouterLoader.php +++ b/Response/AllowedMethodsLoader/AllowedMethodsRouterLoader.php @@ -38,7 +38,7 @@ public function __construct(RouterInterface $router, string $cacheDir, bool $isD public function getAllowedMethods(): array { if (!$this->cache->isFresh()) { - $this->warmUp(null); + $this->warmUp(\dirname($this->cache->getPath())); } return require $this->cache->getPath(); @@ -55,7 +55,7 @@ public function isOptional(): bool /** * {@inheritdoc} */ - public function warmUp($cacheDir): void + public function warmUp(string $cacheDir, ?string $buildDir = null): array { $processedRoutes = []; @@ -90,5 +90,7 @@ public function warmUp($cacheDir): void sprintf('getResources() ); + + return []; } } diff --git a/Tests/Controller/Annotations/FileParamTest.php b/Tests/Controller/Annotations/FileParamTest.php index c776cff3c..81f8bdd77 100644 --- a/Tests/Controller/Annotations/FileParamTest.php +++ b/Tests/Controller/Annotations/FileParamTest.php @@ -14,7 +14,7 @@ use FOS\RestBundle\Controller\Annotations\AbstractParam; use FOS\RestBundle\Controller\Annotations\FileParam; use PHPUnit\Framework\TestCase; -use Symfony\Component\HttpFoundation\ParameterBag; +use Symfony\Component\HttpFoundation\FileBag; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\All; @@ -51,7 +51,7 @@ public function testValueGetter() ->willReturn('foo'); $request = $this->getMockBuilder(Request::class)->getMock(); - $parameterBag = $this->getMockBuilder(ParameterBag::class)->getMock(); + $parameterBag = $this->getMockBuilder(FileBag::class)->getMock(); $parameterBag ->expects($this->once()) ->method('get') diff --git a/Tests/DependencyInjection/FOSRestExtensionTest.php b/Tests/DependencyInjection/FOSRestExtensionTest.php index 1e43e1d5b..54d98ca32 100644 --- a/Tests/DependencyInjection/FOSRestExtensionTest.php +++ b/Tests/DependencyInjection/FOSRestExtensionTest.php @@ -15,6 +15,7 @@ use FOS\RestBundle\DependencyInjection\FOSRestExtension; use FOS\RestBundle\EventListener\ZoneMatcherListener; use PHPUnit\Framework\TestCase; +use Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -386,6 +387,10 @@ public function testViewHandlerSerializerOptions() public function testValidatorAliasWhenEnabled() { + if (!class_exists(SensioFrameworkExtraBundle::class)) { + $this->markTestSkipped('Test requires sensio/framework-extra-bundle'); + } + $config = [ 'fos_rest' => [ 'body_converter' => ['validate' => true], @@ -397,6 +402,10 @@ public function testValidatorAliasWhenEnabled() public function testValidatorAliasWhenDisabled() { + if (!class_exists(SensioFrameworkExtraBundle::class)) { + $this->markTestSkipped('Test requires sensio/framework-extra-bundle'); + } + $config = [ 'fos_rest' => [ 'body_converter' => ['validate' => false], diff --git a/Tests/EventListener/FormatListenerTest.php b/Tests/EventListener/FormatListenerTest.php index ab0e845b2..521bf99fc 100644 --- a/Tests/EventListener/FormatListenerTest.php +++ b/Tests/EventListener/FormatListenerTest.php @@ -18,11 +18,10 @@ use Symfony\Component\HttpFoundation\ChainRequestMatcher; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestMatcher; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Exception\HttpException; -use Symfony\Component\HttpKernel\HttpKernelInterface; -use Symfony\Component\HttpKernel\Kernel; /** * Request listener test. @@ -125,8 +124,8 @@ public function testOnKernelControllerException() ->will($this->returnValue($request)); $event->expects($this->once()) - ->method('getRequestType') - ->will($this->returnValue(HttpKernelInterface::MASTER_REQUEST)); + ->method('isMainRequest') + ->will($this->returnValue(true)); $requestStack = new RequestStack(); $requestStack->push($request); @@ -198,8 +197,8 @@ public function testSfFragmentFormat() ->will($this->returnValue($request)); $event->expects($this->any()) - ->method('getRequestType') - ->will($this->returnValue(HttpKernelInterface::MASTER_REQUEST)); + ->method('isMainRequest') + ->will($this->returnValue(true)); $requestStack = new RequestStack(); $requestStack->push($request); @@ -215,12 +214,12 @@ public function testSfFragmentFormat() $this->assertEquals($request->getRequestFormat(), 'json'); } - private function getRequestMatcher(string $path) + private function getRequestMatcher(string $path): RequestMatcherInterface { - if (Kernel::VERSION_ID < 60200) { - return new RequestMatcher($path); + if (class_exists(ChainRequestMatcher::class)) { + return new ChainRequestMatcher([new RequestMatcher\PathRequestMatcher($path)]); } - return new ChainRequestMatcher([new RequestMatcher\PathRequestMatcher($path)]); + return new RequestMatcher($path); } } diff --git a/Tests/EventListener/MimeTypeListenerTest.php b/Tests/EventListener/MimeTypeListenerTest.php index d60af374b..ba2e7097f 100644 --- a/Tests/EventListener/MimeTypeListenerTest.php +++ b/Tests/EventListener/MimeTypeListenerTest.php @@ -16,7 +16,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; -use Symfony\Component\HttpKernel\HttpKernelInterface; /** * Request listener test. @@ -43,8 +42,8 @@ public function testOnKernelRequest() $this->assertNull($request->getMimeType('jsonp')); $event->expects($this->once()) - ->method('getRequestType') - ->will($this->returnValue(HttpKernelInterface::MASTER_REQUEST)); + ->method('isMainRequest') + ->will($this->returnValue(true)); $listener->onKernelRequest($event); @@ -64,8 +63,8 @@ public function testOnKernelRequestNoZone() ->will($this->returnValue($request)); $event->expects($this->never()) - ->method('getRequestType') - ->will($this->returnValue(HttpKernelInterface::MASTER_REQUEST)); + ->method('isMainRequest') + ->will($this->returnValue(true)); $listener->onKernelRequest($event); @@ -85,8 +84,8 @@ public function testOnKernelRequestWithZone() ->will($this->returnValue($request)); $event->expects($this->once()) - ->method('getRequestType') - ->will($this->returnValue(HttpKernelInterface::MASTER_REQUEST)); + ->method('isMainRequest') + ->will($this->returnValue(true)); $listener->onKernelRequest($event); diff --git a/Tests/EventListener/ResponseStatusCodeListenerTest.php b/Tests/EventListener/ResponseStatusCodeListenerTest.php index d7a208081..0791b36fa 100644 --- a/Tests/EventListener/ResponseStatusCodeListenerTest.php +++ b/Tests/EventListener/ResponseStatusCodeListenerTest.php @@ -43,9 +43,9 @@ public function testResponseStatusCodeIsNotSetWhenRequestNotInRestZone() $request->attributes->set(FOSRestBundle::ZONE_ATTRIBUTE, false); if (class_exists(ExceptionEvent::class)) { - $exceptionEvent = new ExceptionEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, new \DomainException()); + $exceptionEvent = new ExceptionEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST, new \DomainException()); } else { - $exceptionEvent = new GetResponseForExceptionEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, new \DomainException()); + $exceptionEvent = new GetResponseForExceptionEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST, new \DomainException()); } $this->eventListener->getResponseStatusCodeFromThrowable($exceptionEvent); @@ -53,9 +53,9 @@ public function testResponseStatusCodeIsNotSetWhenRequestNotInRestZone() $response = new Response(); if (class_exists(ResponseEvent::class)) { - $responseEvent = new ResponseEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, $response); + $responseEvent = new ResponseEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST, $response); } else { - $responseEvent = new FilterResponseEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, $response); + $responseEvent = new FilterResponseEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST, $response); } $this->eventListener->setResponseStatusCode($responseEvent); @@ -68,9 +68,9 @@ public function testResponseStatusCodeIsNotSetWhenExceptionIsNotMapped() $request = new Request(); if (class_exists(ExceptionEvent::class)) { - $exceptionEvent = new ExceptionEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, new \LogicException()); + $exceptionEvent = new ExceptionEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST, new \LogicException()); } else { - $exceptionEvent = new GetResponseForExceptionEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, new \LogicException()); + $exceptionEvent = new GetResponseForExceptionEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST, new \LogicException()); } $this->eventListener->getResponseStatusCodeFromThrowable($exceptionEvent); @@ -78,9 +78,9 @@ public function testResponseStatusCodeIsNotSetWhenExceptionIsNotMapped() $response = new Response(); if (class_exists(ResponseEvent::class)) { - $responseEvent = new ResponseEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, $response); + $responseEvent = new ResponseEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST, $response); } else { - $responseEvent = new FilterResponseEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, $response); + $responseEvent = new FilterResponseEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST, $response); } $this->eventListener->setResponseStatusCode($responseEvent); @@ -94,9 +94,9 @@ public function testResponseStatusCodeIsSetWhenExceptionTypeIsConfigured() $exception = new \DomainException(); if (class_exists(ExceptionEvent::class)) { - $exceptionEvent = new ExceptionEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, $exception); + $exceptionEvent = new ExceptionEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST, $exception); } else { - $exceptionEvent = new GetResponseForExceptionEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, $exception); + $exceptionEvent = new GetResponseForExceptionEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST, $exception); } $this->eventListener->getResponseStatusCodeFromThrowable($exceptionEvent); @@ -104,9 +104,9 @@ public function testResponseStatusCodeIsSetWhenExceptionTypeIsConfigured() $response = new Response(); if (class_exists(ResponseEvent::class)) { - $responseEvent = new ResponseEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, $response); + $responseEvent = new ResponseEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST, $response); } else { - $responseEvent = new FilterResponseEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, $response); + $responseEvent = new FilterResponseEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST, $response); } $this->eventListener->setResponseStatusCode($responseEvent); @@ -122,14 +122,14 @@ public function testResponseStatusCodeIsSetWhenErrorTypeIsConfigured() $request = new Request(); - $this->eventListener->getResponseStatusCodeFromThrowable(new ExceptionEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, new \ParseError())); + $this->eventListener->getResponseStatusCodeFromThrowable(new ExceptionEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST, new \ParseError())); $response = new Response(); if (class_exists(ResponseEvent::class)) { - $responseEvent = new ResponseEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, $response); + $responseEvent = new ResponseEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST, $response); } else { - $responseEvent = new FilterResponseEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, $response); + $responseEvent = new FilterResponseEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST, $response); } $this->eventListener->setResponseStatusCode($responseEvent); @@ -143,9 +143,9 @@ public function testResponseStatusCodeIsNotOverriddenInSubRequests() $exception = new NotFoundHttpException(); if (class_exists(ExceptionEvent::class)) { - $masterRequestExceptionEvent = new ExceptionEvent($this->createMock(HttpKernelInterface::class), $masterRequest, HttpKernelInterface::MASTER_REQUEST, $exception); + $masterRequestExceptionEvent = new ExceptionEvent($this->createMock(HttpKernelInterface::class), $masterRequest, HttpKernelInterface::MAIN_REQUEST, $exception); } else { - $masterRequestExceptionEvent = new GetResponseForExceptionEvent($this->createMock(HttpKernelInterface::class), $masterRequest, HttpKernelInterface::MASTER_REQUEST, $exception); + $masterRequestExceptionEvent = new GetResponseForExceptionEvent($this->createMock(HttpKernelInterface::class), $masterRequest, HttpKernelInterface::MAIN_REQUEST, $exception); } $this->eventListener->getResponseStatusCodeFromThrowable($masterRequestExceptionEvent); @@ -174,9 +174,9 @@ public function testResponseStatusCodeIsNotOverriddenInSubRequests() $masterRequestResponse = new Response(); if (class_exists(ResponseEvent::class)) { - $masterRequestResponseEvent = new ResponseEvent($this->createMock(HttpKernelInterface::class), $masterRequest, HttpKernelInterface::MASTER_REQUEST, $masterRequestResponse); + $masterRequestResponseEvent = new ResponseEvent($this->createMock(HttpKernelInterface::class), $masterRequest, HttpKernelInterface::MAIN_REQUEST, $masterRequestResponse); } else { - $masterRequestResponseEvent = new FilterResponseEvent($this->createMock(HttpKernelInterface::class), $masterRequest, HttpKernelInterface::MASTER_REQUEST, $masterRequestResponse); + $masterRequestResponseEvent = new FilterResponseEvent($this->createMock(HttpKernelInterface::class), $masterRequest, HttpKernelInterface::MAIN_REQUEST, $masterRequestResponse); } $this->eventListener->setResponseStatusCode($masterRequestResponseEvent); diff --git a/Tests/EventListener/ViewResponseListenerTest.php b/Tests/EventListener/ViewResponseListenerTest.php index d383a1a70..8018bae89 100644 --- a/Tests/EventListener/ViewResponseListenerTest.php +++ b/Tests/EventListener/ViewResponseListenerTest.php @@ -60,7 +60,7 @@ protected function getControllerEvent(Request $request, callable $controller): C { $kernel = $this->createMock(HttpKernelInterface::class); - return new ControllerEvent($kernel, $controller, $request, HttpKernelInterface::MASTER_REQUEST); + return new ControllerEvent($kernel, $controller, $request, HttpKernelInterface::MAIN_REQUEST); } public function testExtractsViewConfigurationFromAnnotationOnMethod() @@ -119,7 +119,7 @@ protected function getViewEvent(Request $request, $result): ViewEvent { $kernel = $this->createMock(HttpKernelInterface::class); - return new ViewEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST, $result); + return new ViewEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, $result); } public function testOnKernelViewWhenControllerResultIsNotViewObject() diff --git a/Tests/Fixtures/Annotations/IdenticalToRequestParam.php b/Tests/Fixtures/Annotations/IdenticalToRequestParam.php new file mode 100644 index 000000000..45b1f97e9 --- /dev/null +++ b/Tests/Fixtures/Annotations/IdenticalToRequestParam.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FOS\RestBundle\Tests\Fixtures\Annotations; + +use FOS\RestBundle\Controller\Annotations\RequestParam; +use Symfony\Component\Validator\Constraints\IdenticalTo; + +/** + * Fixture declaring a request param with a {@see IdenticalTo} validation constraint. + * + * This fixture is required for PHP 8.0 compatibility only. + * + * @Annotation + * @NamedArgumentConstructor + * @Target({"CLASS", "METHOD"}) + */ +#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] +class IdenticalToRequestParam extends RequestParam +{ + /** + * @param mixed $default + */ + public function __construct( + string $name = '', + ?string $key = null, + $identicalTo = null, + $default = null, + string $description = '', + array $incompatibles = [], + bool $strict = true, + bool $map = false, + bool $nullable = false, + bool $allowBlank = true + ) { + parent::__construct($name, $key, null !== $identicalTo ? new IdenticalTo($identicalTo) : null, $default, $description, $incompatibles, $strict, $map, $nullable, $allowBlank); + } +} diff --git a/Tests/Fixtures/Annotations/NotNullFileParam.php b/Tests/Fixtures/Annotations/NotNullFileParam.php new file mode 100644 index 000000000..cba1e89f7 --- /dev/null +++ b/Tests/Fixtures/Annotations/NotNullFileParam.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FOS\RestBundle\Tests\Fixtures\Annotations; + +use FOS\RestBundle\Controller\Annotations\FileParam; +use Symfony\Component\Validator\Constraints\NotNull; + +/** + * Fixture declaring a file param with a {@see NotNull} validation constraint. + * + * This fixture is required for PHP 8.0 compatibility only. + * + * @Annotation + * @NamedArgumentConstructor + * @Target({"CLASS", "METHOD"}) + */ +#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] +class NotNullFileParam extends FileParam +{ + /** + * @param mixed $default + */ + public function __construct( + string $name = '', + bool $strict = true, + bool $image = false, + bool $map = false, + ?string $key = null, + $default = null, + string $description = '', + bool $nullable = false + ) { + parent::__construct($name, $strict, new NotNull(), $image, $map, $key, $default, $description, $nullable); + } +} diff --git a/Tests/Fixtures/Annotations/NotNullQueryParam.php b/Tests/Fixtures/Annotations/NotNullQueryParam.php new file mode 100644 index 000000000..d6658a883 --- /dev/null +++ b/Tests/Fixtures/Annotations/NotNullQueryParam.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FOS\RestBundle\Tests\Fixtures\Annotations; + +use FOS\RestBundle\Controller\Annotations\QueryParam; +use Symfony\Component\Validator\Constraints\NotNull; + +/** + * Fixture declaring a query param with a {@see NotNull} validation constraint. + * + * This fixture is required for PHP 8.0 compatibility only. + * + * @Annotation + * @NamedArgumentConstructor + * @Target({"CLASS", "METHOD"}) + */ +#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] +class NotNullQueryParam extends QueryParam +{ + /** + * @param mixed $default + */ + public function __construct( + string $name = '', + ?string $key = null, + $default = null, + array $incompatibles = [], + string $description = '', + bool $strict = false, + bool $map = false, + bool $nullable = false, + bool $allowBlank = true + ) { + parent::__construct($name, $key, new NotNull(), $default, $incompatibles, $description, $strict, $map, $nullable, $allowBlank); + } +} diff --git a/Tests/Fixtures/Controller/ParamsAnnotatedController.php b/Tests/Fixtures/Controller/ParamsAnnotatedController.php index 7aa40e5ac..679583dc0 100644 --- a/Tests/Fixtures/Controller/ParamsAnnotatedController.php +++ b/Tests/Fixtures/Controller/ParamsAnnotatedController.php @@ -15,7 +15,8 @@ use FOS\RestBundle\Controller\Annotations\RequestParam; use FOS\RestBundle\Controller\Annotations\QueryParam; use FOS\RestBundle\Controller\Annotations\FileParam; -use Symfony\Component\Validator\Constraints\NotNull; +use FOS\RestBundle\Tests\Fixtures\Annotations\NotNullFileParam; +use FOS\RestBundle\Tests\Fixtures\Annotations\NotNullQueryParam; /** * Extract from the documentation. @@ -26,22 +27,26 @@ class ParamsAnnotatedController { /** * @QueryParam(name="page", requirements="\d+", default="1", description="Page of the overview.") + * * @RequestParam(name="byauthor", requirements="[a-z]+", description="by author", incompatibles={"search"}, strict=true) - * @QueryParam(name="filters", map=true, requirements=@NotNull) + * + * @NotNullQueryParam(name="filters", map=true) + * * @FileParam(name="avatar", requirements={"mimeTypes"="application/json"}, image=true) - * @FileParam(name="foo", requirements=@NotNull, strict=false) - * @FileParam(name="bar", requirements=@NotNull, map=true) + * + * @NotNullFileParam(name="foo", strict=false) + * @NotNullFileParam(name="bar", map=true) */ public function getArticlesAction(ParamFetcher $paramFetcher) { } - #[QueryParam(name: 'page', requirements: '\d+', default: '1', description: 'Page of the overview')] + #[QueryParam(name: 'page', requirements: '\d+', default: '1', description: 'Page of the overview.')] #[RequestParam(name: 'byauthor', requirements: '[a-z]+', description: 'by author', incompatibles: ['search'], strict: true)] - #[QueryParam(name: 'filters', requirements: '\d+', default: '1', description: 'Page of the overview')] + #[NotNullQueryParam(name: 'filters', map: true)] #[FileParam(name: 'avatar', requirements: ['mimeTypes' => 'application/json'], image: true)] - #[FileParam(name: 'foo', strict: false)] - #[FileParam(name: 'bar', map: true)] + #[NotNullFileParam(name: 'foo', strict: false)] + #[NotNullFileParam(name: 'bar', map: true)] public function getArticlesAttributesAction(ParamFetcher $paramFetcher) { } diff --git a/Tests/Functional/Bundle/TestBundle/Controller/ArticleController.php b/Tests/Functional/Bundle/TestBundle/Controller/ArticleController.php index 32c03aba3..1002f5e1e 100644 --- a/Tests/Functional/Bundle/TestBundle/Controller/ArticleController.php +++ b/Tests/Functional/Bundle/TestBundle/Controller/ArticleController.php @@ -20,10 +20,6 @@ class ArticleController extends AbstractFOSRestController { /** - * Create a new resource. - * - * @return View view instance - * * @Post("/articles.{_format}", name="post_articles") * * @View() @@ -32,26 +28,18 @@ class ArticleController extends AbstractFOSRestController #[View] public function cpostAction(Request $request) { - $view = $this->routeRedirectView('test_redirect_endpoint', ['name' => $request->request->get('name')]); - - return $view; + return $this->routeRedirectView('test_redirect_endpoint', ['name' => $request->request->get('name')]); } /** - * Get list. - * - * @return View view instance - * * @Get("/articles.{_format}", name="get_article", defaults={"_format": "html"}) * * @View() */ #[Get(path: '/articles.{_format}', name: 'get_article', defaults: ['_format' => 'html'])] #[View] - public function cgetAction(Request $request) + public function cgetAction() { - $view = $this->view(); - - return $view; + return $this->view(); } } diff --git a/Tests/Functional/Bundle/TestBundle/Controller/ParamFetcherController.php b/Tests/Functional/Bundle/TestBundle/Controller/ParamFetcherController.php index 5464e8431..8d9c0e940 100644 --- a/Tests/Functional/Bundle/TestBundle/Controller/ParamFetcherController.php +++ b/Tests/Functional/Bundle/TestBundle/Controller/ParamFetcherController.php @@ -16,6 +16,7 @@ use FOS\RestBundle\Controller\Annotations\QueryParam; use FOS\RestBundle\Controller\Annotations\RequestParam; use FOS\RestBundle\Request\ParamFetcherInterface; +use FOS\RestBundle\Tests\Fixtures\Annotations\IdenticalToRequestParam; use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -28,9 +29,15 @@ class ParamFetcherController extends AbstractFOSRestController * @RequestParam(name="raw", requirements=@IdenticalTo({"foo"="raw", "bar"="foo"}), default="invalid", strict=false) * @RequestParam(name="map", map=true, requirements=@IdenticalTo({"foo"="map", "foobar"="foo"}), default="%invalid2% %%", strict=false) * @RequestParam(name="bar", nullable=true, requirements="%bar%\ foo") + * * @QueryParam(name="foz", requirements="[a-z]+") * @QueryParam(name="baz", requirements="[a-z]+", incompatibles={"foz"}) */ + #[IdenticalToRequestParam(name: 'raw', identicalTo: ['value' => ['foo' => 'raw', 'bar' => 'foo']], default: 'invalid', strict: false)] + #[IdenticalToRequestParam(name: 'map', map: true, identicalTo: ['value' => ['foo' => 'map', 'foobar' => 'foo']], default: '%invalid2% %%', strict: false)] + #[RequestParam(name: 'bar', nullable: true, requirements: '%bar%\ foo')] + #[QueryParam(name: 'foz', requirements: '[a-z]+')] + #[QueryParam(name: 'baz', requirements: '[a-z]+', incompatibles: ['foz'])] public function paramsAction(ParamFetcherInterface $fetcher) { return new JsonResponse($fetcher->all()); @@ -38,8 +45,11 @@ public function paramsAction(ParamFetcherInterface $fetcher) /** * @QueryParam(name="foo", default="invalid") + * * @RequestParam(name="bar", default="%foo%") */ + #[QueryParam(name: 'foo', default: 'invalid')] + #[RequestParam(name: 'bar', default: '%foo%')] public function testAction(Request $request, ParamFetcherInterface $fetcher) { $paramsBefore = $fetcher->all(); @@ -61,6 +71,7 @@ public function testAction(Request $request, ParamFetcherInterface $fetcher) /** * @FileParam(name="single_file", strict=false, default="noFile") */ + #[FileParam(name: 'single_file', strict: false, default: 'noFile')] public function singleFileAction(ParamFetcherInterface $fetcher) { /** @var UploadedFile $file */ @@ -74,6 +85,7 @@ public function singleFileAction(ParamFetcherInterface $fetcher) /** * @FileParam(name="array_files", map=true) */ + #[FileParam(name: 'array_files', map: true)] public function fileCollectionAction(ParamFetcherInterface $fetcher) { $files = $fetcher->get('array_files'); @@ -89,6 +101,7 @@ public function fileCollectionAction(ParamFetcherInterface $fetcher) /** * @FileParam(name="array_images", image=true, strict=false, map=true, default="NotAnImage") */ + #[FileParam(name: 'array_images', image: true, strict: false, map: true, default: 'NotAnImage')] public function imageCollectionAction(ParamFetcherInterface $fetcher) { $files = $fetcher->get('array_images'); diff --git a/Tests/Functional/DependencyInjectionTest.php b/Tests/Functional/DependencyInjectionTest.php index c946ce7ca..1517b7afc 100644 --- a/Tests/Functional/DependencyInjectionTest.php +++ b/Tests/Functional/DependencyInjectionTest.php @@ -64,7 +64,7 @@ public function registerBundles(): array public function registerContainerConfiguration(LoaderInterface $loader): void { $loader->load(function (ContainerBuilder $container) { - $container->loadFromExtension('framework', [ + $frameworkConfig = [ 'annotations' => [ 'enabled' => true, ], @@ -73,7 +73,13 @@ public function registerContainerConfiguration(LoaderInterface $loader): void 'resource' => '%kernel.project_dir%/config/routing.yml', 'utf8' => true, ], - ]); + ]; + + if (Kernel::VERSION_ID >= 70000) { + unset($frameworkConfig['annotations']); + } + + $container->loadFromExtension('framework', $frameworkConfig); $container->loadFromExtension('fos_rest', []); $container->setAlias('test.jms_serializer.handler_registry', new Alias('jms_serializer.handler_registry', true)); $container->setAlias('test.jms_serializer.form_error_handler', new Alias('jms_serializer.form_error_handler', true)); diff --git a/Tests/Functional/ParamFetcherTest.php b/Tests/Functional/ParamFetcherTest.php index 70cb30334..6ecdf3bb2 100644 --- a/Tests/Functional/ParamFetcherTest.php +++ b/Tests/Functional/ParamFetcherTest.php @@ -13,7 +13,6 @@ use Symfony\Bundle\FrameworkBundle\KernelBrowser; use Symfony\Component\HttpFoundation\File\UploadedFile; -use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; /** * @author Ener-Getick @@ -36,27 +35,19 @@ class ParamFetcherTest extends WebTestCase private function createUploadedFile($path, $originalName, $mimeType = null, $error = null, $test = false) { - $ref = new \ReflectionClass(UploadedFile::class); - $params = $ref->getConstructor()->getParameters(); - - if ('error' === $params[3]->getName()) { - // symfony 4 has removed the $size param - return new UploadedFile( - $path, - $originalName, - $mimeType, - $error, - $test - ); - } else { - return new UploadedFile( - $path, - $originalName, - $mimeType, - filesize($path), - $error, - $test - ); + return new UploadedFile( + $path, + $originalName, + $mimeType, + $error, + $test + ); + } + + public static function setUpBeforeClass(): void + { + if (PHP_MAJOR_VERSION === 8 && PHP_MINOR_VERSION === 0) { + self::markTestSkipped('Test fixture contains attributes not parsable on PHP 8.0.'); } } @@ -69,7 +60,10 @@ public function testDefaultParameters() { $this->client->request('POST', '/params'); + $this->assertTrue($this->client->getResponse()->isSuccessful(), 'The request resulted in an error.'); + $data = $this->getData(); + foreach (['raw' => 'invalid', 'map' => 'invalid2 %', 'bar' => null] as $key => $value) { $this->assertArrayHasKey($key, $data); $this->assertSame($value, $data[$key]); @@ -80,6 +74,8 @@ public function testValidRawParameter() { $this->client->request('POST', '/params', ['raw' => $this->validRaw, 'map' => $this->validMap]); + $this->assertTrue($this->client->getResponse()->isSuccessful(), 'The request resulted in an error.'); + $data = $this->getData(); foreach (['raw' => $this->validRaw, 'map' => 'invalid2 %', 'bar' => null] as $key => $value) { $this->assertArrayHasKey($key, $data); @@ -93,8 +89,11 @@ public function testValidMapParameter() 'foo' => $this->validMap, 'bar' => $this->validMap, ]; + $this->client->request('POST', '/params', ['raw' => 'bar', 'map' => $map, 'bar' => 'bar foo']); + $this->assertTrue($this->client->getResponse()->isSuccessful(), 'The request resulted in an error.'); + $data = $this->getData(); foreach (['raw' => 'invalid', 'map' => $map, 'bar' => 'bar foo'] as $key => $value) { $this->assertArrayHasKey($key, $data); @@ -106,6 +105,8 @@ public function testWithSubRequests() { $this->client->request('POST', '/params/test?foo=quz', ['raw' => $this->validRaw]); + $this->assertTrue($this->client->getResponse()->isSuccessful(), 'The request resulted in an error.'); + $expected = [ 'before' => ['foo' => 'quz', 'bar' => 'foo'], 'during' => ['raw' => $this->validRaw, 'map' => 'invalid2 %', 'bar' => null, 'foz' => '', 'baz' => ''], @@ -122,13 +123,15 @@ public function testFileParamWithErrors() { $image = $this->createUploadedFile( 'Tests/Fixtures/Asset/cat.jpeg', - $singleFileName = 'cat.jpeg', + 'cat.jpeg', 'image/jpeg', 7 ); $this->client->request('POST', '/file/test', [], ['single_file' => $image]); + $this->assertTrue($this->client->getResponse()->isSuccessful(), 'The request resulted in an error.'); + $this->assertEquals([ 'single_file' => 'noFile', ], $this->getData()); @@ -144,6 +147,8 @@ public function testFileParam() $this->client->request('POST', '/file/test', [], ['single_file' => $image]); + $this->assertTrue($this->client->getResponse()->isSuccessful(), 'The request resulted in an error.'); + $this->assertEquals([ 'single_file' => $singleFileName, ], $this->getData()); @@ -153,6 +158,8 @@ public function testFileParamNull() { $this->client->request('POST', '/file/test', [], []); + $this->assertTrue($this->client->getResponse()->isSuccessful(), 'The request resulted in an error.'); + $this->assertEquals([ 'single_file' => 'noFile', ], $this->getData()); @@ -175,6 +182,8 @@ public function testFileParamArrayNullItem() $this->client->request('POST', '/file/collection/test', [], ['array_files' => $images]); + $this->assertTrue($this->client->getResponse()->isSuccessful(), 'The request resulted in an error.'); + $this->assertEquals([ 'array_files' => [$imageName, $txtName], ], $this->getData()); @@ -197,6 +206,8 @@ public function testFileParamImageConstraintArray() $this->client->request('POST', '/image/collection/test', [], ['array_images' => $images]); + $this->assertTrue($this->client->getResponse()->isSuccessful(), 'The request resulted in an error.'); + $this->assertEquals([ 'array_images' => [$imageName, $imageName2], ], $this->getData()); @@ -219,6 +230,8 @@ public function testFileParamImageConstraintArrayException() $this->client->request('POST', '/image/collection/test', [], ['array_images' => $images]); + $this->assertTrue($this->client->getResponse()->isSuccessful(), 'The request resulted in an error.'); + $this->assertEquals([ 'array_images' => 'NotAnImage', ], $this->getData()); @@ -228,6 +241,8 @@ public function testValidQueryParameter() { $this->client->request('POST', '/params?foz=val1'); + $this->assertTrue($this->client->getResponse()->isSuccessful(), 'The request resulted in an error.'); + $data = $this->getData(); foreach (['foz' => ''] as $key => $value) { $this->assertArrayHasKey($key, $data); @@ -237,16 +252,10 @@ public function testValidQueryParameter() public function testIncompatibleQueryParameter() { - try { - $this->client->request('POST', '/params?foz=val1&baz=val2'); - - // SF >= 4.4 - $this->assertEquals(400, $this->client->getResponse()->getStatusCode()); - $this->assertStringContainsString('\\"baz\\" param is incompatible with foz param.', $this->client->getResponse()->getContent()); - } catch (BadRequestHttpException $e) { - // SF < 4.4 - $this->assertEquals('"baz" param is incompatible with foz param.', $e->getMessage()); - } + $this->client->request('POST', '/params?foz=val1&baz=val2'); + + $this->assertEquals(400, $this->client->getResponse()->getStatusCode()); + $this->assertStringContainsString('\\"baz\\" param is incompatible with foz param.', $this->client->getResponse()->getContent()); } protected function getData() diff --git a/Tests/Functional/RequestBodyParamConverterTest.php b/Tests/Functional/RequestBodyParamConverterTest.php index 1f27a4e6f..ec857eeb7 100644 --- a/Tests/Functional/RequestBodyParamConverterTest.php +++ b/Tests/Functional/RequestBodyParamConverterTest.php @@ -11,11 +11,19 @@ namespace FOS\RestBundle\Tests\Functional; -use Symfony\Bundle\FrameworkBundle\Test\BrowserKitAssertionsTrait; -use Symfony\Bundle\TwigBundle\Controller\PreviewErrorController; +use Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle; class RequestBodyParamConverterTest extends WebTestCase { + public static function setUpBeforeClass(): void + { + if (!class_exists(SensioFrameworkExtraBundle::class)) { + self::markTestSkipped('Test requires sensio/framework-extra-bundle'); + } + + parent::setUpBeforeClass(); + } + public function testRequestBodyIsDeserialized() { $client = $this->createClient(['test_case' => 'RequestBodyParamConverter']); @@ -32,35 +40,8 @@ public function testRequestBodyIsDeserialized() $this->assertSame('Post 1', $client->getResponse()->getContent()); } - /** - * Added to the legacy group to not trigger a deprecation. This deprecation is triggered on version 4.4 of - * the TwigBundle where the PreviewErrorController class is deprecated. Since we only make sure not to break - * that controller class, we do not have to care about the deprecations. - * - * @group legacy - * - * @see https://github.com/FriendsOfSymfony/FOSRestBundle/issues/1237 - */ - public function testErrorPageServedByTwigBundle() - { - if (!class_exists(PreviewErrorController::class)) { - $this->markTestSkipped(); - } - - $client = $this->createClient(['test_case' => 'RequestBodyParamConverterTwigBundle']); - $client->request('GET', '/_error/404.txt'); - - // Status code 200 as this page describes an error but is not the result of an error. - $this->assertEquals(200, $client->getResponse()->getStatusCode()); - $this->assertStringContainsString('The server returned a "404 Not Found".', $client->getResponse()->getContent()); - } - public function testErrorPageServedByFrameworkBundle() { - if (!trait_exists(BrowserKitAssertionsTrait::class)) { - $this->markTestSkipped(); - } - $client = $this->createClient(['test_case' => 'RequestBodyParamConverterFrameworkBundle']); $client->request('GET', '/_error/404.txt'); diff --git a/Tests/Functional/RouteAttributesTest.php b/Tests/Functional/RouteAttributesTest.php index 7f011f302..3c24a5a23 100644 --- a/Tests/Functional/RouteAttributesTest.php +++ b/Tests/Functional/RouteAttributesTest.php @@ -22,6 +22,7 @@ class RouteAttributesTest extends WebTestCase public static function setUpBeforeClass(): void { parent::setUpBeforeClass(); + static::$client = static::createClient(['test_case' => self::TEST_CASE]); } diff --git a/Tests/Functional/SerializerErrorTest.php b/Tests/Functional/SerializerErrorTest.php index 3277c629f..00bd2e00b 100644 --- a/Tests/Functional/SerializerErrorTest.php +++ b/Tests/Functional/SerializerErrorTest.php @@ -11,8 +11,6 @@ namespace FOS\RestBundle\Tests\Functional; -use Symfony\Component\ErrorHandler\ErrorRenderer\SerializerErrorRenderer; - /** * Test class for serialization errors and exceptions. * @@ -38,10 +36,6 @@ public static function tearDownAfterClass(): void */ public function testSerializeExceptionJsonUsingErrorRenderer(string $testCase, array $expectedJson, string $expectedContentType) { - if (!class_exists(SerializerErrorRenderer::class)) { - $this->markTestSkipped(); - } - $this->iniSet('error_log', file_exists('/dev/null') ? '/dev/null' : 'nul'); $client = $this->createClient(['test_case' => $testCase, 'debug' => false]); @@ -79,10 +73,6 @@ public function serializeExceptionJsonUsingErrorRendererProvider(): array public function testSerializeUnknownExceptionJsonWithDebugUsingErrorRenderer() { - if (!class_exists(SerializerErrorRenderer::class)) { - $this->markTestSkipped(); - } - $this->iniSet('error_log', file_exists('/dev/null') ? '/dev/null' : 'nul'); $client = $this->createClient(['test_case' => 'FlattenExceptionNormalizerLegacyFormatDebug', 'debug' => false]); @@ -93,10 +83,6 @@ public function testSerializeUnknownExceptionJsonWithDebugUsingErrorRenderer() public function testSerializeUnknownExceptionJsonWithoutDebugUsingErrorRenderer() { - if (!class_exists(SerializerErrorRenderer::class)) { - $this->markTestSkipped(); - } - $this->iniSet('error_log', file_exists('/dev/null') ? '/dev/null' : 'nul'); $client = $this->createClient(['test_case' => 'FlattenExceptionNormalizerLegacyFormat', 'debug' => false]); @@ -110,10 +96,6 @@ public function testSerializeUnknownExceptionJsonWithoutDebugUsingErrorRenderer( */ public function testSerializeExceptionCodeMappedToResponseStatusCodeJsonUsingErrorRenderer(string $testCase, array $expectedJson) { - if (!class_exists(SerializerErrorRenderer::class)) { - $this->markTestSkipped(); - } - $this->iniSet('error_log', file_exists('/dev/null') ? '/dev/null' : 'nul'); $client = $this->createClient(['test_case' => $testCase, 'debug' => false]); @@ -124,10 +106,6 @@ public function testSerializeExceptionCodeMappedToResponseStatusCodeJsonUsingErr public function testCustomExceptionSerialization() { - if (!class_exists(SerializerErrorRenderer::class)) { - $this->markTestSkipped(); - } - $this->iniSet('error_log', file_exists('/dev/null') ? '/dev/null' : 'nul'); $client = $this->createClient(['test_case' => 'CustomFlattenExceptionNormalizer', 'debug' => false]); @@ -182,10 +160,6 @@ public function serializeExceptionCodeMappedToResponseStatusCodeJsonProvider(): */ public function testSerializeExceptionXmlUsingErrorRenderer(string $testCase, string $expectedContent, string $expectedContentType) { - if (!class_exists(SerializerErrorRenderer::class)) { - $this->markTestSkipped(); - } - $this->iniSet('error_log', file_exists('/dev/null') ? '/dev/null' : 'nul'); $client = $this->createClient(['test_case' => $testCase, 'debug' => false]); diff --git a/Tests/Functional/VersionTest.php b/Tests/Functional/VersionTest.php index 4fcc98046..993d87656 100644 --- a/Tests/Functional/VersionTest.php +++ b/Tests/Functional/VersionTest.php @@ -21,6 +21,7 @@ class VersionTest extends WebTestCase public static function setUpBeforeClass(): void { parent::setUpBeforeClass(); + static::$client = static::createClient(['test_case' => 'Version']); } diff --git a/Tests/Functional/app/AllowedMethodsListener/bundles.php b/Tests/Functional/app/AllowedMethodsListener/bundles.php index 2e6a41aa8..90086464f 100644 --- a/Tests/Functional/app/AllowedMethodsListener/bundles.php +++ b/Tests/Functional/app/AllowedMethodsListener/bundles.php @@ -9,9 +9,14 @@ * file that was distributed with this source code. */ -return [ +$bundles = [ new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(), new \FOS\RestBundle\FOSRestBundle(), new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(), - new \Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(), ]; + +if (class_exists(\Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class)) { + $bundles[] = new \Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(); +} + +return $bundles; diff --git a/Tests/Functional/app/AllowedMethodsListener/config.yml b/Tests/Functional/app/AllowedMethodsListener/config.yml index 7f6103eb0..feaef7958 100644 --- a/Tests/Functional/app/AllowedMethodsListener/config.yml +++ b/Tests/Functional/app/AllowedMethodsListener/config.yml @@ -1,6 +1,6 @@ imports: - { resource: ../config/default.yml } - - { resource: ../config/sensio_framework_extra.yml } + - { resource: ../config/sensio_framework_extra.php } framework: serializer: diff --git a/Tests/Functional/app/Configuration/bundles.php b/Tests/Functional/app/Configuration/bundles.php index 1077c77f9..72d52ae79 100644 --- a/Tests/Functional/app/Configuration/bundles.php +++ b/Tests/Functional/app/Configuration/bundles.php @@ -9,10 +9,15 @@ * file that was distributed with this source code. */ -return [ +$bundles = [ new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(), new \Symfony\Bundle\SecurityBundle\SecurityBundle(), - new \Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(), new \FOS\RestBundle\FOSRestBundle(), new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(), ]; + +if (class_exists(\Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class)) { + $bundles[] = new \Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(); +} + +return $bundles; diff --git a/Tests/Functional/app/Configuration/config.yml b/Tests/Functional/app/Configuration/config.yml index 530089633..fb4cdd571 100644 --- a/Tests/Functional/app/Configuration/config.yml +++ b/Tests/Functional/app/Configuration/config.yml @@ -1,6 +1,6 @@ imports: - { resource: ../config/default.yml } - - { resource: ../config/sensio_framework_extra.yml } + - { resource: ../config/sensio_framework_extra.php } - { resource: framework.php } - { resource: security.php } diff --git a/Tests/Functional/app/Configuration/framework.php b/Tests/Functional/app/Configuration/framework.php index 1ed19d3eb..7dd101454 100644 --- a/Tests/Functional/app/Configuration/framework.php +++ b/Tests/Functional/app/Configuration/framework.php @@ -29,4 +29,8 @@ $frameworkConfig['http_method_override'] = true; } +if (\Symfony\Component\HttpKernel\Kernel::VERSION_ID >= 70000) { + unset($frameworkConfig['annotations']); +} + $container->loadFromExtension('framework', $frameworkConfig); diff --git a/Tests/Functional/app/ParamFetcher/bundles.php b/Tests/Functional/app/ParamFetcher/bundles.php index 2e6a41aa8..90086464f 100644 --- a/Tests/Functional/app/ParamFetcher/bundles.php +++ b/Tests/Functional/app/ParamFetcher/bundles.php @@ -9,9 +9,14 @@ * file that was distributed with this source code. */ -return [ +$bundles = [ new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(), new \FOS\RestBundle\FOSRestBundle(), new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(), - new \Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(), ]; + +if (class_exists(\Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class)) { + $bundles[] = new \Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(); +} + +return $bundles; diff --git a/Tests/Functional/app/ParamFetcher/config.yml b/Tests/Functional/app/ParamFetcher/config.yml index e511d0dee..63490abe2 100644 --- a/Tests/Functional/app/ParamFetcher/config.yml +++ b/Tests/Functional/app/ParamFetcher/config.yml @@ -1,6 +1,6 @@ imports: - { resource: ../config/default.yml } - - { resource: ../config/sensio_framework_extra.yml } + - { resource: ../config/sensio_framework_extra.php } parameters: bar: bar diff --git a/Tests/Functional/app/RequestBodyParamConverter/bundles.php b/Tests/Functional/app/RequestBodyParamConverter/bundles.php index 2e6a41aa8..90086464f 100644 --- a/Tests/Functional/app/RequestBodyParamConverter/bundles.php +++ b/Tests/Functional/app/RequestBodyParamConverter/bundles.php @@ -9,9 +9,14 @@ * file that was distributed with this source code. */ -return [ +$bundles = [ new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(), new \FOS\RestBundle\FOSRestBundle(), new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(), - new \Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(), ]; + +if (class_exists(\Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class)) { + $bundles[] = new \Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(); +} + +return $bundles; diff --git a/Tests/Functional/app/RequestBodyParamConverter/config.yml b/Tests/Functional/app/RequestBodyParamConverter/config.yml index 6761c494d..7a6df0b89 100644 --- a/Tests/Functional/app/RequestBodyParamConverter/config.yml +++ b/Tests/Functional/app/RequestBodyParamConverter/config.yml @@ -1,6 +1,6 @@ imports: - { resource: ../config/default.yml } - - { resource: ../config/sensio_framework_extra.yml } + - { resource: ./sensio_framework_extra.php } framework: serializer: true @@ -12,10 +12,6 @@ fos_rest: body_converter: enabled: true -sensio_framework_extra: - request: - converters: true - services: get_set_method_normalizer: class: Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer diff --git a/Tests/Functional/app/RequestBodyParamConverter/sensio_framework_extra.php b/Tests/Functional/app/RequestBodyParamConverter/sensio_framework_extra.php new file mode 100644 index 000000000..75dd24d6b --- /dev/null +++ b/Tests/Functional/app/RequestBodyParamConverter/sensio_framework_extra.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (class_exists(\Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class)) { + $loader->load(__DIR__.'/../config/sensio_framework_extra.php'); + + $config = [ + 'request' => [ + 'converters' => true, + ], + ]; + + $container->loadFromExtension('sensio_framework_extra', $config); +} diff --git a/Tests/Functional/app/RequestBodyParamConverterFrameworkBundle/bundles.php b/Tests/Functional/app/RequestBodyParamConverterFrameworkBundle/bundles.php index 2e6a41aa8..90086464f 100644 --- a/Tests/Functional/app/RequestBodyParamConverterFrameworkBundle/bundles.php +++ b/Tests/Functional/app/RequestBodyParamConverterFrameworkBundle/bundles.php @@ -9,9 +9,14 @@ * file that was distributed with this source code. */ -return [ +$bundles = [ new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(), new \FOS\RestBundle\FOSRestBundle(), new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(), - new \Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(), ]; + +if (class_exists(\Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class)) { + $bundles[] = new \Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(); +} + +return $bundles; diff --git a/Tests/Functional/app/RequestBodyParamConverterFrameworkBundle/config.yml b/Tests/Functional/app/RequestBodyParamConverterFrameworkBundle/config.yml index dffa3852e..4eae22660 100644 --- a/Tests/Functional/app/RequestBodyParamConverterFrameworkBundle/config.yml +++ b/Tests/Functional/app/RequestBodyParamConverterFrameworkBundle/config.yml @@ -1,6 +1,6 @@ imports: - { resource: ../config/default.yml } - - { resource: ../config/sensio_framework_extra.yml } + - { resource: ./sensio_framework_extra.php } framework: serializer: true @@ -12,10 +12,6 @@ fos_rest: body_converter: enabled: true -sensio_framework_extra: - request: - converters: true - services: get_set_method_normalizer: class: Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer diff --git a/Tests/Functional/app/RequestBodyParamConverterFrameworkBundle/sensio_framework_extra.php b/Tests/Functional/app/RequestBodyParamConverterFrameworkBundle/sensio_framework_extra.php new file mode 100644 index 000000000..75dd24d6b --- /dev/null +++ b/Tests/Functional/app/RequestBodyParamConverterFrameworkBundle/sensio_framework_extra.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (class_exists(\Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class)) { + $loader->load(__DIR__.'/../config/sensio_framework_extra.php'); + + $config = [ + 'request' => [ + 'converters' => true, + ], + ]; + + $container->loadFromExtension('sensio_framework_extra', $config); +} diff --git a/Tests/Functional/app/RequestBodyParamConverterTwigBundle/bundles.php b/Tests/Functional/app/RequestBodyParamConverterTwigBundle/bundles.php index 442dffad4..2a4b9abff 100644 --- a/Tests/Functional/app/RequestBodyParamConverterTwigBundle/bundles.php +++ b/Tests/Functional/app/RequestBodyParamConverterTwigBundle/bundles.php @@ -9,10 +9,15 @@ * file that was distributed with this source code. */ -return [ +$bundles = [ new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(), new \Symfony\Bundle\TwigBundle\TwigBundle(), new \FOS\RestBundle\FOSRestBundle(), new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(), - new \Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(), ]; + +if (class_exists(\Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class)) { + $bundles[] = new \Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(); +} + +return $bundles; diff --git a/Tests/Functional/app/RequestBodyParamConverterTwigBundle/config.yml b/Tests/Functional/app/RequestBodyParamConverterTwigBundle/config.yml index 7afea2132..5dff9ac07 100644 --- a/Tests/Functional/app/RequestBodyParamConverterTwigBundle/config.yml +++ b/Tests/Functional/app/RequestBodyParamConverterTwigBundle/config.yml @@ -1,6 +1,6 @@ imports: - { resource: ../config/default.yml } - - { resource: ../config/sensio_framework_extra.yml } + - { resource: ../config/sensio_framework_extra.php } framework: serializer: true @@ -12,10 +12,6 @@ fos_rest: body_converter: enabled: true -sensio_framework_extra: - request: - converters: true - twig: strict_variables: '%kernel.debug%' diff --git a/Tests/Functional/app/RequestBodyParamConverterTwigBundle/sensio_framework_extra.php b/Tests/Functional/app/RequestBodyParamConverterTwigBundle/sensio_framework_extra.php new file mode 100644 index 000000000..75dd24d6b --- /dev/null +++ b/Tests/Functional/app/RequestBodyParamConverterTwigBundle/sensio_framework_extra.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (class_exists(\Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class)) { + $loader->load(__DIR__.'/../config/sensio_framework_extra.php'); + + $config = [ + 'request' => [ + 'converters' => true, + ], + ]; + + $container->loadFromExtension('sensio_framework_extra', $config); +} diff --git a/Tests/Functional/app/RouteAttributes/bundles.php b/Tests/Functional/app/RouteAttributes/bundles.php index b0b50211e..90086464f 100644 --- a/Tests/Functional/app/RouteAttributes/bundles.php +++ b/Tests/Functional/app/RouteAttributes/bundles.php @@ -9,9 +9,14 @@ * file that was distributed with this source code. */ -return [ +$bundles = [ new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(), - new \Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(), new \FOS\RestBundle\FOSRestBundle(), new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(), ]; + +if (class_exists(\Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class)) { + $bundles[] = new \Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(); +} + +return $bundles; diff --git a/Tests/Functional/app/RouteAttributes/config.yml b/Tests/Functional/app/RouteAttributes/config.yml index def807c18..b4eb5157e 100644 --- a/Tests/Functional/app/RouteAttributes/config.yml +++ b/Tests/Functional/app/RouteAttributes/config.yml @@ -1,6 +1,6 @@ imports: - { resource: ../config/default.yml } - - { resource: ../config/sensio_framework_extra.yml } + - { resource: ../config/sensio_framework_extra.php } framework: serializer: diff --git a/Tests/Functional/app/Version/bundles.php b/Tests/Functional/app/Version/bundles.php index b0b50211e..90086464f 100644 --- a/Tests/Functional/app/Version/bundles.php +++ b/Tests/Functional/app/Version/bundles.php @@ -9,9 +9,14 @@ * file that was distributed with this source code. */ -return [ +$bundles = [ new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(), - new \Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(), new \FOS\RestBundle\FOSRestBundle(), new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(), ]; + +if (class_exists(\Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class)) { + $bundles[] = new \Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(); +} + +return $bundles; diff --git a/Tests/Functional/app/Version/config.yml b/Tests/Functional/app/Version/config.yml index 1aa744d74..c70d89da0 100644 --- a/Tests/Functional/app/Version/config.yml +++ b/Tests/Functional/app/Version/config.yml @@ -1,6 +1,6 @@ imports: - { resource: ../config/default.yml } - - { resource: ../config/sensio_framework_extra.yml } + - { resource: ../config/sensio_framework_extra.php } framework: serializer: diff --git a/Tests/Functional/app/ViewResponseListener/bundles.php b/Tests/Functional/app/ViewResponseListener/bundles.php index b0b50211e..90086464f 100644 --- a/Tests/Functional/app/ViewResponseListener/bundles.php +++ b/Tests/Functional/app/ViewResponseListener/bundles.php @@ -9,9 +9,14 @@ * file that was distributed with this source code. */ -return [ +$bundles = [ new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(), - new \Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(), new \FOS\RestBundle\FOSRestBundle(), new \FOS\RestBundle\Tests\Functional\Bundle\TestBundle\TestBundle(), ]; + +if (class_exists(\Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class)) { + $bundles[] = new \Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(); +} + +return $bundles; diff --git a/Tests/Functional/app/ViewResponseListener/config.yml b/Tests/Functional/app/ViewResponseListener/config.yml index 63096eed0..949772571 100644 --- a/Tests/Functional/app/ViewResponseListener/config.yml +++ b/Tests/Functional/app/ViewResponseListener/config.yml @@ -1,6 +1,6 @@ imports: - { resource: ../config/default.yml } - - { resource: ../config/sensio_framework_extra.yml } + - { resource: ../config/sensio_framework_extra.php } - { resource: framework.php } fos_rest: diff --git a/Tests/Functional/app/config/framework.php b/Tests/Functional/app/config/framework.php index d29de23ee..6b3ea0d80 100644 --- a/Tests/Functional/app/config/framework.php +++ b/Tests/Functional/app/config/framework.php @@ -41,4 +41,8 @@ $frameworkConfig['http_method_override'] = true; } +if (\Symfony\Component\HttpKernel\Kernel::VERSION_ID >= 70000) { + unset($frameworkConfig['annotations']); +} + $container->loadFromExtension('framework', $frameworkConfig); diff --git a/Tests/Functional/app/config/sensio_framework_extra.php b/Tests/Functional/app/config/sensio_framework_extra.php new file mode 100644 index 000000000..82d7801f2 --- /dev/null +++ b/Tests/Functional/app/config/sensio_framework_extra.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (class_exists(\Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class)) { + $config = [ + 'router' => [ + 'annotations' => false, + ], + ]; + + $container->loadFromExtension('sensio_framework_extra', $config); +} diff --git a/Tests/Functional/app/config/sensio_framework_extra.yml b/Tests/Functional/app/config/sensio_framework_extra.yml deleted file mode 100644 index 1821ccc07..000000000 --- a/Tests/Functional/app/config/sensio_framework_extra.yml +++ /dev/null @@ -1,3 +0,0 @@ -sensio_framework_extra: - router: - annotations: false diff --git a/Tests/Request/ParamReaderTest.php b/Tests/Request/ParamReaderTest.php index 70e2408b7..fa133551d 100644 --- a/Tests/Request/ParamReaderTest.php +++ b/Tests/Request/ParamReaderTest.php @@ -11,13 +11,13 @@ namespace FOS\RestBundle\Tests\Request; +use Composer\InstalledVersions; +use Doctrine\Common\Annotations\AnnotationReader; +use Doctrine\Common\Annotations\Reader; use FOS\RestBundle\Controller\Annotations\ParamInterface; -use FOS\RestBundle\Controller\Annotations\View; use FOS\RestBundle\Request\ParamReader; -use Doctrine\Common\Annotations\AnnotationReader; use FOS\RestBundle\Tests\Fixtures\Controller\ParamsAnnotatedController; use PHPUnit\Framework\TestCase; -use Symfony\Component\Validator\Constraints\NotNull; /** * @author Alexander @@ -26,89 +26,60 @@ class ParamReaderTest extends TestCase { private $paramReader; + private static $validatorSupportsAnnotations = true; + + public static function setUpBeforeClass(): void + { + $validatorSupportsAnnotations = true; + + if (class_exists(InstalledVersions::class) && InstalledVersions::isInstalled('symfony/validator')) { + $validatorVersion = InstalledVersions::getVersion('symfony/validator'); + + $validatorSupportsAnnotations = null !== $validatorVersion && version_compare($validatorVersion, '7.0', '<'); + } + + self::$validatorSupportsAnnotations = $validatorSupportsAnnotations; + } + protected function setUp(): void { - $annotationReader = $this->getMockBuilder(AnnotationReader::class)->getMock(); - - $methodAnnotations = []; - $foo = $this->createMockedParam(); - $foo - ->expects($this->any()) - ->method('getName') - ->willReturn('foo'); - $methodAnnotations[] = $foo; - - $bar = $this->createMockedParam(); - $bar - ->expects($this->any()) - ->method('getName') - ->willReturn('bar'); - $methodAnnotations[] = $bar; - - $methodAnnotations[] = new View([]); - - $annotationReader - ->expects($this->any()) - ->method('getMethodAnnotations') - ->will($this->returnValue($methodAnnotations)); - - $classAnnotations = []; - - $baz = $this->createMockedParam(); - $baz - ->expects($this->any()) - ->method('getName') - ->willReturn('baz'); - $classAnnotations[] = $baz; - - $mikz = $this->createMockedParam(); - $mikz - ->expects($this->any()) - ->method('getName') - ->willReturn('micz'); - $classAnnotations[] = $mikz; - - $classAnnotations[] = new View([]); - - $annotationReader - ->expects($this->any()) - ->method('getClassAnnotations') - ->will($this->returnValue($classAnnotations)); - - $this->paramReader = new ParamReader($annotationReader); + // An annotation reader is only injected when `doctrine/annotations` is installed and `symfony/validator` is installed at a version supporting annotations + if (interface_exists(Reader::class) && self::$validatorSupportsAnnotations) { + $this->paramReader = new ParamReader(new AnnotationReader()); + } else { + $this->paramReader = new ParamReader(); + } } - /** - * Test that only ParamInterface annotations are returned. - */ - public function testReadsOnlyParamAnnotations() + public function testReadsAnnotations() { - $annotations = $this->paramReader->read(new \ReflectionClass(__CLASS__), 'setUp'); + if (PHP_MAJOR_VERSION === 8 && PHP_MINOR_VERSION === 0) { + $this->markTestSkipped('Test fixture contains attributes not parsable on PHP 8.0.'); + } - $this->assertCount(4, $annotations); + if (!interface_exists(Reader::class)) { + $this->markTestSkipped('Test requires doctrine/annotations'); + } - foreach ($annotations as $name => $annotation) { - $this->assertInstanceOf(ParamInterface::class, $annotation); - $this->assertEquals($annotation->getName(), $name); + if (!self::$validatorSupportsAnnotations) { + $this->markTestSkipped('Test requires symfony/validator:<7.0'); } - } - /** - * @requires PHP 8 - */ - public function testReadsAttributes() - { - $paramReader = new ParamReader(); - $params = $paramReader->read(new \ReflectionClass(ParamsAnnotatedController::class), 'getArticlesAttributesAction'); + $params = $this->paramReader->read(new \ReflectionClass(ParamsAnnotatedController::class), 'getArticlesAction'); $this->assertCount(6, $params); + foreach ($params as $name => $param) { + $this->assertInstanceOf(ParamInterface::class, $param); + $this->assertEquals($param->getName(), $name); + } + // Param 1 (query) $this->assertArrayHasKey('page', $params); $this->assertEquals('page', $params['page']->name); $this->assertEquals('\\d+', $params['page']->requirements); $this->assertEquals('1', $params['page']->default); - $this->assertEquals('Page of the overview', $params['page']->description); + $this->assertEquals('Page of the overview.', $params['page']->description); $this->assertFalse($params['page']->map); $this->assertFalse($params['page']->strict); @@ -124,7 +95,7 @@ public function testReadsAttributes() // Param 3 (query) $this->assertArrayHasKey('filters', $params); $this->assertEquals('filters', $params['filters']->name); - $this->assertFalse($params['filters']->map); + $this->assertTrue($params['filters']->map); // Param 4 (file) $this->assertArrayHasKey('avatar', $params); @@ -140,57 +111,62 @@ public function testReadsAttributes() $this->assertFalse($params['foo']->strict); } - public function testExceptionOnNonExistingMethod() + /** + * @requires PHP 8.1 + */ + public function testReadsAttributes() { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage(sprintf('Class "%s" has no method "foo".', self::class)); + $params = $this->paramReader->read(new \ReflectionClass(ParamsAnnotatedController::class), 'getArticlesAttributesAction'); - $this->paramReader->read(new \ReflectionClass(__CLASS__), 'foo'); - } - - public function testAnnotationReader() - { - $reader = new AnnotationReader(); + $this->assertCount(6, $params); - $method = new \ReflectionMethod(ParamsAnnotatedController::class, 'getArticlesAction'); - $params = $reader->getMethodAnnotations($method); + foreach ($params as $name => $param) { + $this->assertInstanceOf(ParamInterface::class, $param); + $this->assertEquals($param->getName(), $name); + } // Param 1 (query) - $this->assertEquals('page', $params[0]->name); - $this->assertEquals('\\d+', $params[0]->requirements); - $this->assertEquals('1', $params[0]->default); - $this->assertEquals('Page of the overview.', $params[0]->description); - $this->assertFalse($params[0]->map); - $this->assertFalse($params[0]->strict); + $this->assertArrayHasKey('page', $params); + $this->assertEquals('page', $params['page']->name); + $this->assertEquals('\\d+', $params['page']->requirements); + $this->assertEquals('1', $params['page']->default); + $this->assertEquals('Page of the overview.', $params['page']->description); + $this->assertFalse($params['page']->map); + $this->assertFalse($params['page']->strict); // Param 2 (request) - $this->assertEquals('byauthor', $params[1]->name); - $this->assertEquals('[a-z]+', $params[1]->requirements); - $this->assertEquals('by author', $params[1]->description); - $this->assertEquals(['search'], $params[1]->incompatibles); - $this->assertFalse($params[1]->map); - $this->assertTrue($params[1]->strict); + $this->assertArrayHasKey('byauthor', $params); + $this->assertEquals('byauthor', $params['byauthor']->name); + $this->assertEquals('[a-z]+', $params['byauthor']->requirements); + $this->assertEquals('by author', $params['byauthor']->description); + $this->assertEquals(['search'], $params['byauthor']->incompatibles); + $this->assertFalse($params['byauthor']->map); + $this->assertTrue($params['byauthor']->strict); // Param 3 (query) - $this->assertEquals('filters', $params[2]->name); - $this->assertTrue($params[2]->map); - $this->assertEquals(new NotNull(), $params[2]->requirements); + $this->assertArrayHasKey('filters', $params); + $this->assertEquals('filters', $params['filters']->name); + $this->assertTrue($params['filters']->map); // Param 4 (file) - $this->assertEquals('avatar', $params[3]->name); - $this->assertEquals(['mimeTypes' => 'application/json'], $params[3]->requirements); - $this->assertTrue($params[3]->image); - $this->assertTrue($params[3]->strict); + $this->assertArrayHasKey('avatar', $params); + $this->assertEquals('avatar', $params['avatar']->name); + $this->assertEquals(['mimeTypes' => 'application/json'], $params['avatar']->requirements); + $this->assertTrue($params['avatar']->image); + $this->assertTrue($params['avatar']->strict); // Param 5 (file) - $this->assertEquals('foo', $params[4]->name); - $this->assertEquals(new NotNull(), $params[4]->requirements); - $this->assertFalse($params[4]->image); - $this->assertFalse($params[4]->strict); + $this->assertArrayHasKey('foo', $params); + $this->assertEquals('foo', $params['foo']->name); + $this->assertFalse($params['foo']->image); + $this->assertFalse($params['foo']->strict); } - protected function createMockedParam() + public function testExceptionOnNonExistingMethod() { - return $this->getMockBuilder(ParamInterface::class)->getMock(); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf('Class "%s" has no method "foo".', self::class)); + + $this->paramReader->read(new \ReflectionClass(__CLASS__), 'foo'); } } diff --git a/Tests/Request/RequestBodyParamConverterTest.php b/Tests/Request/RequestBodyParamConverterTest.php index 372f29672..b0094c65b 100644 --- a/Tests/Request/RequestBodyParamConverterTest.php +++ b/Tests/Request/RequestBodyParamConverterTest.php @@ -34,23 +34,15 @@ class RequestBodyParamConverterTest extends TestCase protected $serializer; protected $converter; - protected function setUp(): void + public static function setUpBeforeClass(): void { - // skip the test if the installed version of SensioFrameworkExtraBundle - // is not compatible with the RequestBodyParamConverter class - $parameter = new \ReflectionParameter( - [ - ParamConverterInterface::class, - 'supports', - ], - 'configuration' - ); - if (ParamConverter::class != $parameter->getType()->getName()) { - $this->markTestSkipped( - 'skipping RequestBodyParamConverterTest due to an incompatible version of the SensioFrameworkExtraBundle' - ); + if (!class_exists(ParamConverterInterface::class)) { + self::markTestSkipped('Test requires sensio/framework-extra-bundle'); } + } + protected function setUp(): void + { $this->serializer = $this->getMockBuilder(Serializer::class)->getMock(); $this->converter = new RequestBodyParamConverter($this->serializer); } diff --git a/composer.json b/composer.json index d4cae108f..9738ffaca 100644 --- a/composer.json +++ b/composer.json @@ -29,46 +29,46 @@ ] }, "require": { - "php": "^7.2|^8.0", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", + "php": "^7.4|^8.0", + "symfony/config": "^5.4|^6.4|^7.0", + "symfony/dependency-injection": "^5.4|^6.4|^7.0", "symfony/deprecation-contracts": "^2.1|^3.0", - "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/framework-bundle": "^4.4.1|^5.0|^6.0", - "symfony/http-foundation": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/routing": "^5.4|^6.0", - "symfony/security-core": "^5.4|^6.0", + "symfony/event-dispatcher": "^5.4|^6.4|^7.0", + "symfony/framework-bundle": "^5.4|^6.4|^7.0", + "symfony/http-foundation": "^5.4|^6.4|^7.0", + "symfony/http-kernel": "^5.4|^6.4|^7.0", + "symfony/routing": "^5.4|^6.4|^7.0", + "symfony/security-core": "^5.4|^6.4|^7.0", "willdurand/jsonp-callback-validator": "^1.0|^2.0", "willdurand/negotiation": "^2.0|^3.0" }, "require-dev": { - "doctrine/annotations": "^1.13.2|^2.0 ", - "friendsofphp/php-cs-fixer": "^3.0", + "doctrine/annotations": "^1.13.2|^2.0", + "friendsofphp/php-cs-fixer": "^3.43", "jms/serializer": "^1.13|^2.0|^3.0", "jms/serializer-bundle": "^2.4.3|^3.0.1|^4.0|^5.0", "psr/http-message": "^1.0", "psr/log": "^1.0|^2.0|^3.0", "sensio/framework-extra-bundle": "^6.1", + "symfony/asset": "^5.4|^6.4|^7.0", + "symfony/browser-kit": "^5.4|^6.4|^7.0", + "symfony/css-selector": "^5.4|^6.4|^7.0", + "symfony/expression-language": "^5.4|^6.4|^7.0", + "symfony/form": "^5.4|^6.4|^7.0", + "symfony/mime": "^5.4|^6.4|^7.0", "symfony/phpunit-bridge": "^7.0.1", - "symfony/asset": "^5.4|^6.0", - "symfony/browser-kit": "^5.4|^6.0", - "symfony/css-selector": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/form": "^5.4|^6.0", - "symfony/mime": "^5.4|^6.0", - "symfony/security-bundle": "^5.4|^6.0", - "symfony/serializer": "^5.4|^6.0", - "symfony/twig-bundle": "^5.4|^6.0", - "symfony/validator": "^5.4|^6.0", - "symfony/web-profiler-bundle": "^5.4|^6.0", - "symfony/yaml": "^5.4|^6.0" + "symfony/security-bundle": "^5.4|^6.4|^7.0", + "symfony/serializer": "^5.4|^6.4|^7.0", + "symfony/twig-bundle": "^5.4|^6.4|^7.0", + "symfony/validator": "^5.4|^6.4|^7.0", + "symfony/web-profiler-bundle": "^5.4|^6.4|^7.0", + "symfony/yaml": "^5.4|^6.4|^7.0" }, "suggest": { - "sensio/framework-extra-bundle": "Add support for the request body converter and the view response listener, requires ^3.0", - "jms/serializer-bundle": "Add support for advanced serialization capabilities, recommended, requires ^2.0|^3.0", - "symfony/serializer": "Add support for basic serialization capabilities and xml decoding, requires ^2.7|^3.0", - "symfony/validator": "Add support for validation capabilities in the ParamFetcher, requires ^2.7|^3.0" + "jms/serializer-bundle": "Add support for advanced serialization capabilities, recommended", + "sensio/framework-extra-bundle": "Add support for the request body converter and the view response listener, not supported with Symfony >=7.0", + "symfony/serializer": "Add support for basic serialization capabilities and xml decoding", + "symfony/validator": "Add support for validation capabilities in the ParamFetcher" }, "conflict": { "doctrine/annotations": "<1.12", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index b66e830fb..09dcaefda 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,10 +1,16 @@ - + - - + + + @@ -13,14 +19,14 @@ - - + + ./ - - ./Resources - ./Tests - ./vendor - - - + + + ./Resources + ./Tests + ./vendor + +