Merge branch '3.4' into 4.3
* 3.4: CS [Serializer] Skip uninitialized (PHP 7.4) properties in PropertyNormalizer and ObjectNormalizer stop using deprecated Doctrine persistence classes Fix regex lookahead syntax in ApplicationTest [SecurityBundle][FirewallMap] Remove unused property [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 fix comparisons with null values at property paths
This commit is contained in:
commit
02c1cda67e
@ -55,6 +55,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;
|
||||
|
@ -955,6 +955,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');
|
||||
|
@ -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)
|
||||
|
@ -605,7 +605,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;
|
||||
}
|
||||
|
||||
|
@ -71,17 +71,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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,16 +109,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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,17 +144,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(
|
||||
|
@ -29,6 +29,7 @@ use Symfony\Component\Serializer\Serializer;
|
||||
use Symfony\Component\Serializer\SerializerInterface;
|
||||
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;
|
||||
@ -86,6 +87,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;
|
||||
}
|
||||
}
|
||||
|
@ -234,6 +234,31 @@ 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')
|
||||
->setCode($this->getErrorCode())
|
||||
->assertRaised();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
@ -255,6 +280,8 @@ abstract class AbstractComparisonValidatorTestCase extends ConstraintValidatorTe
|
||||
*/
|
||||
abstract public function provideInvalidComparisons();
|
||||
|
||||
abstract public function provideComparisonsToNullValueAtPropertyPath();
|
||||
|
||||
/**
|
||||
* @param array|null $options Options for the constraint
|
||||
*
|
||||
|
@ -75,4 +75,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],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -78,4 +78,11 @@ class GreaterThanOrEqualValidatorTest extends AbstractComparisonValidatorTestCas
|
||||
['b', '"b"', 'c', '"c"', 'string'],
|
||||
];
|
||||
}
|
||||
|
||||
public function provideComparisonsToNullValueAtPropertyPath()
|
||||
{
|
||||
return [
|
||||
[5, '5', true],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -80,4 +80,11 @@ class GreaterThanValidatorTest extends AbstractComparisonValidatorTestCase
|
||||
['22', '"22"', '22', '"22"', 'string'],
|
||||
];
|
||||
}
|
||||
|
||||
public function provideComparisonsToNullValueAtPropertyPath()
|
||||
{
|
||||
return [
|
||||
[5, '5', true],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -93,4 +93,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],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -81,4 +81,11 @@ class LessThanOrEqualValidatorTest extends AbstractComparisonValidatorTestCase
|
||||
['c', '"c"', 'b', '"b"', 'string'],
|
||||
];
|
||||
}
|
||||
|
||||
public function provideComparisonsToNullValueAtPropertyPath()
|
||||
{
|
||||
return [
|
||||
[5, '5', true],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -79,4 +79,11 @@ class LessThanValidatorTest extends AbstractComparisonValidatorTestCase
|
||||
['333', '"333"', '22', '"22"', 'string'],
|
||||
];
|
||||
}
|
||||
|
||||
public function provideComparisonsToNullValueAtPropertyPath()
|
||||
{
|
||||
return [
|
||||
[5, '5', true],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -75,4 +75,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],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -93,4 +93,11 @@ class NotIdenticalToValidatorTest extends AbstractComparisonValidatorTestCase
|
||||
|
||||
return $comparisons;
|
||||
}
|
||||
|
||||
public function provideComparisonsToNullValueAtPropertyPath()
|
||||
{
|
||||
return [
|
||||
[5, '5', true],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user