diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 0a4dd2efe..e333810fc 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -53,6 +53,10 @@ jobs: composer-flags: "" can-fail: false symfony-require: "6.4.*" + - php-version: "8.5" + composer-flags: "" + can-fail: false + symfony-require: "6.4.*" - php-version: "8.3" composer-flags: "" can-fail: false @@ -68,6 +72,36 @@ jobs: can-fail: false 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: false + symfony-require: "7.0.*" + remove-sensio-bundle: yes # SensioFrameworkExtraBundle is not compatible with Symfony 7.0 or later + - php-version: "8.4" + composer-flags: "" + can-fail: false + symfony-require: "7.0.*" + remove-sensio-bundle: yes # SensioFrameworkExtraBundle is not compatible with Symfony 7.0 or later + - php-version: "8.4" + composer-flags: "" + can-fail: false + symfony-require: "7.4.*" + remove-sensio-bundle: yes # SensioFrameworkExtraBundle is not compatible with Symfony 7.0 or later + - php-version: "8.5" + composer-flags: "" + can-fail: false + symfony-require: "7.4.*" + remove-sensio-bundle: yes # SensioFrameworkExtraBundle is not compatible with Symfony 7.0 or later + - php-version: "8.4" + composer-flags: "" + can-fail: false + symfony-require: "8.0.*" + remove-sensio-bundle: yes # SensioFrameworkExtraBundle is not compatible with Symfony 7.0 or later + - php-version: "8.5" + composer-flags: "" + can-fail: false + symfony-require: "8.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 @@ -76,6 +110,10 @@ jobs: 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 + - php-version: "8.5" + 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 }} diff --git a/Controller/Annotations/AbstractScalarParam.php b/Controller/Annotations/AbstractScalarParam.php index 5243f48c1..646f32388 100644 --- a/Controller/Annotations/AbstractScalarParam.php +++ b/Controller/Annotations/AbstractScalarParam.php @@ -41,19 +41,19 @@ public function getConstraints() if ($this->requirements instanceof Constraint) { $constraints[] = $this->requirements; } elseif (is_scalar($this->requirements)) { - $constraints[] = new Regex([ - 'pattern' => '#^(?:'.$this->requirements.')$#xsu', - 'message' => sprintf( + $constraints[] = new Regex( + '#^(?:'.$this->requirements.')$#xsu', + sprintf( 'Parameter \'%s\' value, does not match requirements \'%s\'', $this->getName(), $this->requirements ), - ]); + ); } elseif (is_array($this->requirements) && isset($this->requirements['rule']) && $this->requirements['error_message']) { - $constraints[] = new Regex([ - 'pattern' => '#^(?:'.$this->requirements['rule'].')$#xsu', - 'message' => $this->requirements['error_message'], - ]); + $constraints[] = new Regex( + '#^(?:'.$this->requirements['rule'].')$#xsu', + $this->requirements['error_message'], + ); } elseif (is_array($this->requirements)) { foreach ($this->requirements as $index => $requirement) { if ($requirement instanceof Constraint) { @@ -75,9 +75,12 @@ public function getConstraints() // If the user wants to map the value, apply all constraints to every // value of the map if ($this->map) { - $constraints = [ - new All(['constraints' => $constraints]), - ]; + if ([] !== $constraints) { + $constraints = [ + new All($constraints), + ]; + } + if (false === $this->nullable) { $constraints[] = new NotNull(); } diff --git a/Controller/Annotations/FileParam.php b/Controller/Annotations/FileParam.php index 55e624db3..8b157475f 100644 --- a/Controller/Annotations/FileParam.php +++ b/Controller/Annotations/FileParam.php @@ -79,15 +79,19 @@ public function getConstraints() $options = is_array($this->requirements) ? $this->requirements : []; if ($this->image) { - $constraints[] = new Image($options); + $constraint = new Image(); } else { - $constraints[] = new File($options); + $constraint = new File(); } + foreach ($options as $name => $value) { + $constraint->$name = $value; + } + $constraints[] = $constraint; // If the user wants to map the value if ($this->map) { $constraints = [ - new All(['constraints' => $constraints]), + new All($constraints), ]; } diff --git a/Controller/Annotations/Route.php b/Controller/Annotations/Route.php index b3682e875..caac132a5 100644 --- a/Controller/Annotations/Route.php +++ b/Controller/Annotations/Route.php @@ -123,7 +123,11 @@ public function __construct( ); } - if (!$this->getMethods()) { + if (isset($this->methods)) { + if (!$this->methods) { + $this->methods = (array) $this->getMethod(); + } + } elseif (!$this->getMethods()) { $this->setMethods((array) $this->getMethod()); } } diff --git a/EventListener/AllowedMethodsListener.php b/EventListener/AllowedMethodsListener.php index fa88c8a78..ca10b141e 100644 --- a/EventListener/AllowedMethodsListener.php +++ b/EventListener/AllowedMethodsListener.php @@ -41,10 +41,10 @@ public function onKernelResponse(ResponseEvent $event): void $allowedMethods = $this->loader->getAllowedMethods(); - if (isset($allowedMethods[$event->getRequest()->get('_route')])) { + if (isset($allowedMethods[$event->getRequest()->attributes->get('_route')])) { $event->getResponse() ->headers - ->set('Allow', implode(', ', $allowedMethods[$event->getRequest()->get('_route')])); + ->set('Allow', implode(', ', $allowedMethods[$event->getRequest()->attributes->get('_route')])); } } } diff --git a/README.md b/README.md index 966629721..12e05d554 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,9 @@ applications with Symfony. Features include: compatible with RFC 7807 using the Symfony Serializer component or the JMS Serializer -[![Build Status](https://img.shields.io/github/workflow/status/FriendsOfSymfony/FOSRestBundle/CI?style=flat-square)](https://github.com/FriendsOfSymfony/FOSRestBundle/actions?query=workflow:CI) -[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/FriendsOfSymfony/FOSRestBundle/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/FriendsOfSymfony/FOSRestBundle/?branch=master) -[![Code Coverage](https://scrutinizer-ci.com/g/FriendsOfSymfony/FOSRestBundle/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/FriendsOfSymfony/FOSRestBundle/?branch=master) +[![Build Status](https://github.com/FriendsOfSymfony/FOSRestBundle/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/FriendsOfSymfony/FOSRestBundle/actions/workflows/continuous-integration.yml) [![Total Downloads](https://poser.pugx.org/FriendsOfSymfony/rest-bundle/downloads.svg)](https://packagist.org/packages/FriendsOfSymfony/rest-bundle) [![Latest Stable Version](https://poser.pugx.org/FriendsOfSymfony/rest-bundle/v/stable.svg)](https://packagist.org/packages/FriendsOfSymfony/rest-bundle) -[![SensioLabsInsight](https://insight.sensiolabs.com/projects/0be23389-2e85-49cf-b333-caaa36d11c62/mini.png)](https://insight.sensiolabs.com/projects/0be23389-2e85-49cf-b333-caaa36d11c62) Documentation ------------- diff --git a/Tests/Controller/Annotations/AbstractScalarParamTest.php b/Tests/Controller/Annotations/AbstractScalarParamTest.php index b5d35894f..1db1e7e4a 100644 --- a/Tests/Controller/Annotations/AbstractScalarParamTest.php +++ b/Tests/Controller/Annotations/AbstractScalarParamTest.php @@ -80,10 +80,10 @@ public function testScalarRequirements() $this->param->requirements = 'foo %bar% %%'; $this->assertEquals([ new NotNull(), - new Regex([ - 'pattern' => '#^(?:foo %bar% %%)$#xsu', - 'message' => "Parameter 'bar' value, does not match requirements 'foo %bar% %%'", - ]), + new Regex( + '#^(?:foo %bar% %%)$#xsu', + "Parameter 'bar' value, does not match requirements 'foo %bar% %%'", + ), ], $this->param->getConstraints()); } @@ -95,10 +95,10 @@ public function testArrayRequirements() ]; $this->assertEquals([ new NotNull(), - new Regex([ - 'pattern' => '#^(?:foo)$#xsu', - 'message' => 'bar', - ]), + new Regex( + '#^(?:foo)$#xsu', + 'bar', + ), ], $this->param->getConstraints()); } @@ -133,8 +133,6 @@ public function testArrayWithNoConstraintsDoesNotCreateInvalidConstraint() { $this->param->nullable = true; $this->param->map = true; - $this->assertEquals([new All([ - 'constraints' => [], - ])], $this->param->getConstraints()); + $this->assertEquals([], $this->param->getConstraints()); } } diff --git a/Tests/Controller/Annotations/FileParamTest.php b/Tests/Controller/Annotations/FileParamTest.php index 81f8bdd77..fe7773a71 100644 --- a/Tests/Controller/Annotations/FileParamTest.php +++ b/Tests/Controller/Annotations/FileParamTest.php @@ -75,19 +75,19 @@ public function testComplexRequirements() public function testFileRequirements() { $this->param->nullable = true; - $this->param->requirements = $requirements = ['mimeTypes' => 'application/json']; + $this->param->requirements = ['mimeTypes' => 'application/json']; $this->assertEquals([ - new File($requirements), + new File(null, null, null, 'application/json'), ], $this->param->getConstraints()); } public function testImageRequirements() { $this->param->image = true; - $this->param->requirements = $requirements = ['mimeTypes' => 'image/gif']; + $this->param->requirements = ['mimeTypes' => ['image/*']]; $this->assertEquals([ new NotNull(), - new Image($requirements), + new Image(null, null, null, ['image/*']), ], $this->param->getConstraints()); } @@ -95,20 +95,20 @@ public function testImageConstraintsTransformWhenParamIsAnArray() { $this->param->image = true; $this->param->map = true; - $this->param->requirements = $requirements = ['mimeTypes' => 'image/gif']; + $this->param->requirements = ['mimeTypes' => ['image/*']]; $this->assertEquals([new All([ new NotNull(), - new Image($requirements), + new Image(null, null, null, ['image/*']), ])], $this->param->getConstraints()); } public function testFileConstraintsWhenParamIsAnArray() { $this->param->map = true; - $this->param->requirements = $requirements = ['mimeTypes' => 'application/pdf']; + $this->param->requirements = ['mimeTypes' => 'application/pdf']; $this->assertEquals([new All([ new NotNull(), - new File($requirements), + new File(null, null, null, 'application/pdf'), ])], $this->param->getConstraints()); } } diff --git a/Tests/Controller/Annotations/RouteTest.php b/Tests/Controller/Annotations/RouteTest.php index 394e72c1e..7ee9f6c57 100644 --- a/Tests/Controller/Annotations/RouteTest.php +++ b/Tests/Controller/Annotations/RouteTest.php @@ -41,14 +41,16 @@ public function testCanInstantiate() $condition ); - $this->assertEquals($path, $route->getPath()); - $this->assertEquals($name, $route->getName()); - $this->assertEquals($requirements, $route->getRequirements()); - $this->assertEquals($options, $route->getOptions()); - $this->assertEquals($defaults, $route->getDefaults()); - $this->assertEquals($host, $route->getHost()); - $this->assertEquals($methods, $route->getMethods()); - $this->assertEquals($schemes, $route->getSchemes()); - $this->assertEquals($condition, $route->getCondition()); + $isPublic = isset($route->methods); + + $this->assertEquals($path, $isPublic ? $route->path : $route->getPath()); + $this->assertEquals($name, $isPublic ? $route->name : $route->getName()); + $this->assertEquals($requirements, $isPublic ? $route->requirements : $route->getRequirements()); + $this->assertEquals($options, $isPublic ? $route->options : $route->getOptions()); + $this->assertEquals($defaults, $isPublic ? $route->defaults : $route->getDefaults()); + $this->assertEquals($host, $isPublic ? $route->host : $route->getHost()); + $this->assertEquals($methods, $isPublic ? $route->methods : $route->getMethods()); + $this->assertEquals($schemes, $isPublic ? $route->schemes : $route->getSchemes()); + $this->assertEquals($condition, $isPublic ? $route->condition : $route->getCondition()); } } diff --git a/Tests/Fixtures/Annotations/IdenticalToRequestParam.php b/Tests/Fixtures/Annotations/IdenticalToRequestParam.php index 45b1f97e9..0935c605f 100644 --- a/Tests/Fixtures/Annotations/IdenticalToRequestParam.php +++ b/Tests/Fixtures/Annotations/IdenticalToRequestParam.php @@ -11,6 +11,7 @@ namespace FOS\RestBundle\Tests\Fixtures\Annotations; +use Composer\InstalledVersions; use FOS\RestBundle\Controller\Annotations\RequestParam; use Symfony\Component\Validator\Constraints\IdenticalTo; @@ -41,6 +42,21 @@ public function __construct( bool $nullable = false, bool $allowBlank = true ) { - parent::__construct($name, $key, null !== $identicalTo ? new IdenticalTo($identicalTo) : null, $default, $description, $incompatibles, $strict, $map, $nullable, $allowBlank); + $validatorSupportsArrayConfig = true; + if (class_exists(InstalledVersions::class)) { + $validatorVersion = InstalledVersions::getVersion('symfony/validator'); + + $validatorSupportsArrayConfig = version_compare($validatorVersion, '8.0', '<'); + } + + if (null === $identicalTo) { + $options = null; + } elseif ($validatorSupportsArrayConfig) { + $options = $identicalTo; + } else { + $options = $identicalTo['value']; + } + + parent::__construct($name, $key, null !== $options ? new IdenticalTo($options) : null, $default, $description, $incompatibles, $strict, $map, $nullable, $allowBlank); } } diff --git a/Tests/Negotiation/FormatNegotiatorTest.php b/Tests/Negotiation/FormatNegotiatorTest.php index 0a92a2014..6b89c6d9a 100644 --- a/Tests/Negotiation/FormatNegotiatorTest.php +++ b/Tests/Negotiation/FormatNegotiatorTest.php @@ -112,7 +112,9 @@ public function testGetBestWithPreferExtension() $reflectionClass = new \ReflectionClass(get_class($this->request)); $reflectionProperty = $reflectionClass->getProperty('pathInfo'); - $reflectionProperty->setAccessible(true); + if (PHP_VERSION_ID < 80100) { + $reflectionProperty->setAccessible(true); + } $reflectionProperty->setValue($this->request, '/file.json'); // Without extension mime-type in Accept header @@ -135,7 +137,9 @@ public function testGetBestWithPreferExtensionAndUnknownExtension() $reflectionClass = new \ReflectionClass(get_class($this->request)); $reflectionProperty = $reflectionClass->getProperty('pathInfo'); - $reflectionProperty->setAccessible(true); + if (PHP_VERSION_ID < 80100) { + $reflectionProperty->setAccessible(true); + } $reflectionProperty->setValue($this->request, '/file.123456789'); $this->request->headers->set('Accept', 'text/html, application/json'); diff --git a/Tests/Request/RequestBodyParamConverterTest.php b/Tests/Request/RequestBodyParamConverterTest.php index b0094c65b..05bf67a03 100644 --- a/Tests/Request/RequestBodyParamConverterTest.php +++ b/Tests/Request/RequestBodyParamConverterTest.php @@ -194,7 +194,9 @@ public function testContextConfiguration() ]; $contextConfigurationMethod = new \ReflectionMethod($converter, 'configureContext'); - $contextConfigurationMethod->setAccessible(true); + if (PHP_VERSION_ID < 80100) { + $contextConfigurationMethod->setAccessible(true); + } $contextConfigurationMethod->invoke($converter, $context = new Context(), $options); $expectedContext = new Context(); @@ -225,7 +227,9 @@ public function testValidatorOptionsGetter() ]; $validatorMethod = new \ReflectionMethod($converter, 'getValidatorOptions'); - $validatorMethod->setAccessible(true); + if (PHP_VERSION_ID < 80100) { + $validatorMethod->setAccessible(true); + } $this->assertEquals(['groups' => ['foo'], 'traverse' => true, 'deep' => false], $validatorMethod->invoke($converter, $options1)); $this->assertEquals(['groups' => false, 'traverse' => false, 'deep' => true], $validatorMethod->invoke($converter, $options2)); } diff --git a/Tests/View/ViewHandlerTest.php b/Tests/View/ViewHandlerTest.php index 26ece5058..f5f340cae 100644 --- a/Tests/View/ViewHandlerTest.php +++ b/Tests/View/ViewHandlerTest.php @@ -308,7 +308,9 @@ public function testSerializeNullDataValues($expected, $serializeNull) $viewHandler->setSerializeNullStrategy($serializeNull); $contextMethod = new \ReflectionMethod($viewHandler, 'getSerializationContext'); - $contextMethod->setAccessible(true); + if (PHP_VERSION_ID < 80100) { + $contextMethod->setAccessible(true); + } $view = new View(); $context = $contextMethod->invoke($viewHandler, $view); @@ -384,7 +386,9 @@ public function testConfigurableViewHandlerInterface() $viewHandler->setSerializeNullStrategy(true); $contextMethod = new \ReflectionMethod($viewHandler, 'getSerializationContext'); - $contextMethod->setAccessible(true); + if (PHP_VERSION_ID < 80100) { + $contextMethod->setAccessible(true); + } $view = new View(); $context = $contextMethod->invoke($viewHandler, $view); diff --git a/composer.json b/composer.json index 9738ffaca..1d0aa6c18 100644 --- a/composer.json +++ b/composer.json @@ -30,15 +30,15 @@ }, "require": { "php": "^7.4|^8.0", - "symfony/config": "^5.4|^6.4|^7.0", - "symfony/dependency-injection": "^5.4|^6.4|^7.0", + "symfony/config": "^5.4|^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^5.4|^6.4|^7.0|^8.0", "symfony/deprecation-contracts": "^2.1|^3.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", + "symfony/event-dispatcher": "^5.4|^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^5.4|^6.4|^7.0|^8.0", + "symfony/http-foundation": "^5.4|^6.4|^7.0|^8.0", + "symfony/http-kernel": "^5.4|^6.4|^7.0|^8.0", + "symfony/routing": "^5.4|^6.4|^7.0|^8.0", + "symfony/security-core": "^5.4|^6.4|^7.0|^8.0", "willdurand/jsonp-callback-validator": "^1.0|^2.0", "willdurand/negotiation": "^2.0|^3.0" }, @@ -50,19 +50,19 @@ "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/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" + "symfony/asset": "^5.4|^6.4|^7.0|^8.0", + "symfony/browser-kit": "^5.4|^6.4|^7.0|^8.0", + "symfony/css-selector": "^5.4|^6.4|^7.0|^8.0", + "symfony/expression-language": "^5.4|^6.4|^7.0|^8.0", + "symfony/form": "^5.4|^6.4|^7.0|^8.0", + "symfony/mime": "^5.4|^6.4|^7.0|^8.0", + "symfony/phpunit-bridge": "^7.0.1|^8.0", + "symfony/security-bundle": "^5.4|^6.4|^7.0|^8.0", + "symfony/serializer": "^5.4|^6.4|^7.0|^8.0", + "symfony/twig-bundle": "^5.4|^6.4|^7.0|^8.0", + "symfony/validator": "^5.4|^6.4|^7.0|^8.0", + "symfony/web-profiler-bundle": "^5.4|^6.4|^7.0|^8.0", + "symfony/yaml": "^5.4|^6.4|^7.0|^8.0" }, "suggest": { "jms/serializer-bundle": "Add support for advanced serialization capabilities, recommended",