diff --git a/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php b/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php
index f6febb2a7e..3ec525d2cf 100644
--- a/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php
+++ b/src/Symfony/Bridge/Doctrine/Messenger/DoctrinePingConnectionMiddleware.php
@@ -11,6 +11,7 @@
namespace Symfony\Bridge\Doctrine\Messenger;
+use Doctrine\DBAL\DBALException;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Middleware\StackInterface;
@@ -36,7 +37,9 @@ class DoctrinePingConnectionMiddleware extends AbstractDoctrineMiddleware
{
$connection = $entityManager->getConnection();
- if (!$connection->ping()) {
+ try {
+ $connection->query($connection->getDatabasePlatform()->getDummySelectSQL());
+ } catch (DBALException $e) {
$connection->close();
$connection->connect();
}
diff --git a/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrinePingConnectionMiddlewareTest.php b/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrinePingConnectionMiddlewareTest.php
index a0932371df..e491558a2a 100644
--- a/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrinePingConnectionMiddlewareTest.php
+++ b/src/Symfony/Bridge/Doctrine/Tests/Messenger/DoctrinePingConnectionMiddlewareTest.php
@@ -12,6 +12,7 @@
namespace Symfony\Bridge\Doctrine\Tests\Messenger;
use Doctrine\DBAL\Connection;
+use Doctrine\DBAL\DBALException;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Bridge\Doctrine\Messenger\DoctrinePingConnectionMiddleware;
@@ -47,8 +48,8 @@ class DoctrinePingConnectionMiddlewareTest extends MiddlewareTestCase
public function testMiddlewarePingOk()
{
$this->connection->expects($this->once())
- ->method('ping')
- ->willReturn(false);
+ ->method('getDatabasePlatform')
+ ->will($this->throwException(new DBALException()));
$this->connection->expects($this->once())
->method('close')
@@ -65,6 +66,10 @@ class DoctrinePingConnectionMiddlewareTest extends MiddlewareTestCase
public function testMiddlewarePingResetEntityManager()
{
+ $this->connection->expects($this->once())
+ ->method('getDatabasePlatform')
+ ->will($this->throwException(new DBALException()));
+
$this->entityManager->expects($this->once())
->method('isOpen')
->willReturn(false)
diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV9.php b/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV9.php
index aa48ca5f24..2511380257 100644
--- a/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV9.php
+++ b/src/Symfony/Bridge/PhpUnit/Legacy/CommandForV9.php
@@ -12,9 +12,11 @@
namespace Symfony\Bridge\PhpUnit\Legacy;
use PHPUnit\TextUI\Command as BaseCommand;
-use PHPUnit\TextUI\Configuration\Configuration;
+use PHPUnit\TextUI\Configuration\Configuration as LegacyConfiguration;
use PHPUnit\TextUI\Configuration\Registry;
use PHPUnit\TextUI\TestRunner as BaseRunner;
+use PHPUnit\TextUI\XmlConfiguration\Configuration;
+use PHPUnit\TextUI\XmlConfiguration\Loader;
use Symfony\Bridge\PhpUnit\SymfonyTestsListener;
/**
@@ -43,9 +45,13 @@ class CommandForV9 extends BaseCommand
if (isset($this->arguments['configuration'])) {
$configuration = $this->arguments['configuration'];
- if (!$configuration instanceof Configuration) {
+
+ if (!class_exists(Configuration::class) && !$configuration instanceof LegacyConfiguration) {
$configuration = Registry::getInstance()->get($this->arguments['configuration']);
+ } elseif (class_exists(Configuration::class) && !$configuration instanceof Configuration) {
+ $configuration = (new Loader())->load($this->arguments['configuration']);
}
+
foreach ($configuration->listeners() as $registeredListener) {
if ('Symfony\Bridge\PhpUnit\SymfonyTestsListener' === ltrim($registeredListener->className(), '\\')) {
$registeredLocally = true;
diff --git a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd
index b50fdbdac9..03988686ba 100644
--- a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd
+++ b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd
@@ -289,6 +289,7 @@
+
diff --git a/src/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php b/src/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php
index 2dbe47851d..97e16caea8 100644
--- a/src/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php
+++ b/src/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php
@@ -97,6 +97,7 @@ class ValidatorTypeGuesser implements FormTypeGuesserInterface
case 'long':
return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\IntegerType', [], Guess::MEDIUM_CONFIDENCE);
+ case \DateTime::class:
case '\DateTime':
return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\DateType', [], Guess::MEDIUM_CONFIDENCE);
diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorTypeGuesserTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorTypeGuesserTest.php
index 2b50c7cc2f..c4256a315a 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorTypeGuesserTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorTypeGuesserTest.php
@@ -12,9 +12,17 @@
namespace Symfony\Component\Form\Tests\Extension\Validator;
use PHPUnit\Framework\TestCase;
+use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
+use Symfony\Component\Form\Extension\Core\Type\CollectionType;
+use Symfony\Component\Form\Extension\Core\Type\DateType;
+use Symfony\Component\Form\Extension\Core\Type\IntegerType;
+use Symfony\Component\Form\Extension\Core\Type\NumberType;
+use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Validator\ValidatorTypeGuesser;
use Symfony\Component\Form\Guess\Guess;
+use Symfony\Component\Form\Guess\TypeGuess;
use Symfony\Component\Form\Guess\ValueGuess;
+use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\File;
use Symfony\Component\Validator\Constraints\IsTrue;
@@ -60,6 +68,35 @@ class ValidatorTypeGuesserTest extends TestCase
$this->guesser = new ValidatorTypeGuesser($this->metadataFactory);
}
+ /**
+ * @dataProvider guessTypeProvider
+ */
+ public function testGuessType(Constraint $constraint, TypeGuess $guess)
+ {
+ $this->metadata->addPropertyConstraint(self::TEST_PROPERTY, $constraint);
+
+ $this->assertEquals($guess, $this->guesser->guessType(self::TEST_CLASS, self::TEST_PROPERTY));
+ }
+
+ public function guessTypeProvider()
+ {
+ return [
+ [new Type('array'), new TypeGuess(CollectionType::class, [], Guess::MEDIUM_CONFIDENCE)],
+ [new Type('bool'), new TypeGuess(CheckboxType::class, [], Guess::MEDIUM_CONFIDENCE)],
+ [new Type('boolean'), new TypeGuess(CheckboxType::class, [], Guess::MEDIUM_CONFIDENCE)],
+ [new Type('double'), new TypeGuess(NumberType::class, [], Guess::MEDIUM_CONFIDENCE)],
+ [new Type('float'), new TypeGuess(NumberType::class, [], Guess::MEDIUM_CONFIDENCE)],
+ [new Type('numeric'), new TypeGuess(NumberType::class, [], Guess::MEDIUM_CONFIDENCE)],
+ [new Type('real'), new TypeGuess(NumberType::class, [], Guess::MEDIUM_CONFIDENCE)],
+ [new Type('int'), new TypeGuess(IntegerType::class, [], Guess::MEDIUM_CONFIDENCE)],
+ [new Type('integer'), new TypeGuess(IntegerType::class, [], Guess::MEDIUM_CONFIDENCE)],
+ [new Type('long'), new TypeGuess(IntegerType::class, [], Guess::MEDIUM_CONFIDENCE)],
+ [new Type('string'), new TypeGuess(TextType::class, [], Guess::LOW_CONFIDENCE)],
+ [new Type(\DateTime::class), new TypeGuess(DateType::class, [], Guess::MEDIUM_CONFIDENCE)],
+ [new Type('\DateTime'), new TypeGuess(DateType::class, [], Guess::MEDIUM_CONFIDENCE)],
+ ];
+ }
+
public function guessRequiredProvider()
{
return [
diff --git a/src/Symfony/Component/HttpClient/Internal/HttplugWaitLoop.php b/src/Symfony/Component/HttpClient/Internal/HttplugWaitLoop.php
index 2feafdb0bd..885721add1 100644
--- a/src/Symfony/Component/HttpClient/Internal/HttplugWaitLoop.php
+++ b/src/Symfony/Component/HttpClient/Internal/HttplugWaitLoop.php
@@ -17,6 +17,7 @@ use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Symfony\Component\HttpClient\Response\CommonResponseTrait;
use Symfony\Component\HttpClient\Response\StreamWrapper;
+use Symfony\Component\HttpClient\Response\TraceableResponse;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
@@ -119,7 +120,7 @@ final class HttplugWaitLoop
}
}
- if (isset(class_uses($response)[CommonResponseTrait::class])) {
+ if ($response instanceof TraceableResponse || isset(class_uses($response)[CommonResponseTrait::class])) {
$body = $this->streamFactory->createStreamFromResource($response->toStream(false));
} elseif (!$buffer) {
$body = $this->streamFactory->createStreamFromResource(StreamWrapper::createResource($response, $this->client));
diff --git a/src/Symfony/Component/HttpClient/Psr18Client.php b/src/Symfony/Component/HttpClient/Psr18Client.php
index 2e952b06ed..83492b39eb 100644
--- a/src/Symfony/Component/HttpClient/Psr18Client.php
+++ b/src/Symfony/Component/HttpClient/Psr18Client.php
@@ -29,6 +29,7 @@ use Psr\Http\Message\UriFactoryInterface;
use Psr\Http\Message\UriInterface;
use Symfony\Component\HttpClient\Response\CommonResponseTrait;
use Symfony\Component\HttpClient\Response\StreamWrapper;
+use Symfony\Component\HttpClient\Response\TraceableResponse;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
@@ -104,7 +105,7 @@ final class Psr18Client implements ClientInterface, RequestFactoryInterface, Str
}
}
- $body = isset(class_uses($response)[CommonResponseTrait::class]) ? $response->toStream(false) : StreamWrapper::createResource($response, $this->client);
+ $body = $response instanceof TraceableResponse || isset(class_uses($response)[CommonResponseTrait::class]) ? $response->toStream(false) : StreamWrapper::createResource($response, $this->client);
$body = $this->streamFactory->createStreamFromResource($body);
if ($body->isSeekable()) {
diff --git a/src/Symfony/Component/HttpClient/Response/StreamWrapper.php b/src/Symfony/Component/HttpClient/Response/StreamWrapper.php
index 210fcf6c4d..ce80e32d1f 100644
--- a/src/Symfony/Component/HttpClient/Response/StreamWrapper.php
+++ b/src/Symfony/Component/HttpClient/Response/StreamWrapper.php
@@ -49,7 +49,7 @@ class StreamWrapper
*/
public static function createResource(ResponseInterface $response, HttpClientInterface $client = null)
{
- if (\is_callable([$response, 'toStream']) && isset(class_uses($response)[CommonResponseTrait::class])) {
+ if ($response instanceof TraceableResponse || (\is_callable([$response, 'toStream']) && isset(class_uses($response)[CommonResponseTrait::class]))) {
$stack = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS, 2);
if ($response !== ($stack[1]['object'] ?? null)) {
diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php
index 6567692f9b..3712b0dd94 100644
--- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php
+++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php
@@ -130,7 +130,7 @@ class Connection implements ResetInterface
'available_at' => '?',
]);
- $this->executeQuery($queryBuilder->getSQL(), [
+ $this->executeUpdate($queryBuilder->getSQL(), [
$body,
json_encode($headers),
$this->configuration['queue_name'],
@@ -181,7 +181,7 @@ class Connection implements ResetInterface
->set('delivered_at', '?')
->where('id = ?');
$now = new \DateTime();
- $this->executeQuery($queryBuilder->getSQL(), [
+ $this->executeUpdate($queryBuilder->getSQL(), [
$now,
$doctrineEnvelope['id'],
], [
@@ -338,6 +338,25 @@ class Connection implements ResetInterface
return $stmt;
}
+ private function executeUpdate(string $sql, array $parameters = [], array $types = [])
+ {
+ try {
+ $stmt = $this->driverConnection->executeUpdate($sql, $parameters, $types);
+ } catch (TableNotFoundException $e) {
+ if ($this->driverConnection->isTransactionActive()) {
+ throw $e;
+ }
+
+ // create table
+ if ($this->autoSetup) {
+ $this->setup();
+ }
+ $stmt = $this->driverConnection->executeUpdate($sql, $parameters, $types);
+ }
+
+ return $stmt;
+ }
+
private function getSchema(): Schema
{
$schema = new Schema([], [], $this->driverConnection->getSchemaManager()->createSchemaConfig());
diff --git a/src/Symfony/Component/Mime/FileinfoMimeTypeGuesser.php b/src/Symfony/Component/Mime/FileinfoMimeTypeGuesser.php
index b91a4ffeac..3028159858 100644
--- a/src/Symfony/Component/Mime/FileinfoMimeTypeGuesser.php
+++ b/src/Symfony/Component/Mime/FileinfoMimeTypeGuesser.php
@@ -57,7 +57,13 @@ class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface
if (false === $finfo = new \finfo(FILEINFO_MIME_TYPE, $this->magicFile)) {
return null;
}
+ $mimeType = $finfo->file($path);
- return $finfo->file($path);
+ if ($mimeType && 0 === (\strlen($mimeType) % 2)) {
+ $mimeStart = substr($mimeType, 0, \strlen($mimeType) >> 1);
+ $mimeType = $mimeStart.$mimeStart === $mimeType ? $mimeStart : $mimeType;
+ }
+
+ return $mimeType;
}
}
diff --git a/src/Symfony/Component/Mime/Tests/AbstractMimeTypeGuesserTest.php b/src/Symfony/Component/Mime/Tests/AbstractMimeTypeGuesserTest.php
index 70e419c847..6b5de034b3 100644
--- a/src/Symfony/Component/Mime/Tests/AbstractMimeTypeGuesserTest.php
+++ b/src/Symfony/Component/Mime/Tests/AbstractMimeTypeGuesserTest.php
@@ -79,6 +79,11 @@ abstract class AbstractMimeTypeGuesserTest extends TestCase
$this->assertEquals('application/octet-stream', $this->getGuesser()->guessMimeType(__DIR__.'/Fixtures/mimetypes/.unknownextension'));
}
+ public function testGuessWithDuplicatedFileType()
+ {
+ $this->assertEquals('application/vnd.openxmlformats-officedocument.wordprocessingml.document', $this->getGuesser()->guessMimeType(__DIR__.'/Fixtures/test.docx'));
+ }
+
public function testGuessWithIncorrectPath()
{
if (!$this->getGuesser()->isGuesserSupported()) {
diff --git a/src/Symfony/Component/Mime/Tests/Fixtures/test.docx b/src/Symfony/Component/Mime/Tests/Fixtures/test.docx
new file mode 100644
index 0000000000..2e86b6fcea
Binary files /dev/null and b/src/Symfony/Component/Mime/Tests/Fixtures/test.docx differ
diff --git a/src/Symfony/Component/Security/Core/Authorization/Voter/Voter.php b/src/Symfony/Component/Security/Core/Authorization/Voter/Voter.php
index 1ef95d0e97..f6e853a604 100644
--- a/src/Symfony/Component/Security/Core/Authorization/Voter/Voter.php
+++ b/src/Symfony/Component/Security/Core/Authorization/Voter/Voter.php
@@ -30,8 +30,21 @@ abstract class Voter implements VoterInterface
$vote = self::ACCESS_ABSTAIN;
foreach ($attributes as $attribute) {
- if (!$this->supports($attribute, $subject)) {
- continue;
+ try {
+ if (!$this->supports($attribute, $subject)) {
+ continue;
+ }
+ } catch (\TypeError $e) {
+ if (\PHP_VERSION_ID < 80000) {
+ if (0 === strpos($e->getMessage(), 'Argument 1 passed to')
+ && false !== strpos($e->getMessage(), '::supports() must be of the type string')) {
+ continue;
+ }
+ } elseif (false !== strpos($e->getMessage(), 'supports(): Argument #1')) {
+ continue;
+ }
+
+ throw $e;
}
// as soon as at least one attribute is supported, default is to deny access
diff --git a/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/VoterTest.php b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/VoterTest.php
index a4078298c9..dc1d391477 100644
--- a/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/VoterTest.php
+++ b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/VoterTest.php
@@ -27,34 +27,49 @@ class VoterTest extends TestCase
public function getTests()
{
+ $voter = new VoterTest_Voter();
+ $integerVoter = new IntegerVoterTest_Voter();
+
return [
- [['EDIT'], VoterInterface::ACCESS_GRANTED, new \stdClass(), 'ACCESS_GRANTED if attribute and class are supported and attribute grants access'],
- [['CREATE'], VoterInterface::ACCESS_DENIED, new \stdClass(), 'ACCESS_DENIED if attribute and class are supported and attribute does not grant access'],
+ [$voter, ['EDIT'], VoterInterface::ACCESS_GRANTED, new \stdClass(), 'ACCESS_GRANTED if attribute and class are supported and attribute grants access'],
+ [$voter, ['CREATE'], VoterInterface::ACCESS_DENIED, new \stdClass(), 'ACCESS_DENIED if attribute and class are supported and attribute does not grant access'],
- [['DELETE', 'EDIT'], VoterInterface::ACCESS_GRANTED, new \stdClass(), 'ACCESS_GRANTED if one attribute is supported and grants access'],
- [['DELETE', 'CREATE'], VoterInterface::ACCESS_DENIED, new \stdClass(), 'ACCESS_DENIED if one attribute is supported and denies access'],
+ [$voter, ['DELETE', 'EDIT'], VoterInterface::ACCESS_GRANTED, new \stdClass(), 'ACCESS_GRANTED if one attribute is supported and grants access'],
+ [$voter, ['DELETE', 'CREATE'], VoterInterface::ACCESS_DENIED, new \stdClass(), 'ACCESS_DENIED if one attribute is supported and denies access'],
- [['CREATE', 'EDIT'], VoterInterface::ACCESS_GRANTED, new \stdClass(), 'ACCESS_GRANTED if one attribute grants access'],
+ [$voter, ['CREATE', 'EDIT'], VoterInterface::ACCESS_GRANTED, new \stdClass(), 'ACCESS_GRANTED if one attribute grants access'],
- [['DELETE'], VoterInterface::ACCESS_ABSTAIN, new \stdClass(), 'ACCESS_ABSTAIN if no attribute is supported'],
+ [$voter, ['DELETE'], VoterInterface::ACCESS_ABSTAIN, new \stdClass(), 'ACCESS_ABSTAIN if no attribute is supported'],
- [['EDIT'], VoterInterface::ACCESS_ABSTAIN, $this, 'ACCESS_ABSTAIN if class is not supported'],
+ [$voter, ['EDIT'], VoterInterface::ACCESS_ABSTAIN, $this, 'ACCESS_ABSTAIN if class is not supported'],
- [['EDIT'], VoterInterface::ACCESS_ABSTAIN, null, 'ACCESS_ABSTAIN if object is null'],
+ [$voter, ['EDIT'], VoterInterface::ACCESS_ABSTAIN, null, 'ACCESS_ABSTAIN if object is null'],
- [[], VoterInterface::ACCESS_ABSTAIN, new \stdClass(), 'ACCESS_ABSTAIN if no attributes were provided'],
+ [$voter, [], VoterInterface::ACCESS_ABSTAIN, new \stdClass(), 'ACCESS_ABSTAIN if no attributes were provided'],
+
+ [$voter, [new StringableAttribute()], VoterInterface::ACCESS_GRANTED, new \stdClass(), 'ACCESS_GRANTED if attribute and class are supported and attribute grants access'],
+
+ [$voter, [new \stdClass()], VoterInterface::ACCESS_ABSTAIN, new \stdClass(), 'ACCESS_ABSTAIN if attributes were not strings'],
+
+ [$integerVoter, [42], VoterInterface::ACCESS_GRANTED, new \stdClass(), 'ACCESS_GRANTED if attribute is an integer'],
];
}
/**
* @dataProvider getTests
*/
- public function testVote(array $attributes, $expectedVote, $object, $message)
+ public function testVote(VoterInterface $voter, array $attributes, $expectedVote, $object, $message)
{
- $voter = new VoterTest_Voter();
-
$this->assertEquals($expectedVote, $voter->vote($this->token, $object, $attributes), $message);
}
+
+ public function testVoteWithTypeError()
+ {
+ $this->expectException('TypeError');
+ $this->expectExceptionMessage('Should error');
+ $voter = new TypeErrorVoterTest_Voter();
+ $voter->vote($this->token, new \stdClass(), ['EDIT']);
+ }
}
class VoterTest_Voter extends Voter
@@ -69,3 +84,37 @@ class VoterTest_Voter extends Voter
return $object instanceof \stdClass && \in_array($attribute, ['EDIT', 'CREATE']);
}
}
+
+class IntegerVoterTest_Voter extends Voter
+{
+ protected function voteOnAttribute($attribute, $object, TokenInterface $token): bool
+ {
+ return 42 === $attribute;
+ }
+
+ protected function supports($attribute, $object): bool
+ {
+ return $object instanceof \stdClass && \is_int($attribute);
+ }
+}
+
+class TypeErrorVoterTest_Voter extends Voter
+{
+ protected function voteOnAttribute($attribute, $object, TokenInterface $token): bool
+ {
+ return false;
+ }
+
+ protected function supports($attribute, $object): bool
+ {
+ throw new \TypeError('Should error');
+ }
+}
+
+class StringableAttribute
+{
+ public function __toString(): string
+ {
+ return 'EDIT';
+ }
+}
diff --git a/src/Symfony/Component/Validator/Constraint.php b/src/Symfony/Component/Validator/Constraint.php
index 4ad4261941..31f65f92b9 100644
--- a/src/Symfony/Component/Validator/Constraint.php
+++ b/src/Symfony/Component/Validator/Constraint.php
@@ -116,7 +116,7 @@ abstract class Constraint
$defaultOption = $this->getDefaultOption();
$invalidOptions = [];
$missingOptions = array_flip((array) $this->getRequiredOptions());
- $knownOptions = get_object_vars($this);
+ $knownOptions = get_class_vars(static::class);
// The "groups" option is added to the object lazily
$knownOptions['groups'] = true;
diff --git a/src/Symfony/Component/Validator/Tests/ConstraintTest.php b/src/Symfony/Component/Validator/Tests/ConstraintTest.php
index 6c481b0088..26cc460d39 100644
--- a/src/Symfony/Component/Validator/Tests/ConstraintTest.php
+++ b/src/Symfony/Component/Validator/Tests/ConstraintTest.php
@@ -13,10 +13,13 @@ namespace Symfony\Component\Validator\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\InvalidOptionsException;
use Symfony\Component\Validator\Tests\Fixtures\ClassConstraint;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintB;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintC;
+use Symfony\Component\Validator\Tests\Fixtures\ConstraintWithStaticProperty;
+use Symfony\Component\Validator\Tests\Fixtures\ConstraintWithTypedProperty;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintWithValue;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintWithValueAsDefault;
@@ -245,4 +248,25 @@ class ConstraintTest extends TestCase
$this->expectExceptionMessage('No default option is configured for constraint "Symfony\Component\Validator\Tests\Fixtures\ConstraintB".');
new ConstraintB(['value' => 1]);
}
+
+ public function testStaticPropertiesAreNoOptions()
+ {
+ $this->expectException(InvalidOptionsException::class);
+
+ new ConstraintWithStaticProperty([
+ 'foo' => 'bar',
+ ]);
+ }
+
+ /**
+ * @requires PHP 7.4
+ */
+ public function testSetTypedProperty()
+ {
+ $constraint = new ConstraintWithTypedProperty([
+ 'foo' => 'bar',
+ ]);
+
+ $this->assertSame('bar', $constraint->foo);
+ }
}
diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/ConstraintWithStaticProperty.php b/src/Symfony/Component/Validator/Tests/Fixtures/ConstraintWithStaticProperty.php
new file mode 100644
index 0000000000..f8b2169408
--- /dev/null
+++ b/src/Symfony/Component/Validator/Tests/Fixtures/ConstraintWithStaticProperty.php
@@ -0,0 +1,10 @@
+