Merge branch '3.4' into 4.2

* 3.4:
  [Serializer] Respect ignored attributes in cache key of normalizer
  fix resetting the COLUMN environment variable
  Fix TestRunner compatibility to PhpUnit 8
  prevent mixup of the object to populate
This commit is contained in:
Christian Flothmann 2019-04-08 13:36:05 +02:00
commit ec41d76624
13 changed files with 138 additions and 208 deletions

View File

@ -23,6 +23,24 @@ class CommandForV5 extends \PHPUnit_TextUI_Command
*/
protected function createRunner()
{
return new TestRunnerForV5($this->arguments['loader']);
$listener = new SymfonyTestsListenerForV5();
$this->arguments['listeners'] = isset($this->arguments['listeners']) ? $this->arguments['listeners'] : array();
$registeredLocally = false;
foreach ($this->arguments['listeners'] as $registeredListener) {
if ($registeredListener instanceof SymfonyTestsListenerForV5) {
$registeredListener->globalListenerDisabled();
$registeredLocally = true;
break;
}
}
if (!$registeredLocally) {
$this->arguments['listeners'][] = $listener;
}
return parent::createRunner();
}
}

View File

@ -13,7 +13,7 @@ namespace Symfony\Bridge\PhpUnit\Legacy;
use PHPUnit\TextUI\Command as BaseCommand;
use PHPUnit\TextUI\TestRunner as BaseRunner;
use Symfony\Bridge\PhpUnit\TextUI\TestRunner;
use Symfony\Bridge\PhpUnit\SymfonyTestsListener;
/**
* {@inheritdoc}
@ -27,6 +27,24 @@ class CommandForV6 extends BaseCommand
*/
protected function createRunner(): BaseRunner
{
return new TestRunner($this->arguments['loader']);
$listener = new SymfonyTestsListener();
$this->arguments['listeners'] = isset($this->arguments['listeners']) ? $this->arguments['listeners'] : [];
$registeredLocally = false;
foreach ($this->arguments['listeners'] as $registeredListener) {
if ($registeredListener instanceof SymfonyTestsListener) {
$registeredListener->globalListenerDisabled();
$registeredLocally = true;
break;
}
}
if (!$registeredLocally) {
$this->arguments['listeners'][] = $listener;
}
return parent::createRunner();
}
}

View File

@ -1,48 +0,0 @@
<?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\Bridge\PhpUnit\Legacy;
/**
* {@inheritdoc}
*
* @internal
*/
class TestRunnerForV5 extends \PHPUnit_TextUI_TestRunner
{
/**
* {@inheritdoc}
*/
protected function handleConfiguration(array &$arguments)
{
$listener = new SymfonyTestsListenerForV5();
$result = parent::handleConfiguration($arguments);
$arguments['listeners'] = isset($arguments['listeners']) ? $arguments['listeners'] : array();
$registeredLocally = false;
foreach ($arguments['listeners'] as $registeredListener) {
if ($registeredListener instanceof SymfonyTestsListenerForV5) {
$registeredListener->globalListenerDisabled();
$registeredLocally = true;
break;
}
}
if (!$registeredLocally) {
$arguments['listeners'][] = $listener;
}
return $result;
}
}

View File

@ -1,49 +0,0 @@
<?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\Bridge\PhpUnit\Legacy;
use PHPUnit\TextUI\TestRunner as BaseRunner;
use Symfony\Bridge\PhpUnit\SymfonyTestsListener;
/**
* {@inheritdoc}
*
* @internal
*/
class TestRunnerForV6 extends BaseRunner
{
/**
* {@inheritdoc}
*/
protected function handleConfiguration(array &$arguments)
{
$listener = new SymfonyTestsListener();
parent::handleConfiguration($arguments);
$arguments['listeners'] = isset($arguments['listeners']) ? $arguments['listeners'] : array();
$registeredLocally = false;
foreach ($arguments['listeners'] as $registeredListener) {
if ($registeredListener instanceof SymfonyTestsListener) {
$registeredListener->globalListenerDisabled();
$registeredLocally = true;
break;
}
}
if (!$registeredLocally) {
$arguments['listeners'][] = $listener;
}
}
}

View File

@ -1,49 +0,0 @@
<?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\Bridge\PhpUnit\Legacy;
use PHPUnit\TextUI\TestRunner as BaseRunner;
use Symfony\Bridge\PhpUnit\SymfonyTestsListener;
/**
* {@inheritdoc}
*
* @internal
*/
class TestRunnerForV7 extends BaseRunner
{
/**
* {@inheritdoc}
*/
protected function handleConfiguration(array &$arguments): void
{
$listener = new SymfonyTestsListener();
parent::handleConfiguration($arguments);
$arguments['listeners'] = isset($arguments['listeners']) ? $arguments['listeners'] : array();
$registeredLocally = false;
foreach ($arguments['listeners'] as $registeredListener) {
if ($registeredListener instanceof SymfonyTestsListener) {
$registeredListener->globalListenerDisabled();
$registeredLocally = true;
break;
}
}
if (!$registeredLocally) {
$arguments['listeners'][] = $listener;
}
}
}

View File

@ -1,26 +0,0 @@
<?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\Bridge\PhpUnit\TextUI;
if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Version::id(), '6.0.0', '<')) {
class_alias('Symfony\Bridge\PhpUnit\Legacy\TestRunnerForV5', 'Symfony\Bridge\PhpUnit\TextUI\TestRunner');
} elseif (version_compare(\PHPUnit\Runner\Version::id(), '7.0.0', '<')) {
class_alias('Symfony\Bridge\PhpUnit\Legacy\TestRunnerForV6', 'Symfony\Bridge\PhpUnit\TextUI\TestRunner');
} else {
class_alias('Symfony\Bridge\PhpUnit\Legacy\TestRunnerForV7', 'Symfony\Bridge\PhpUnit\TextUI\TestRunner');
}
if (false) {
class TestRunner
{
}
}

View File

@ -48,6 +48,14 @@ class ApplicationTest extends TestCase
$this->colSize = getenv('COLUMNS');
}
protected function tearDown()
{
putenv($this->colSize ? 'COLUMNS='.$this->colSize : 'COLUMNS');
putenv('SHELL_VERBOSITY');
unset($_ENV['SHELL_VERBOSITY']);
unset($_SERVER['SHELL_VERBOSITY']);
}
public static function setUpBeforeClass()
{
self::$fixturesPath = realpath(__DIR__.'/Fixtures/');
@ -1749,14 +1757,6 @@ class ApplicationTest extends TestCase
$tester = new ApplicationTester($application);
$tester->run(['command' => 'foo']);
}
protected function tearDown()
{
putenv($this->colSize ? 'COLUMNS' : 'COLUMNS='.$this->colSize);
putenv('SHELL_VERBOSITY');
unset($_ENV['SHELL_VERBOSITY']);
unset($_SERVER['SHELL_VERBOSITY']);
}
}
class CustomApplication extends Application

View File

@ -33,7 +33,7 @@ class ProgressBarTest extends TestCase
protected function tearDown()
{
putenv($this->colSize ? 'COLUMNS' : 'COLUMNS='.$this->colSize);
putenv($this->colSize ? 'COLUMNS='.$this->colSize : 'COLUMNS');
}
public function testMultipleStart()

View File

@ -38,7 +38,7 @@ class SymfonyStyleTest extends TestCase
protected function tearDown()
{
putenv($this->colSize ? 'COLUMNS' : 'COLUMNS='.$this->colSize);
putenv($this->colSize ? 'COLUMNS='.$this->colSize : 'COLUMNS');
$this->command = null;
$this->tester = null;
}

View File

@ -25,6 +25,12 @@ class TerminalTest extends TestCase
$this->lineSize = getenv('LINES');
}
protected function tearDown()
{
putenv($this->colSize ? 'COLUMNS='.$this->colSize : 'COLUMNS');
putenv($this->lineSize ? 'LINES' : 'LINES='.$this->lineSize);
}
public function test()
{
putenv('COLUMNS=100');
@ -40,12 +46,6 @@ class TerminalTest extends TestCase
$this->assertSame(60, $terminal->getHeight());
}
protected function tearDown()
{
putenv($this->colSize ? 'COLUMNS' : 'COLUMNS='.$this->colSize);
putenv($this->lineSize ? 'LINES' : 'LINES='.$this->lineSize);
}
public function test_zero_values()
{
putenv('COLUMNS=0');

View File

@ -385,6 +385,8 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
return $object;
}
// clean up even if no match
unset($context[static::OBJECT_TO_POPULATE]);
$constructor = $this->getConstructor($data, $class, $context, $reflectionClass, $allowedAttributes);
if ($constructor) {
@ -453,7 +455,7 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
throw new LogicException(sprintf('Cannot create an instance of %s from serialized data because the serializer inject in "%s" is not a denormalizer', $parameter->getClass(), self::class));
}
$parameterClass = $parameter->getClass()->getName();
$parameterData = $this->serializer->denormalize($parameterData, $parameterClass, $format, $this->createChildContext($context, $parameterName));
$parameterData = $this->serializer->denormalize($parameterData, $parameterClass, $format, $this->createChildContext($context, $parameterName, $format));
}
} catch (\ReflectionException $e) {
throw new RuntimeException(sprintf('Could not determine the class of the parameter "%s".', $parameterName), 0, $e);
@ -468,14 +470,15 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
}
/**
* @param array $parentContext
* @param string $attribute
* @param array $parentContext
* @param string $attribute Attribute name
* @param string|null $format
*
* @return array
*
* @internal
*/
protected function createChildContext(array $parentContext, $attribute)
protected function createChildContext(array $parentContext, $attribute/*, string $format = null */)
{
if (isset($parentContext[self::ATTRIBUTES][$attribute])) {
$parentContext[self::ATTRIBUTES] = $parentContext[self::ATTRIBUTES][$attribute];

View File

@ -129,7 +129,7 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
throw new LogicException(sprintf('Cannot normalize attribute "%s" because the injected serializer is not a normalizer', $attribute));
}
$data = $this->updateData($data, $attribute, $this->serializer->normalize($attributeValue, $format, $this->createChildContext($context, $attribute)), $class, $format, $context);
$data = $this->updateData($data, $attribute, $this->serializer->normalize($attributeValue, $format, $this->createChildContext($context, $attribute, $format)), $class, $format, $context);
}
return $data;
@ -185,21 +185,17 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
return $allowedAttributes;
}
if ($context[self::ATTRIBUTES] ?? $this->defaultContext[self::ATTRIBUTES] ?? false) {
return $this->extractAttributes($object, $format, $context);
}
if (isset($this->attributesCache[$class])) {
return $this->attributesCache[$class];
}
$attributes = $this->extractAttributes($object, $format, $context);
if ($this->classDiscriminatorResolver && $mapping = $this->classDiscriminatorResolver->getMappingForMappedObject($object)) {
array_unshift($attributes, $mapping->getTypeProperty());
}
return $this->attributesCache[$class] = $attributes;
if ($context['cache_key']) {
$this->attributesCache[$key] = $attributes;
}
return $attributes;
}
/**
@ -347,7 +343,7 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
throw new LogicException(sprintf('Cannot denormalize attribute "%s" for class "%s" because injected serializer is not a denormalizer', $attribute, $class));
}
$childContext = $this->createChildContext($context, $attribute);
$childContext = $this->createChildContext($context, $attribute, $format);
if ($this->serializer->supportsDenormalization($data, $class, $format, $childContext)) {
return $this->serializer->denormalize($data, $class, $format, $childContext);
}
@ -477,7 +473,32 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
}
/**
* Gets the cache key to use.
* Overwritten to update the cache key for the child.
*
* We must not mix up the attribute cache between parent and children.
*
* {@inheritdoc}
*/
protected function createChildContext(array $parentContext, $attribute/*, string $format = null */)
{
if (\func_num_args() >= 3) {
$format = \func_get_arg(2);
} else {
// will be deprecated in version 4
$format = null;
}
$context = parent::createChildContext($parentContext, $attribute, $format);
// format is already included in the cache_key of the parent.
$context['cache_key'] = $this->getCacheKey($format, $context);
return $context;
}
/**
* Builds the cache key for the attributes cache.
*
* The key must be different for every option in the context that could change which attributes should be handled.
*
* @return bool|string
*/
@ -487,9 +508,14 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
unset($context[$key]);
}
unset($context[self::EXCLUDE_FROM_CACHE_KEY]);
unset($context['cache_key']); // avoid artificially different keys
try {
return md5($format.serialize($context));
return md5($format.serialize([
'context' => $context,
'ignored' => $this->ignoredAttributes,
'camelized' => $this->camelizedAttributes,
]));
} catch (\Exception $exception) {
// The context cannot be serialized, skip the cache
return false;

View File

@ -394,6 +394,30 @@ class ObjectNormalizerTest extends TestCase
);
}
public function testObjectToPopulateNoMatch()
{
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$this->normalizer = new ObjectNormalizer($classMetadataFactory, null, null, new PhpDocExtractor());
new Serializer([$this->normalizer]);
$objectToPopulate = new ObjectInner();
$objectToPopulate->foo = 'foo';
$outer = $this->normalizer->denormalize([
'foo' => 'foo',
'inner' => [
'bar' => 'bar',
],
], ObjectOuter::class, null, [ObjectNormalizer::OBJECT_TO_POPULATE => $objectToPopulate]);
$this->assertInstanceOf(ObjectOuter::class, $outer);
$inner = $outer->getInner();
$this->assertInstanceOf(ObjectInner::class, $inner);
$this->assertNotSame($objectToPopulate, $inner);
$this->assertSame('bar', $inner->bar);
$this->assertNull($inner->foo);
}
/**
* @dataProvider provideCallbacks
*/
@ -472,6 +496,16 @@ class ObjectNormalizerTest extends TestCase
['fooBar' => 'foobar'],
$this->normalizer->normalize($obj, 'any')
);
$this->normalizer->setIgnoredAttributes(['foo', 'baz', 'camelCase', 'object']);
$this->assertEquals(
[
'fooBar' => 'foobar',
'bar' => 'bar',
],
$this->normalizer->normalize($obj, 'any')
);
}
public function testIgnoredAttributesDenormalize()
@ -1208,6 +1242,9 @@ class ObjectOuter
{
public $foo;
public $bar;
/**
* @var ObjectInner
*/
private $inner;
private $date;