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 @@ +