Merge branch '4.3' into 4.4
* 4.3: fix merge CS [Serializer] Skip uninitialized (PHP 7.4) properties in PropertyNormalizer and ObjectNormalizer stop using deprecated Doctrine persistence classes [Cache] Fix wrong classname in deprecation message Fix regex lookahead syntax in ApplicationTest Fixed syntax in comment [SecurityBundle][FirewallMap] Remove unused property [Messenger][AMQP] Use delivery_mode=2 by default [DI] Improve performance of processDefinition Fix invalid Windows path normalization [Validator][ConstraintValidator] Safe fail on invalid timezones [DoctrineBridge] Fixed submitting invalid ids when using queries with limit [FrameworkBundle] Add info & example to auto_mapping config fix comparisons with null values at property paths
This commit is contained in:
commit
68681e49f2
@ -50,6 +50,21 @@ class ORMQueryBuilderLoader implements EntityLoaderInterface
|
||||
*/
|
||||
public function getEntitiesByIds($identifier, array $values)
|
||||
{
|
||||
if (null !== $this->queryBuilder->getMaxResults() || null !== $this->queryBuilder->getFirstResult()) {
|
||||
// an offset or a limit would apply on results including the where clause with submitted id values
|
||||
// that could make invalid choices valid
|
||||
$choices = [];
|
||||
$metadata = $this->queryBuilder->getEntityManager()->getClassMetadata(current($this->queryBuilder->getRootEntities()));
|
||||
|
||||
foreach ($this->getEntities() as $entity) {
|
||||
if (\in_array(current($metadata->getIdentifierValues($entity)), $values, true)) {
|
||||
$choices[] = $entity;
|
||||
}
|
||||
}
|
||||
|
||||
return $choices;
|
||||
}
|
||||
|
||||
$qb = clone $this->queryBuilder;
|
||||
$alias = current($qb->getRootAliases());
|
||||
$parameter = 'ORMQueryBuilderLoader_getEntitiesByIds_'.$identifier;
|
||||
|
@ -953,6 +953,31 @@ class EntityTypeTest extends BaseTypeTest
|
||||
$this->assertNull($field->getData());
|
||||
}
|
||||
|
||||
public function testDisallowChoicesThatAreNotIncludedQueryBuilderSingleIdentifierWithLimit()
|
||||
{
|
||||
$entity1 = new SingleIntIdEntity(1, 'Foo');
|
||||
$entity2 = new SingleIntIdEntity(2, 'Bar');
|
||||
$entity3 = new SingleIntIdEntity(3, 'Baz');
|
||||
|
||||
$this->persist([$entity1, $entity2, $entity3]);
|
||||
|
||||
$repository = $this->em->getRepository(self::SINGLE_IDENT_CLASS);
|
||||
|
||||
$field = $this->factory->createNamed('name', static::TESTED_TYPE, null, [
|
||||
'em' => 'default',
|
||||
'class' => self::SINGLE_IDENT_CLASS,
|
||||
'query_builder' => $repository->createQueryBuilder('e')
|
||||
->where('e.id IN (1, 2, 3)')
|
||||
->setMaxResults(1),
|
||||
'choice_label' => 'name',
|
||||
]);
|
||||
|
||||
$field->submit('3');
|
||||
|
||||
$this->assertFalse($field->isSynchronized());
|
||||
$this->assertNull($field->getData());
|
||||
}
|
||||
|
||||
public function testDisallowChoicesThatAreNotIncludedQueryBuilderSingleAssocIdentifier()
|
||||
{
|
||||
$innerEntity1 = new SingleIntIdNoToStringEntity(1, 'InFoo');
|
||||
|
@ -861,6 +861,11 @@ class Configuration implements ConfigurationInterface
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('auto_mapping')
|
||||
->info('A collection of namespaces for which auto-mapping will be enabled.')
|
||||
->example([
|
||||
'App\\Entity\\' => [],
|
||||
'App\\WithSpecificLoaders\\' => ['validator.property_info_loader'],
|
||||
])
|
||||
->useAttributeAsKey('namespace')
|
||||
->normalizeKeys(false)
|
||||
->beforeNormalization()
|
||||
|
@ -48,7 +48,7 @@ class TemplateNameParser extends BaseTemplateNameParser
|
||||
}
|
||||
|
||||
// normalize name
|
||||
$name = str_replace(':/', ':', preg_replace('#/{2,}#', '/', str_replace('\\', '/', $name)));
|
||||
$name = preg_replace('#/{2,}#', '/', str_replace('\\', '/', $name));
|
||||
|
||||
if (false !== strpos($name, '..')) {
|
||||
throw new \RuntimeException(sprintf('Template name "%s" contains invalid characters.', $name));
|
||||
|
@ -26,13 +26,11 @@ class FirewallMap implements FirewallMapInterface
|
||||
{
|
||||
private $container;
|
||||
private $map;
|
||||
private $contexts;
|
||||
|
||||
public function __construct(ContainerInterface $container, iterable $map)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->map = $map;
|
||||
$this->contexts = new \SplObjectStorage();
|
||||
}
|
||||
|
||||
public function getListeners(Request $request)
|
||||
|
@ -11,7 +11,9 @@
|
||||
|
||||
namespace Symfony\Component\Cache\Simple;
|
||||
|
||||
use Symfony\Component\Cache\Adapter\ApcuAdapter;
|
||||
use Symfony\Component\Cache\Traits\ApcuTrait;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
|
||||
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', ApcuCache::class, ApcuAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
|
||||
|
||||
|
@ -616,7 +616,7 @@ class ApplicationTest extends TestCase
|
||||
$this->assertRegExp(sprintf('/Command "%s" is not defined./', $commandName), $e->getMessage(), '->find() throws a CommandNotFoundException if command does not exist, with alternatives');
|
||||
$this->assertRegExp('/afoobar1/', $e->getMessage(), '->find() throws a CommandNotFoundException if command does not exist, with alternative : "afoobar1"');
|
||||
$this->assertRegExp('/foo:bar1/', $e->getMessage(), '->find() throws a CommandNotFoundException if command does not exist, with alternative : "foo:bar1"');
|
||||
$this->assertNotRegExp('/foo:bar(?>!1)/', $e->getMessage(), '->find() throws a CommandNotFoundException if command does not exist, without "foo:bar" alternative');
|
||||
$this->assertNotRegExp('/foo:bar(?!1)/', $e->getMessage(), '->find() throws a CommandNotFoundException if command does not exist, without "foo:bar" alternative');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,9 +63,10 @@ class ResolveInstanceofConditionalsPass implements CompilerPassInterface
|
||||
$instanceofTags = [];
|
||||
$instanceofCalls = [];
|
||||
$instanceofBindings = [];
|
||||
$reflectionClass = null;
|
||||
|
||||
foreach ($conditionals as $interface => $instanceofDefs) {
|
||||
if ($interface !== $class && (!$container->getReflectionClass($class, false))) {
|
||||
if ($interface !== $class && !(null === $reflectionClass ? $reflectionClass = ($container->getReflectionClass($class, false) ?: false) : $reflectionClass)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ interface MessageSubscriberInterface extends MessageHandlerInterface
|
||||
* It can also change the priority per classes.
|
||||
*
|
||||
* yield FirstMessage::class => ['priority' => 0];
|
||||
* yield SecondMessage::class => ['priority => -10];
|
||||
* yield SecondMessage::class => ['priority' => -10];
|
||||
*
|
||||
* It can also specify a method, a priority, a bus and/or a transport per message:
|
||||
*
|
||||
|
@ -227,7 +227,7 @@ class ConnectionTest extends TestCase
|
||||
);
|
||||
|
||||
$amqpExchange->expects($this->once())->method('declareExchange');
|
||||
$amqpExchange->expects($this->once())->method('publish')->with('body', null, AMQP_NOPARAM, ['headers' => []]);
|
||||
$amqpExchange->expects($this->once())->method('publish')->with('body', null, AMQP_NOPARAM, ['headers' => [], 'delivery_mode' => 2]);
|
||||
$amqpQueue->expects($this->once())->method('declareQueue');
|
||||
$amqpQueue->expects($this->once())->method('bind')->with(self::DEFAULT_EXCHANGE_NAME, null);
|
||||
|
||||
@ -250,7 +250,7 @@ class ConnectionTest extends TestCase
|
||||
$factory->method('createQueue')->will($this->onConsecutiveCalls($amqpQueue0, $amqpQueue1));
|
||||
|
||||
$amqpExchange->expects($this->once())->method('declareExchange');
|
||||
$amqpExchange->expects($this->once())->method('publish')->with('body', 'routing_key', AMQP_NOPARAM, ['headers' => []]);
|
||||
$amqpExchange->expects($this->once())->method('publish')->with('body', 'routing_key', AMQP_NOPARAM, ['headers' => [], 'delivery_mode' => 2]);
|
||||
$amqpQueue0->expects($this->once())->method('declareQueue');
|
||||
$amqpQueue0->expects($this->exactly(2))->method('bind')->withConsecutive(
|
||||
[self::DEFAULT_EXCHANGE_NAME, 'binding_key0'],
|
||||
@ -400,7 +400,7 @@ class ConnectionTest extends TestCase
|
||||
$delayQueue->expects($this->once())->method('declareQueue');
|
||||
$delayQueue->expects($this->once())->method('bind')->with('delays', 'delay_messages__5000');
|
||||
|
||||
$delayExchange->expects($this->once())->method('publish')->with('{}', 'delay_messages__5000', AMQP_NOPARAM, ['headers' => ['x-some-headers' => 'foo']]);
|
||||
$delayExchange->expects($this->once())->method('publish')->with('{}', 'delay_messages__5000', AMQP_NOPARAM, ['headers' => ['x-some-headers' => 'foo'], 'delivery_mode' => 2]);
|
||||
|
||||
$connection = Connection::fromDsn('amqp://localhost', [], $factory);
|
||||
$connection->publish('{}', ['x-some-headers' => 'foo'], 5000);
|
||||
@ -442,7 +442,7 @@ class ConnectionTest extends TestCase
|
||||
$delayQueue->expects($this->once())->method('declareQueue');
|
||||
$delayQueue->expects($this->once())->method('bind')->with('delays', 'delay_messages__120000');
|
||||
|
||||
$delayExchange->expects($this->once())->method('publish')->with('{}', 'delay_messages__120000', AMQP_NOPARAM, ['headers' => []]);
|
||||
$delayExchange->expects($this->once())->method('publish')->with('{}', 'delay_messages__120000', AMQP_NOPARAM, ['headers' => [], 'delivery_mode' => 2]);
|
||||
$connection->publish('{}', [], 120000);
|
||||
}
|
||||
|
||||
@ -474,12 +474,27 @@ class ConnectionTest extends TestCase
|
||||
$amqpExchange = $this->createMock(\AMQPExchange::class)
|
||||
);
|
||||
|
||||
$amqpExchange->expects($this->once())->method('publish')->with('body', null, AMQP_NOPARAM, ['headers' => ['Foo' => 'X', 'Bar' => 'Y']]);
|
||||
$amqpExchange->expects($this->once())->method('publish')->with('body', null, AMQP_NOPARAM, ['headers' => ['Foo' => 'X', 'Bar' => 'Y'], 'delivery_mode' => 2]);
|
||||
|
||||
$connection = Connection::fromDsn('amqp://localhost', [], $factory);
|
||||
$connection->publish('body', ['Foo' => 'X'], 0, new AmqpStamp(null, AMQP_NOPARAM, ['headers' => ['Bar' => 'Y']]));
|
||||
}
|
||||
|
||||
public function testAmqpStampDelireryModeIsUsed()
|
||||
{
|
||||
$factory = new TestAmqpFactory(
|
||||
$this->createMock(\AMQPConnection::class),
|
||||
$this->createMock(\AMQPChannel::class),
|
||||
$this->createMock(\AMQPQueue::class),
|
||||
$amqpExchange = $this->createMock(\AMQPExchange::class)
|
||||
);
|
||||
|
||||
$amqpExchange->expects($this->once())->method('publish')->with('body', null, AMQP_NOPARAM, ['headers' => [], 'delivery_mode' => 1]);
|
||||
|
||||
$connection = Connection::fromDsn('amqp://localhost', [], $factory);
|
||||
$connection->publish('body', [], 0, new AmqpStamp(null, AMQP_NOPARAM, ['delivery_mode' => 1]));
|
||||
}
|
||||
|
||||
public function testItCanPublishWithTheDefaultRoutingKey()
|
||||
{
|
||||
$factory = new TestAmqpFactory(
|
||||
@ -546,7 +561,7 @@ class ConnectionTest extends TestCase
|
||||
$delayQueue->expects($this->once())->method('declareQueue');
|
||||
$delayQueue->expects($this->once())->method('bind')->with('delays', 'delay_messages_routing_key_120000');
|
||||
|
||||
$delayExchange->expects($this->once())->method('publish')->with('{}', 'delay_messages_routing_key_120000', AMQP_NOPARAM, ['headers' => []]);
|
||||
$delayExchange->expects($this->once())->method('publish')->with('{}', 'delay_messages_routing_key_120000', AMQP_NOPARAM, ['headers' => [], 'delivery_mode' => 2]);
|
||||
$connection->publish('{}', [], 120000, new AmqpStamp('routing_key'));
|
||||
}
|
||||
|
||||
|
@ -230,6 +230,7 @@ class Connection
|
||||
{
|
||||
$attributes = $amqpStamp ? $amqpStamp->getAttributes() : [];
|
||||
$attributes['headers'] = array_merge($attributes['headers'] ?? [], $headers);
|
||||
$attributes['delivery_mode'] = $attributes['delivery_mode'] ?? 2;
|
||||
|
||||
$exchange->publish(
|
||||
$body,
|
||||
|
@ -75,17 +75,13 @@ class AccessDecisionManager implements AccessDecisionManagerInterface
|
||||
$deny = 0;
|
||||
foreach ($this->voters as $voter) {
|
||||
$result = $voter->vote($token, $object, $attributes);
|
||||
switch ($result) {
|
||||
case VoterInterface::ACCESS_GRANTED:
|
||||
return true;
|
||||
|
||||
case VoterInterface::ACCESS_DENIED:
|
||||
++$deny;
|
||||
if (VoterInterface::ACCESS_GRANTED === $result) {
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
if (VoterInterface::ACCESS_DENIED === $result) {
|
||||
++$deny;
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,16 +113,10 @@ class AccessDecisionManager implements AccessDecisionManagerInterface
|
||||
foreach ($this->voters as $voter) {
|
||||
$result = $voter->vote($token, $object, $attributes);
|
||||
|
||||
switch ($result) {
|
||||
case VoterInterface::ACCESS_GRANTED:
|
||||
++$grant;
|
||||
|
||||
break;
|
||||
|
||||
case VoterInterface::ACCESS_DENIED:
|
||||
++$deny;
|
||||
|
||||
break;
|
||||
if (VoterInterface::ACCESS_GRANTED === $result) {
|
||||
++$grant;
|
||||
} elseif (VoterInterface::ACCESS_DENIED === $result) {
|
||||
++$deny;
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,17 +148,12 @@ class AccessDecisionManager implements AccessDecisionManagerInterface
|
||||
foreach ($attributes as $attribute) {
|
||||
$result = $voter->vote($token, $object, [$attribute]);
|
||||
|
||||
switch ($result) {
|
||||
case VoterInterface::ACCESS_GRANTED:
|
||||
++$grant;
|
||||
if (VoterInterface::ACCESS_DENIED === $result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case VoterInterface::ACCESS_DENIED:
|
||||
return false;
|
||||
|
||||
default:
|
||||
break;
|
||||
if (VoterInterface::ACCESS_GRANTED === $result) {
|
||||
++$grant;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -103,8 +103,14 @@ class ObjectNormalizer extends AbstractObjectNormalizer
|
||||
}
|
||||
}
|
||||
|
||||
$checkPropertyInitialization = \PHP_VERSION_ID >= 70400;
|
||||
|
||||
// properties
|
||||
foreach ($reflClass->getProperties(\ReflectionProperty::IS_PUBLIC) as $reflProperty) {
|
||||
if ($checkPropertyInitialization && !$reflProperty->isInitialized($object)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($reflProperty->isStatic() || !$this->isAllowedAttribute($object, $reflProperty->name, $format, $context)) {
|
||||
continue;
|
||||
}
|
||||
|
@ -101,9 +101,20 @@ class PropertyNormalizer extends AbstractObjectNormalizer
|
||||
{
|
||||
$reflectionObject = new \ReflectionObject($object);
|
||||
$attributes = [];
|
||||
$checkPropertyInitialization = \PHP_VERSION_ID >= 70400;
|
||||
|
||||
do {
|
||||
foreach ($reflectionObject->getProperties() as $property) {
|
||||
if ($checkPropertyInitialization) {
|
||||
if (!$property->isPublic()) {
|
||||
$property->setAccessible(true);
|
||||
}
|
||||
|
||||
if (!$property->isInitialized($object)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->isAllowedAttribute($reflectionObject->getName(), $property->name, $format, $context)) {
|
||||
continue;
|
||||
}
|
||||
|
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Serializer\Tests\Fixtures;
|
||||
|
||||
/**
|
||||
* @author Valentin Udaltsov <udaltsov.valentin@gmail.com>
|
||||
*/
|
||||
final class Php74Dummy
|
||||
{
|
||||
public string $uninitializedProperty;
|
||||
|
||||
public string $initializedProperty = 'defaultValue';
|
||||
}
|
@ -33,6 +33,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\CircularReferenceDummy;
|
||||
use Symfony\Component\Serializer\Tests\Fixtures\GroupDummy;
|
||||
use Symfony\Component\Serializer\Tests\Fixtures\MaxDepthDummy;
|
||||
use Symfony\Component\Serializer\Tests\Fixtures\OtherSerializedNameDummy;
|
||||
use Symfony\Component\Serializer\Tests\Fixtures\Php74Dummy;
|
||||
use Symfony\Component\Serializer\Tests\Fixtures\SiblingHolder;
|
||||
use Symfony\Component\Serializer\Tests\Normalizer\Features\AttributesTestTrait;
|
||||
use Symfony\Component\Serializer\Tests\Normalizer\Features\CallbacksObject;
|
||||
@ -114,6 +115,18 @@ class ObjectNormalizerTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires PHP 7.4
|
||||
*/
|
||||
public function testNormalizeObjectWithUninitializedProperties()
|
||||
{
|
||||
$obj = new Php74Dummy();
|
||||
$this->assertEquals(
|
||||
['initializedProperty' => 'defaultValue'],
|
||||
$this->normalizer->normalize($obj, 'any')
|
||||
);
|
||||
}
|
||||
|
||||
public function testDenormalize()
|
||||
{
|
||||
$obj = $this->normalizer->denormalize(
|
||||
|
@ -30,6 +30,7 @@ use Symfony\Component\Serializer\SerializerInterface;
|
||||
use Symfony\Component\Serializer\Tests\Fixtures\Dummy;
|
||||
use Symfony\Component\Serializer\Tests\Fixtures\GroupDummy;
|
||||
use Symfony\Component\Serializer\Tests\Fixtures\GroupDummyChild;
|
||||
use Symfony\Component\Serializer\Tests\Fixtures\Php74Dummy;
|
||||
use Symfony\Component\Serializer\Tests\Fixtures\PropertyCircularReferenceDummy;
|
||||
use Symfony\Component\Serializer\Tests\Fixtures\PropertySiblingHolder;
|
||||
use Symfony\Component\Serializer\Tests\Normalizer\Features\CallbacksObject;
|
||||
@ -87,6 +88,18 @@ class PropertyNormalizerTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires PHP 7.4
|
||||
*/
|
||||
public function testNormalizeObjectWithUninitializedProperties()
|
||||
{
|
||||
$obj = new Php74Dummy();
|
||||
$this->assertEquals(
|
||||
['initializedProperty' => 'defaultValue'],
|
||||
$this->normalizer->normalize($obj, 'any')
|
||||
);
|
||||
}
|
||||
|
||||
public function testDenormalize()
|
||||
{
|
||||
$obj = $this->normalizer->denormalize(
|
||||
|
@ -87,16 +87,12 @@ abstract class ConstraintValidator implements ConstraintValidatorInterface
|
||||
{
|
||||
if (($format & self::PRETTY_DATE) && $value instanceof \DateTimeInterface) {
|
||||
if (class_exists('IntlDateFormatter')) {
|
||||
$locale = \Locale::getDefault();
|
||||
$formatter = new \IntlDateFormatter($locale, \IntlDateFormatter::MEDIUM, \IntlDateFormatter::SHORT, $value->getTimezone());
|
||||
$formatter = new \IntlDateFormatter(\Locale::getDefault(), \IntlDateFormatter::MEDIUM, \IntlDateFormatter::SHORT, 'UTC');
|
||||
|
||||
// neither the native nor the stub IntlDateFormatter support
|
||||
// DateTimeImmutable as of yet
|
||||
if (!$value instanceof \DateTime) {
|
||||
$value = new \DateTime($value->format('Y-m-d H:i:s.u e'));
|
||||
}
|
||||
|
||||
return $formatter->format($value);
|
||||
return $formatter->format(new \DateTime(
|
||||
$value->format('Y-m-d H:i:s.u'),
|
||||
new \DateTimeZone('UTC')
|
||||
));
|
||||
}
|
||||
|
||||
return $value->format('Y-m-d H:i:s');
|
||||
|
@ -24,7 +24,7 @@ class GreaterThanOrEqualValidator extends AbstractComparisonValidator
|
||||
*/
|
||||
protected function compareValues($value1, $value2)
|
||||
{
|
||||
return $value1 >= $value2;
|
||||
return null === $value2 || $value1 >= $value2;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -24,7 +24,7 @@ class GreaterThanValidator extends AbstractComparisonValidator
|
||||
*/
|
||||
protected function compareValues($value1, $value2)
|
||||
{
|
||||
return $value1 > $value2;
|
||||
return null === $value2 || $value1 > $value2;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -24,7 +24,7 @@ class LessThanOrEqualValidator extends AbstractComparisonValidator
|
||||
*/
|
||||
protected function compareValues($value1, $value2)
|
||||
{
|
||||
return $value1 <= $value2;
|
||||
return null === $value2 || $value1 <= $value2;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -24,7 +24,7 @@ class LessThanValidator extends AbstractComparisonValidator
|
||||
*/
|
||||
protected function compareValues($value1, $value2)
|
||||
{
|
||||
return $value1 < $value2;
|
||||
return null === $value2 || $value1 < $value2;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,6 +27,9 @@ final class ConstraintValidatorTest extends TestCase
|
||||
|
||||
public function formatValueProvider()
|
||||
{
|
||||
$defaultTimezone = date_default_timezone_get();
|
||||
date_default_timezone_set('Europe/Moscow'); // GMT+3
|
||||
|
||||
$data = [
|
||||
['true', true],
|
||||
['false', false],
|
||||
@ -36,10 +39,15 @@ final class ConstraintValidatorTest extends TestCase
|
||||
['array', []],
|
||||
['object', $toString = new TestToStringObject()],
|
||||
['ccc', $toString, ConstraintValidator::OBJECT_TO_STRING],
|
||||
['object', $dateTime = (new \DateTimeImmutable('@0'))->setTimezone(new \DateTimeZone('UTC'))],
|
||||
[class_exists(\IntlDateFormatter::class) ? 'Jan 1, 1970, 12:00 AM' : '1970-01-01 00:00:00', $dateTime, ConstraintValidator::PRETTY_DATE],
|
||||
['object', $dateTime = new \DateTimeImmutable('1971-02-02T08:00:00UTC')],
|
||||
[class_exists(\IntlDateFormatter::class) ? 'Oct 4, 2019, 11:02 AM' : '2019-10-04 11:02:03', new \DateTimeImmutable('2019-10-04T11:02:03+09:00'), ConstraintValidator::PRETTY_DATE],
|
||||
[class_exists(\IntlDateFormatter::class) ? 'Feb 2, 1971, 8:00 AM' : '1971-02-02 08:00:00', $dateTime, ConstraintValidator::PRETTY_DATE],
|
||||
[class_exists(\IntlDateFormatter::class) ? 'Jan 1, 1970, 6:00 AM' : '1970-01-01 06:00:00', new \DateTimeImmutable('1970-01-01T06:00:00Z'), ConstraintValidator::PRETTY_DATE],
|
||||
[class_exists(\IntlDateFormatter::class) ? 'Jan 1, 1970, 3:00 PM' : '1970-01-01 15:00:00', (new \DateTimeImmutable('1970-01-01T23:00:00'))->setTimezone(new \DateTimeZone('America/New_York')), ConstraintValidator::PRETTY_DATE],
|
||||
];
|
||||
|
||||
date_default_timezone_set($defaultTimezone);
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
@ -247,6 +247,32 @@ abstract class AbstractComparisonValidatorTestCase extends ConstraintValidatorTe
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideComparisonsToNullValueAtPropertyPath
|
||||
*/
|
||||
public function testCompareWithNullValueAtPropertyAt($dirtyValue, $dirtyValueAsString, $isValid)
|
||||
{
|
||||
$constraint = $this->createConstraint(['propertyPath' => 'value']);
|
||||
$constraint->message = 'Constraint Message';
|
||||
|
||||
$object = new ComparisonTest_Class(null);
|
||||
$this->setObject($object);
|
||||
|
||||
$this->validator->validate($dirtyValue, $constraint);
|
||||
|
||||
if ($isValid) {
|
||||
$this->assertNoViolation();
|
||||
} else {
|
||||
$this->buildViolation('Constraint Message')
|
||||
->setParameter('{{ value }}', $dirtyValueAsString)
|
||||
->setParameter('{{ compared_value }}', 'null')
|
||||
->setParameter('{{ compared_value_type }}', 'NULL')
|
||||
->setParameter('{{ compared_value_path }}', 'value')
|
||||
->setCode($this->getErrorCode())
|
||||
->assertRaised();
|
||||
}
|
||||
}
|
||||
|
||||
public function provideAllInvalidComparisons(): array
|
||||
{
|
||||
// The provider runs before setUp(), so we need to manually fix
|
||||
@ -262,6 +288,8 @@ abstract class AbstractComparisonValidatorTestCase extends ConstraintValidatorTe
|
||||
|
||||
abstract public function provideInvalidComparisons(): array;
|
||||
|
||||
abstract public function provideComparisonsToNullValueAtPropertyPath();
|
||||
|
||||
/**
|
||||
* @param array|null $options Options for the constraint
|
||||
*/
|
||||
|
@ -98,4 +98,9 @@ class DivisibleByValidatorTest extends AbstractComparisonValidatorTestCase
|
||||
[\ArrayIterator::class, new \ArrayIterator(), 12],
|
||||
];
|
||||
}
|
||||
|
||||
public function provideComparisonsToNullValueAtPropertyPath()
|
||||
{
|
||||
$this->markTestSkipped('DivisibleByValidator rejects null values.');
|
||||
}
|
||||
}
|
||||
|
@ -76,4 +76,11 @@ class EqualToValidatorTest extends AbstractComparisonValidatorTestCase
|
||||
[new ComparisonTest_Class(4), '4', new ComparisonTest_Class(5), '5', __NAMESPACE__.'\ComparisonTest_Class'],
|
||||
];
|
||||
}
|
||||
|
||||
public function provideComparisonsToNullValueAtPropertyPath()
|
||||
{
|
||||
return [
|
||||
[5, '5', false],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -79,4 +79,11 @@ class GreaterThanOrEqualValidatorTest extends AbstractComparisonValidatorTestCas
|
||||
['b', '"b"', 'c', '"c"', 'string'],
|
||||
];
|
||||
}
|
||||
|
||||
public function provideComparisonsToNullValueAtPropertyPath()
|
||||
{
|
||||
return [
|
||||
[5, '5', true],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -81,4 +81,11 @@ class GreaterThanValidatorTest extends AbstractComparisonValidatorTestCase
|
||||
['22', '"22"', '22', '"22"', 'string'],
|
||||
];
|
||||
}
|
||||
|
||||
public function provideComparisonsToNullValueAtPropertyPath()
|
||||
{
|
||||
return [
|
||||
[5, '5', true],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -94,4 +94,11 @@ class IdenticalToValidatorTest extends AbstractComparisonValidatorTestCase
|
||||
[new ComparisonTest_Class(4), '4', new ComparisonTest_Class(5), '5', __NAMESPACE__.'\ComparisonTest_Class'],
|
||||
];
|
||||
}
|
||||
|
||||
public function provideComparisonsToNullValueAtPropertyPath()
|
||||
{
|
||||
return [
|
||||
[5, '5', false],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -82,4 +82,11 @@ class LessThanOrEqualValidatorTest extends AbstractComparisonValidatorTestCase
|
||||
['c', '"c"', 'b', '"b"', 'string'],
|
||||
];
|
||||
}
|
||||
|
||||
public function provideComparisonsToNullValueAtPropertyPath()
|
||||
{
|
||||
return [
|
||||
[5, '5', true],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -80,4 +80,11 @@ class LessThanValidatorTest extends AbstractComparisonValidatorTestCase
|
||||
['333', '"333"', '22', '"22"', 'string'],
|
||||
];
|
||||
}
|
||||
|
||||
public function provideComparisonsToNullValueAtPropertyPath()
|
||||
{
|
||||
return [
|
||||
[5, '5', true],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -76,4 +76,11 @@ class NotEqualToValidatorTest extends AbstractComparisonValidatorTestCase
|
||||
[new ComparisonTest_Class(5), '5', new ComparisonTest_Class(5), '5', __NAMESPACE__.'\ComparisonTest_Class'],
|
||||
];
|
||||
}
|
||||
|
||||
public function provideComparisonsToNullValueAtPropertyPath()
|
||||
{
|
||||
return [
|
||||
[5, '5', true],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -94,4 +94,11 @@ class NotIdenticalToValidatorTest extends AbstractComparisonValidatorTestCase
|
||||
|
||||
return $comparisons;
|
||||
}
|
||||
|
||||
public function provideComparisonsToNullValueAtPropertyPath()
|
||||
{
|
||||
return [
|
||||
[5, '5', true],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user