Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
98491a5
Update AbstractScalarParam.php
dev-craftec May 23, 2025
aa58577
Update AbstractScalarParam.php
dev-craftec May 23, 2025
a0b160a
Fix deprecations in symfony/validator, including the fix in #2417
tjveldhuizen Jul 23, 2025
f3e6058
Fix MimeTypeTest for Symfony 7.4
alexander-schranz Nov 20, 2025
115b818
Don't use deprecated `#[Route]` methods
HypeMC Nov 18, 2025
8295112
Merge pull request #2423 from alexander-schranz/bugfix/test-mime-type
goetas Dec 20, 2025
8ce9c0c
Merge pull request #2422 from HypeMC/dont-use-deprecated-methods
goetas Dec 20, 2025
6c246b7
Merge pull request #2417 from dev-craftec/bugfix/regex-validator-cons…
goetas Dec 20, 2025
d5f98e5
Merge pull request #2418 from tjveldhuizen/validator-deprecations
goetas Dec 20, 2025
a78c127
Switch to php for service configurations
W0rma Oct 29, 2025
c3be05f
Merge pull request #2421 from W0rma/replace-xml-config
goetas Dec 20, 2025
02d0e33
Fix usage of deprecated method Request::get()
W0rma Dec 21, 2025
13213ab
Fix deprecated usages of symfony/validator
W0rma Dec 21, 2025
ce7380c
Add support for symfony 8
W0rma Dec 21, 2025
bd9acb0
Merge pull request #2427 from W0rma/symfony8
goetas Dec 21, 2025
d71a83c
Test against PHP 8.5
W0rma Dec 20, 2025
c7a4615
Fix noop call of deprecated setAccessible() in PHP > 8.0
W0rma Dec 21, 2025
ee64e63
Merge pull request #2426 from W0rma/php85
goetas Dec 21, 2025
5dce860
Fix build status badge
W0rma Dec 22, 2025
438a54d
Remove broken SensioLabsInsight badge
W0rma Dec 22, 2025
c93f1e7
Remove outdated scrutinizer badges
W0rma Dec 22, 2025
342632e
Merge pull request #2428 from W0rma/fix-readme-badge
goetas Dec 22, 2025
0cb1733
Allow usage with symfony/config v8
W0rma Dec 22, 2025
113cb1b
Merge remote-tracking branch 'upstream/3.x' into symfony8-support
nikola-jovanovic-php Jan 14, 2026
43ff4d5
Merge remote-tracking branch 'w0rma/fix-symfony8' into symfony8-support
nikola-jovanovic-php Jan 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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 }}
Expand Down
25 changes: 14 additions & 11 deletions Controller/Annotations/AbstractScalarParam.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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();
}
Expand Down
10 changes: 7 additions & 3 deletions Controller/Annotations/FileParam.php
Original file line number Diff line number Diff line change
Expand Up @@ -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),
];
}

Expand Down
6 changes: 5 additions & 1 deletion Controller/Annotations/Route.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
Expand Down
4 changes: 2 additions & 2 deletions EventListener/AllowedMethodsListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -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')]));
}
}
}
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------------
Expand Down
20 changes: 9 additions & 11 deletions Tests/Controller/Annotations/AbstractScalarParamTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}

Expand All @@ -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());
}

Expand Down Expand Up @@ -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());
}
}
16 changes: 8 additions & 8 deletions Tests/Controller/Annotations/FileParamTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,40 +75,40 @@ 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'),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The constructor for Symfony\Component\Validator\Constraints\File has changed in newer Symfony versions. The current instantiation new File(null, null, null, 'application/json') is incorrect as it passes the mime type as the 4th argument, which corresponds to mimeTypesMessage. It should be the 3rd argument (mimeTypes).

            new File(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/*']),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Similar to the File constraint, the constructor for Symfony\Component\Validator\Constraints\Image has changed. The current instantiation new Image(null, null, null, ['image/*']) is incorrect. The mime types should be passed as the 3rd argument.

            new Image(null, null, ['image/*']),

], $this->param->getConstraints());
}

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/*']),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The instantiation of the Image constraint inside All is incorrect. The mime types should be the 3rd argument to the constructor, not the 4th.

            new Image(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'),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The instantiation of the File constraint inside All is incorrect. The mime type should be the 3rd argument to the constructor, not the 4th.

            new File(null, null, 'application/pdf'),

])], $this->param->getConstraints());
}
}
20 changes: 11 additions & 9 deletions Tests/Controller/Annotations/RouteTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
18 changes: 17 additions & 1 deletion Tests/Fixtures/Annotations/IdenticalToRequestParam.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
}
}
8 changes: 6 additions & 2 deletions Tests/Negotiation/FormatNegotiatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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');
Expand Down
8 changes: 6 additions & 2 deletions Tests/Request/RequestBodyParamConverterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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));
}
Expand Down
8 changes: 6 additions & 2 deletions Tests/View/ViewHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Loading