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:
Nicolas Grekas 2019-12-16 11:45:21 +01:00
commit 68681e49f2
33 changed files with 261 additions and 56 deletions

View File

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

View File

@ -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');

View File

@ -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()

View File

@ -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));

View File

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

View File

@ -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);

View File

@ -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');
}
}

View File

@ -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;
}

View File

@ -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:
*

View File

@ -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'));
}

View File

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

View File

@ -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;
}
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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';
}

View File

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

View File

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

View File

@ -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');

View File

@ -24,7 +24,7 @@ class GreaterThanOrEqualValidator extends AbstractComparisonValidator
*/
protected function compareValues($value1, $value2)
{
return $value1 >= $value2;
return null === $value2 || $value1 >= $value2;
}
/**

View File

@ -24,7 +24,7 @@ class GreaterThanValidator extends AbstractComparisonValidator
*/
protected function compareValues($value1, $value2)
{
return $value1 > $value2;
return null === $value2 || $value1 > $value2;
}
/**

View File

@ -24,7 +24,7 @@ class LessThanOrEqualValidator extends AbstractComparisonValidator
*/
protected function compareValues($value1, $value2)
{
return $value1 <= $value2;
return null === $value2 || $value1 <= $value2;
}
/**

View File

@ -24,7 +24,7 @@ class LessThanValidator extends AbstractComparisonValidator
*/
protected function compareValues($value1, $value2)
{
return $value1 < $value2;
return null === $value2 || $value1 < $value2;
}
/**

View File

@ -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;
}
}

View File

@ -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
*/

View File

@ -98,4 +98,9 @@ class DivisibleByValidatorTest extends AbstractComparisonValidatorTestCase
[\ArrayIterator::class, new \ArrayIterator(), 12],
];
}
public function provideComparisonsToNullValueAtPropertyPath()
{
$this->markTestSkipped('DivisibleByValidator rejects null values.');
}
}

View File

@ -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],
];
}
}

View File

@ -79,4 +79,11 @@ class GreaterThanOrEqualValidatorTest extends AbstractComparisonValidatorTestCas
['b', '"b"', 'c', '"c"', 'string'],
];
}
public function provideComparisonsToNullValueAtPropertyPath()
{
return [
[5, '5', true],
];
}
}

View File

@ -81,4 +81,11 @@ class GreaterThanValidatorTest extends AbstractComparisonValidatorTestCase
['22', '"22"', '22', '"22"', 'string'],
];
}
public function provideComparisonsToNullValueAtPropertyPath()
{
return [
[5, '5', true],
];
}
}

View File

@ -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],
];
}
}

View File

@ -82,4 +82,11 @@ class LessThanOrEqualValidatorTest extends AbstractComparisonValidatorTestCase
['c', '"c"', 'b', '"b"', 'string'],
];
}
public function provideComparisonsToNullValueAtPropertyPath()
{
return [
[5, '5', true],
];
}
}

View File

@ -80,4 +80,11 @@ class LessThanValidatorTest extends AbstractComparisonValidatorTestCase
['333', '"333"', '22', '"22"', 'string'],
];
}
public function provideComparisonsToNullValueAtPropertyPath()
{
return [
[5, '5', true],
];
}
}

View File

@ -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],
];
}
}

View File

@ -94,4 +94,11 @@ class NotIdenticalToValidatorTest extends AbstractComparisonValidatorTestCase
return $comparisons;
}
public function provideComparisonsToNullValueAtPropertyPath()
{
return [
[5, '5', true],
];
}
}