Merge branch '4.1'

* 4.1:
  Add color support for Hyper terminal .
  [HttpFoundation] Fix tests: new message for status 425
  [Doctrine Bridge] Fixed usage of wrong variable when tagged subscriber is invalid
  [Workflow] Update phpdoc to fit a used className
  [PropertyInfo] added handling of nullable types in PhpDoc
  [HttpKernel] Make AbstractTestSessionListener compatible with CookieClearingLogoutHandler
  [Cache] provider does not respect option maxIdLength with versioning enabled
  [Form] Fix fixtures for forward compat
  [Lock] Fix SemaphoreStoreTest on OS X
  Ensure the class discriminator mechanism works with serialization groups as well
  fix handling of empty DI extension configs
This commit is contained in:
Nicolas Grekas 2018-07-03 20:14:13 +02:00
commit a32393ba37
29 changed files with 253 additions and 147 deletions

View File

@ -102,7 +102,7 @@
}, },
"conflict": { "conflict": {
"phpdocumentor/reflection-docblock": "<3.0||>=3.2.0,<3.2.2", "phpdocumentor/reflection-docblock": "<3.0||>=3.2.0,<3.2.2",
"phpdocumentor/type-resolver": "<0.2.1", "phpdocumentor/type-resolver": "<0.3.0",
"phpunit/phpunit": "<5.4.3" "phpunit/phpunit": "<5.4.3"
}, },
"provide": { "provide": {

View File

@ -69,7 +69,7 @@ class RegisterEventListenersAndSubscribersPass implements CompilerPassInterface
$connections = isset($tag['connection']) ? array($tag['connection']) : array_keys($this->connections); $connections = isset($tag['connection']) ? array($tag['connection']) : array_keys($this->connections);
foreach ($connections as $con) { foreach ($connections as $con) {
if (!isset($this->connections[$con])) { if (!isset($this->connections[$con])) {
throw new RuntimeException(sprintf('The Doctrine connection "%s" referenced in service "%s" does not exist. Available connections names: %s', $con, $taggedSubscriber, implode(', ', array_keys($this->connections)))); throw new RuntimeException(sprintf('The Doctrine connection "%s" referenced in service "%s" does not exist. Available connections names: %s', $con, $id, implode(', ', array_keys($this->connections))));
} }
$this->getEventManagerDef($container, $con)->addMethodCall('addEventSubscriber', array(new Reference($id))); $this->getEventManagerDef($container, $con)->addMethodCall('addEventSubscriber', array(new Reference($id)));

View File

@ -316,7 +316,8 @@ class DeprecationErrorHandler
&& sapi_windows_vt100_support(STDOUT)) && sapi_windows_vt100_support(STDOUT))
|| false !== getenv('ANSICON') || false !== getenv('ANSICON')
|| 'ON' === getenv('ConEmuANSI') || 'ON' === getenv('ConEmuANSI')
|| 'xterm' === getenv('TERM'); || 'xterm' === getenv('TERM')
|| 'Hyper' === getenv('TERM_PROGRAM');
} }
if (function_exists('stream_isatty')) { if (function_exists('stream_isatty')) {

View File

@ -34,6 +34,34 @@ class MaxIdLengthAdapterTest extends TestCase
$cache->hasItem(str_repeat('-', 39)); $cache->hasItem(str_repeat('-', 39));
} }
public function testLongKeyVersioning()
{
$cache = $this->getMockBuilder(MaxIdLengthAdapter::class)
->setConstructorArgs(array(str_repeat('-', 26)))
->getMock();
$reflectionClass = new \ReflectionClass(AbstractAdapter::class);
$reflectionMethod = $reflectionClass->getMethod('getId');
$reflectionMethod->setAccessible(true);
// No versioning enabled
$this->assertEquals('--------------------------:------------', $reflectionMethod->invokeArgs($cache, array(str_repeat('-', 12))));
$this->assertLessThanOrEqual(50, strlen($reflectionMethod->invokeArgs($cache, array(str_repeat('-', 12)))));
$this->assertLessThanOrEqual(50, strlen($reflectionMethod->invokeArgs($cache, array(str_repeat('-', 23)))));
$this->assertLessThanOrEqual(50, strlen($reflectionMethod->invokeArgs($cache, array(str_repeat('-', 40)))));
$reflectionProperty = $reflectionClass->getProperty('versioningIsEnabled');
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($cache, true);
// Versioning enabled
$this->assertEquals('--------------------------:1:------------', $reflectionMethod->invokeArgs($cache, array(str_repeat('-', 12))));
$this->assertLessThanOrEqual(50, strlen($reflectionMethod->invokeArgs($cache, array(str_repeat('-', 12)))));
$this->assertLessThanOrEqual(50, strlen($reflectionMethod->invokeArgs($cache, array(str_repeat('-', 23)))));
$this->assertLessThanOrEqual(50, strlen($reflectionMethod->invokeArgs($cache, array(str_repeat('-', 40)))));
}
/** /**
* @expectedException \Symfony\Component\Cache\Exception\InvalidArgumentException * @expectedException \Symfony\Component\Cache\Exception\InvalidArgumentException
* @expectedExceptionMessage Namespace must be 26 chars max, 40 given ("----------------------------------------") * @expectedExceptionMessage Namespace must be 26 chars max, 40 given ("----------------------------------------")

View File

@ -122,6 +122,7 @@ trait AbstractTrait
} }
if ($cleared = true === $cleared || array() === $cleared) { if ($cleared = true === $cleared || array() === $cleared) {
$this->namespaceVersion = $namespaceVersion; $this->namespaceVersion = $namespaceVersion;
$this->ids = array();
} }
} }
@ -196,6 +197,7 @@ trait AbstractTrait
$wasEnabled = $this->versioningIsEnabled; $wasEnabled = $this->versioningIsEnabled;
$this->versioningIsEnabled = (bool) $enable; $this->versioningIsEnabled = (bool) $enable;
$this->namespaceVersion = ''; $this->namespaceVersion = '';
$this->ids = array();
return $wasEnabled; return $wasEnabled;
} }
@ -242,6 +244,7 @@ trait AbstractTrait
private function getId($key) private function getId($key)
{ {
if ($this->versioningIsEnabled && '' === $this->namespaceVersion) { if ($this->versioningIsEnabled && '' === $this->namespaceVersion) {
$this->ids = array();
$this->namespaceVersion = '1:'; $this->namespaceVersion = '1:';
try { try {
foreach ($this->doFetch(array('@'.$this->namespace)) as $v) { foreach ($this->doFetch(array('@'.$this->namespace)) as $v) {
@ -262,7 +265,7 @@ trait AbstractTrait
} }
if (\strlen($id = $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) { if (\strlen($id = $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) {
// Use MD5 to favor speed over security, which is not an issue here // Use MD5 to favor speed over security, which is not an issue here
$this->ids[$key] = $id = substr_replace(base64_encode(hash('md5', $key, true)), ':', -2); $this->ids[$key] = $id = substr_replace(base64_encode(hash('md5', $key, true)), ':', -(\strlen($this->namespaceVersion) + 2));
$id = $this->namespace.$this->namespaceVersion.$id; $id = $this->namespace.$this->namespaceVersion.$id;
} }

View File

@ -98,7 +98,8 @@ class StreamOutput extends Output
&& @sapi_windows_vt100_support($this->stream)) && @sapi_windows_vt100_support($this->stream))
|| false !== getenv('ANSICON') || false !== getenv('ANSICON')
|| 'ON' === getenv('ConEmuANSI') || 'ON' === getenv('ConEmuANSI')
|| 'xterm' === getenv('TERM'); || 'xterm' === getenv('TERM')
|| 'Hyper' === getenv('TERM_PROGRAM');
} }
if (function_exists('stream_isatty')) { if (function_exists('stream_isatty')) {

View File

@ -269,7 +269,7 @@ class SymfonyStyle extends OutputStyle
{ {
$progressBar = parent::createProgressBar($max); $progressBar = parent::createProgressBar($max);
if ('\\' !== DIRECTORY_SEPARATOR) { if ('\\' !== DIRECTORY_SEPARATOR || 'Hyper' === getenv('TERM_PROGRAM')) {
$progressBar->setEmptyBarCharacter('░'); // light shade character \u2591 $progressBar->setEmptyBarCharacter('░'); // light shade character \u2591
$progressBar->setProgressCharacter(''); $progressBar->setProgressCharacter('');
$progressBar->setBarCharacter('▓'); // dark shade character \u2593 $progressBar->setBarCharacter('▓'); // dark shade character \u2593

View File

@ -65,7 +65,7 @@ class ValidateEnvPlaceholdersPass implements CompilerPassInterface
$processor = new Processor(); $processor = new Processor();
foreach ($extensions as $name => $extension) { foreach ($extensions as $name => $extension) {
if (!$extension instanceof ConfigurationExtensionInterface || !$config = $container->getExtensionConfig($name)) { if (!$extension instanceof ConfigurationExtensionInterface || !$config = array_filter($container->getExtensionConfig($name))) {
// this extension has no semantic configuration or was not called // this extension has no semantic configuration or was not called
continue; continue;
} }

View File

@ -15,9 +15,9 @@ Symfony\Component\Form\Extension\Core\Type\ChoiceType (choice_translation_domain
---------------- --------------------%s ---------------- --------------------%s
Allowed values - %s Allowed values - %s
---------------- --------------------%s ---------------- --------------------%s
Normalizer Closure%s{ %s Normalizer Closure%s{%w
parameters: 2 %s parameters: 2 %s
file: "%s%eExtension%eCore%eType%eChoiceType.php" file: "%s%eExtension%eCore%eType%eChoiceType.php"%w
line: "%s to %s" %s line: "%s to %s" %s
} %s } %s
---------------- --------------------%s ---------------- --------------------%s

View File

@ -8,14 +8,14 @@ Symfony\Component\Form\Tests\Console\Descriptor\FooType (empty_data)
Default Value: null %s Default Value: null %s
%s %s
Closure(s): [ %s Closure(s): [ %s
Closure%s{ %s Closure%s{%w
parameters: 1 %s parameters: 1 %s
file: "%s%eExtension%eCore%eType%eFormType.php" file: "%s%eExtension%eCore%eType%eFormType.php"%w
line: "%s to %s" %s line: "%s to %s" %s
}, %s }, %s
Closure%s{ %s Closure%s{%w
parameters: 2 %s parameters: 2 %s
file: "%s%eTests%eConsole%eDescriptor%eAbstractDescriptorTest.php" file: "%s%eTests%eConsole%eDescriptor%eAbstractDescriptorTest.php"%w
line: "%s to %s" %s line: "%s to %s" %s
} %s } %s
] %s ] %s

View File

@ -16,9 +16,9 @@ Symfony\Component\Form\Tests\Console\Descriptor\FooType (foo)
"baz" %s "baz" %s
] %s ] %s
---------------- --------------------%s ---------------- --------------------%s
Normalizer Closure%s{ %s Normalizer Closure%s{%w
parameters: 2 %s parameters: 2 %s
file: "%s%eTests%eConsole%eDescriptor%eAbstractDescriptorTest.php" file: "%s%eTests%eConsole%eDescriptor%eAbstractDescriptorTest.php"%w
line: "%s to %s" %s line: "%s to %s" %s
} %s } %s
---------------- --------------------%s ---------------- --------------------%s

View File

@ -64,7 +64,12 @@ class Response
const HTTP_UNPROCESSABLE_ENTITY = 422; // RFC4918 const HTTP_UNPROCESSABLE_ENTITY = 422; // RFC4918
const HTTP_LOCKED = 423; // RFC4918 const HTTP_LOCKED = 423; // RFC4918
const HTTP_FAILED_DEPENDENCY = 424; // RFC4918 const HTTP_FAILED_DEPENDENCY = 424; // RFC4918
/**
* @deprecated
*/
const HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL = 425; // RFC2817 const HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL = 425; // RFC2817
const HTTP_TOO_EARLY = 425; // RFC-ietf-httpbis-replay-04
const HTTP_UPGRADE_REQUIRED = 426; // RFC2817 const HTTP_UPGRADE_REQUIRED = 426; // RFC2817
const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585 const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585
const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585 const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585
@ -169,7 +174,7 @@ class Response
422 => 'Unprocessable Entity', // RFC4918 422 => 'Unprocessable Entity', // RFC4918
423 => 'Locked', // RFC4918 423 => 'Locked', // RFC4918
424 => 'Failed Dependency', // RFC4918 424 => 'Failed Dependency', // RFC4918
425 => 'Reserved for WebDAV advanced collections expired proposal', // RFC2817 425 => 'Too Early', // RFC-ietf-httpbis-replay-04
426 => 'Upgrade Required', // RFC2817 426 => 'Upgrade Required', // RFC2817
428 => 'Precondition Required', // RFC6585 428 => 'Precondition Required', // RFC6585
429 => 'Too Many Requests', // RFC6585 429 => 'Too Many Requests', // RFC6585

View File

@ -73,6 +73,13 @@ abstract class AbstractTestSessionListener implements EventSubscriberInterface
if ($session instanceof Session ? !$session->isEmpty() || (null !== $this->sessionId && $session->getId() !== $this->sessionId) : $wasStarted) { if ($session instanceof Session ? !$session->isEmpty() || (null !== $this->sessionId && $session->getId() !== $this->sessionId) : $wasStarted) {
$params = session_get_cookie_params(); $params = session_get_cookie_params();
foreach ($event->getResponse()->headers->getCookies() as $cookie) {
if ($session->getName() === $cookie->getName() && $params['path'] === $cookie->getPath() && $params['domain'] == $cookie->getDomain()) {
return;
}
}
$event->getResponse()->headers->setCookie(new Cookie($session->getName(), $session->getId(), 0 === $params['lifetime'] ? 0 : time() + $params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly'])); $event->getResponse()->headers->setCookie(new Cookie($session->getName(), $session->getId(), 0 === $params['lifetime'] ? 0 : time() + $params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly']));
$this->sessionId = $session->getId(); $this->sessionId = $session->getId();
} }

View File

@ -106,6 +106,36 @@ class TestSessionListenerTest extends TestCase
$this->assertNotEmpty($response->headers->getCookies()); $this->assertNotEmpty($response->headers->getCookies());
} }
/**
* @dataProvider anotherCookieProvider
*/
public function testSessionWithNewSessionIdAndNewCookieDoesNotSendAnotherCookie($existing, array $expected)
{
$this->sessionHasBeenStarted();
$this->sessionIsEmpty();
$this->fixSessionId('456');
$kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock();
$request = Request::create('/', 'GET', array(), array('MOCKSESSID' => '123'));
$event = new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST);
$this->listener->onKernelRequest($event);
$response = new Response('', 200, array('Set-Cookie' => $existing));
$response = $this->filterResponse(new Request(), HttpKernelInterface::MASTER_REQUEST, $response);
$this->assertSame($expected, $response->headers->get('Set-Cookie', null, false));
}
public function anotherCookieProvider()
{
return array(
'same' => array('MOCKSESSID=789; path=/', array('MOCKSESSID=789; path=/')),
'different domain' => array('MOCKSESSID=789; path=/; domain=example.com', array('MOCKSESSID=789; path=/; domain=example.com', 'MOCKSESSID=456; path=/')),
'different path' => array('MOCKSESSID=789; path=/foo', array('MOCKSESSID=789; path=/foo', 'MOCKSESSID=456; path=/')),
);
}
public function testUnstartedSessionIsNotSave() public function testUnstartedSessionIsNotSave()
{ {
$this->sessionHasNotBeenStarted(); $this->sessionHasNotBeenStarted();
@ -133,10 +163,10 @@ class TestSessionListenerTest extends TestCase
$this->assertTrue(true); $this->assertTrue(true);
} }
private function filterResponse(Request $request, $type = HttpKernelInterface::MASTER_REQUEST) private function filterResponse(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, Response $response = null)
{ {
$request->setSession($this->session); $request->setSession($this->session);
$response = new Response(); $response = $response ?: new Response();
$kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(); $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock();
$event = new FilterResponseEvent($kernel, $request, $type, $response); $event = new FilterResponseEvent($kernel, $request, $type, $response);

View File

@ -46,13 +46,22 @@ class SemaphoreStoreTest extends AbstractStoreTest
private function getOpenedSemaphores() private function getOpenedSemaphores()
{ {
if ('Darwin' === PHP_OS) {
$lines = explode(PHP_EOL, trim(`ipcs -s`));
if (-1 === $start = array_search('Semaphores:', $lines)) {
throw new \Exception('Failed to extract list of opened semaphores. Expected a Semaphore list, got '.implode(PHP_EOL, $lines));
}
return \count(\array_slice($lines, ++$start));
}
$lines = explode(PHP_EOL, trim(`LC_ALL=C ipcs -su`)); $lines = explode(PHP_EOL, trim(`LC_ALL=C ipcs -su`));
if ('------ Semaphore Status --------' !== $lines[0]) { if ('------ Semaphore Status --------' !== $lines[0]) {
throw new \Exception('Failed to extract list of opend semaphores. Expect a Semaphore status, got '.implode(PHP_EOL, $lines)); throw new \Exception('Failed to extract list of opened semaphores. Expected a Semaphore status, got '.implode(PHP_EOL, $lines));
} }
list($key, $value) = explode(' = ', $lines[1]); list($key, $value) = explode(' = ', $lines[1]);
if ('used arrays' !== $key) { if ('used arrays' !== $key) {
throw new \Exception('Failed to extract list of opend semaphores. Expect a used arrays key, got '.implode(PHP_EOL, $lines)); throw new \Exception('Failed to extract list of opened semaphores. Expected a "used arrays" key, got '.implode(PHP_EOL, $lines));
} }
return (int) $value; return (int) $value;

View File

@ -41,6 +41,21 @@ class PhpDocExtractorTest extends TestCase
$this->assertSame($longDescription, $this->extractor->getLongDescription('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property)); $this->assertSame($longDescription, $this->extractor->getLongDescription('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property));
} }
public function testParamTagTypeIsOmitted()
{
$this->assertNull($this->extractor->getTypes(OmittedParamTagTypeDocBlock::class, 'omittedType'));
}
/**
* @dataProvider typesWithNoPrefixesProvider
*/
public function testExtractTypesWithNoPrefixes($property, array $type = null)
{
$noPrefixExtractor = new PhpDocExtractor(null, array(), array(), array());
$this->assertEquals($type, $noPrefixExtractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property));
}
public function typesProvider() public function typesProvider()
{ {
return array( return array(
@ -69,8 +84,10 @@ class PhpDocExtractorTest extends TestCase
array('d', array(new Type(Type::BUILTIN_TYPE_BOOL)), null, null), array('d', array(new Type(Type::BUILTIN_TYPE_BOOL)), null, null),
array('e', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_RESOURCE))), null, null), array('e', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_RESOURCE))), null, null),
array('f', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTime'))), null, null), array('f', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTime'))), null, null),
array('g', array(new Type(Type::BUILTIN_TYPE_BOOL, true)), null, null), array('g', array(new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true)), 'Nullable array.', null),
array('array', array(new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true)), 'Nullable array.', null), array('h', array(new Type(Type::BUILTIN_TYPE_STRING, true)), null, null),
array('i', array(new Type(Type::BUILTIN_TYPE_STRING, true), new Type(Type::BUILTIN_TYPE_INT, true)), null, null),
array('j', array(new Type(Type::BUILTIN_TYPE_OBJECT, true, 'DateTime')), null, null),
array('donotexist', null, null, null), array('donotexist', null, null, null),
array('staticGetter', null, null, null), array('staticGetter', null, null, null),
array('staticSetter', null, null, null), array('staticSetter', null, null, null),
@ -111,11 +128,6 @@ class PhpDocExtractorTest extends TestCase
); );
} }
public function testParamTagTypeIsOmitted()
{
$this->assertNull($this->extractor->getTypes(OmittedParamTagTypeDocBlock::class, 'omittedType'));
}
/** /**
* @dataProvider typesWithCustomPrefixesProvider * @dataProvider typesWithCustomPrefixesProvider
*/ */
@ -154,23 +166,16 @@ class PhpDocExtractorTest extends TestCase
array('d', array(new Type(Type::BUILTIN_TYPE_BOOL)), null, null), array('d', array(new Type(Type::BUILTIN_TYPE_BOOL)), null, null),
array('e', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_RESOURCE))), null, null), array('e', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_RESOURCE))), null, null),
array('f', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTime'))), null, null), array('f', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTime'))), null, null),
array('g', null), array('g', array(new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true)), 'Nullable array.', null),
array('h', array(new Type(Type::BUILTIN_TYPE_STRING, true)), null, null),
array('i', array(new Type(Type::BUILTIN_TYPE_STRING, true), new Type(Type::BUILTIN_TYPE_INT, true)), null, null),
array('j', array(new Type(Type::BUILTIN_TYPE_OBJECT, true, 'DateTime')), null, null),
array('donotexist', null, null, null), array('donotexist', null, null, null),
array('staticGetter', null, null, null), array('staticGetter', null, null, null),
array('staticSetter', null, null, null), array('staticSetter', null, null, null),
); );
} }
/**
* @dataProvider typesWithNoPrefixesProvider
*/
public function testExtractTypesWithNoPrefixes($property, array $type = null)
{
$noPrefixExtractor = new PhpDocExtractor(null, array(), array(), array());
$this->assertEquals($type, $noPrefixExtractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property));
}
public function typesWithNoPrefixesProvider() public function typesWithNoPrefixesProvider()
{ {
return array( return array(
@ -199,7 +204,10 @@ class PhpDocExtractorTest extends TestCase
array('d', null, null, null), array('d', null, null, null),
array('e', null, null, null), array('e', null, null, null),
array('f', null, null, null), array('f', null, null, null),
array('g', null), array('g', array(new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true)), 'Nullable array.', null),
array('h', array(new Type(Type::BUILTIN_TYPE_STRING, true)), null, null),
array('i', array(new Type(Type::BUILTIN_TYPE_STRING, true), new Type(Type::BUILTIN_TYPE_INT, true)), null, null),
array('j', array(new Type(Type::BUILTIN_TYPE_OBJECT, true, 'DateTime')), null, null),
array('donotexist', null, null, null), array('donotexist', null, null, null),
array('staticGetter', null, null, null), array('staticGetter', null, null, null),
array('staticSetter', null, null, null), array('staticSetter', null, null, null),

View File

@ -40,7 +40,10 @@ class ReflectionExtractorTest extends TestCase
'collection', 'collection',
'B', 'B',
'Guid', 'Guid',
'array', 'g',
'h',
'i',
'j',
'emptyVar', 'emptyVar',
'iteratorCollection', 'iteratorCollection',
'iteratorCollectionWithKey', 'iteratorCollectionWithKey',
@ -61,7 +64,6 @@ class ReflectionExtractorTest extends TestCase
'd', 'd',
'e', 'e',
'f', 'f',
'g',
), ),
$this->extractor->getProperties('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy') $this->extractor->getProperties('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy')
); );
@ -80,7 +82,10 @@ class ReflectionExtractorTest extends TestCase
'collection', 'collection',
'B', 'B',
'Guid', 'Guid',
'array', 'g',
'h',
'i',
'j',
'emptyVar', 'emptyVar',
'iteratorCollection', 'iteratorCollection',
'iteratorCollectionWithKey', 'iteratorCollectionWithKey',
@ -111,7 +116,10 @@ class ReflectionExtractorTest extends TestCase
'collection', 'collection',
'B', 'B',
'Guid', 'Guid',
'array', 'g',
'h',
'i',
'j',
'emptyVar', 'emptyVar',
'iteratorCollection', 'iteratorCollection',
'iteratorCollectionWithKey', 'iteratorCollectionWithKey',
@ -127,24 +135,6 @@ class ReflectionExtractorTest extends TestCase
); );
} }
public function testGetPropertiesPhp71()
{
$noPrefixExtractor = new ReflectionExtractor();
$this->assertSame(
array(
'string',
'stringOrNull',
'foo',
'buz',
'bar',
'baz',
'intWithAccessor',
),
$noPrefixExtractor->getProperties('Symfony\Component\PropertyInfo\Tests\Fixtures\Php71Dummy')
);
}
/** /**
* @dataProvider typesProvider * @dataProvider typesProvider
*/ */
@ -206,22 +196,9 @@ class ReflectionExtractorTest extends TestCase
array('bar', array(new Type(Type::BUILTIN_TYPE_INT, true))), array('bar', array(new Type(Type::BUILTIN_TYPE_INT, true))),
array('baz', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)))), array('baz', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)))),
array('donotexist', null), array('donotexist', null),
array('string', array(new Type(Type::BUILTIN_TYPE_STRING, false))),
array('stringOrNull', array(new Type(Type::BUILTIN_TYPE_STRING, true))),
array('intPrivate', array(new Type(Type::BUILTIN_TYPE_INT, false))),
array('intWithAccessor', array(new Type(Type::BUILTIN_TYPE_INT, false))),
); );
} }
public function testExtractPhp71TypeWithParentConstructor()
{
$property = 'string';
$type = array(new Type(Type::BUILTIN_TYPE_STRING, false));
$this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Php71DummyChild', $property, array()));
$this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Php71DummyChild2', $property, array()));
$this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Php71DummyChild3', $property, array()));
}
/** /**
* @dataProvider getReadableProperties * @dataProvider getReadableProperties
*/ */
@ -245,12 +222,10 @@ class ReflectionExtractorTest extends TestCase
array('d', true), array('d', true),
array('e', false), array('e', false),
array('f', false), array('f', false),
array('g', true),
array('Id', true), array('Id', true),
array('id', true), array('id', true),
array('Guid', true), array('Guid', true),
array('guid', false), array('guid', false),
array('guid', false),
); );
} }
@ -277,7 +252,6 @@ class ReflectionExtractorTest extends TestCase
array('d', false), array('d', false),
array('e', true), array('e', true),
array('f', true), array('f', true),
array('g', false),
array('Id', false), array('Id', false),
array('Guid', true), array('Guid', true),
array('guid', false), array('guid', false),

View File

@ -66,7 +66,22 @@ class Dummy extends ParentDummy
* *
* @var array|null * @var array|null
*/ */
public $array; public $g;
/**
* @var ?string
*/
public $h;
/**
* @var ?string|int
*/
public $i;
/**
* @var ?\DateTime
*/
public $j;
/** /**
* This should not be removed. * This should not be removed.

View File

@ -75,11 +75,4 @@ class ParentDummy
public function removeF(\DateTime $f) public function removeF(\DateTime $f)
{ {
} }
/**
* @return bool|null
*/
public function hasG()
{
}
} }

View File

@ -16,22 +16,6 @@ namespace Symfony\Component\PropertyInfo\Tests\Fixtures;
*/ */
class Php71Dummy class Php71Dummy
{ {
public $string;
public $stringOrNull;
private $intPrivate;
private $intWithAccessor;
public function __construct(string $string, ?string $stringOrNull, int $intPrivate, int $intWithAccessor)
{
$this->string = $string;
$this->stringOrNull = $stringOrNull;
$this->intPrivate = $intPrivate;
$this->intWithAccessor = $intWithAccessor;
}
public function getFoo(): ?array public function getFoo(): ?array
{ {
} }
@ -47,9 +31,4 @@ class Php71Dummy
public function addBaz(string $baz) public function addBaz(string $baz)
{ {
} }
public function getIntWithAccessor()
{
return $this->intWithAccessor;
}
} }

View File

@ -15,6 +15,7 @@ use phpDocumentor\Reflection\Type as DocType;
use phpDocumentor\Reflection\Types\Collection; use phpDocumentor\Reflection\Types\Collection;
use phpDocumentor\Reflection\Types\Compound; use phpDocumentor\Reflection\Types\Compound;
use phpDocumentor\Reflection\Types\Null_; use phpDocumentor\Reflection\Types\Null_;
use phpDocumentor\Reflection\Types\Nullable;
use Symfony\Component\PropertyInfo\Type; use Symfony\Component\PropertyInfo\Type;
/** /**
@ -35,6 +36,11 @@ final class PhpDocTypeHelper
$types = array(); $types = array();
$nullable = false; $nullable = false;
if ($varType instanceof Nullable) {
$nullable = true;
$varType = $varType->getActualType();
}
if (!$varType instanceof Compound) { if (!$varType instanceof Compound) {
if ($varType instanceof Null_) { if ($varType instanceof Null_) {
$nullable = true; $nullable = true;

View File

@ -35,7 +35,7 @@
}, },
"conflict": { "conflict": {
"phpdocumentor/reflection-docblock": "<3.0||>=3.2.0,<3.2.2", "phpdocumentor/reflection-docblock": "<3.0||>=3.2.0,<3.2.2",
"phpdocumentor/type-resolver": "<0.2.1", "phpdocumentor/type-resolver": "<0.3.0",
"symfony/dependency-injection": "<3.4" "symfony/dependency-injection": "<3.4"
}, },
"suggest": { "suggest": {

View File

@ -16,6 +16,7 @@ use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface; use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
use Symfony\Component\Serializer\Exception\RuntimeException; use Symfony\Component\Serializer\Exception\RuntimeException;
use Symfony\Component\Serializer\Mapping\AttributeMetadata;
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface; use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
@ -131,4 +132,24 @@ class ObjectNormalizer extends AbstractObjectNormalizer
// Properties not found are ignored // Properties not found are ignored
} }
} }
/**
* {@inheritdoc}
*/
protected function getAllowedAttributes($classOrObject, array $context, $attributesAsString = false)
{
if (false === $allowedAttributes = parent::getAllowedAttributes($classOrObject, $context, $attributesAsString)) {
return false;
}
if (null !== $this->classDiscriminatorResolver && null !== $discriminatorMapping = $this->classDiscriminatorResolver->getMappingForMappedObject($classOrObject)) {
$allowedAttributes[] = $attributesAsString ? $discriminatorMapping->getTypeProperty() : new AttributeMetadata($discriminatorMapping->getTypeProperty());
foreach ($discriminatorMapping->getTypesMapping() as $class) {
$allowedAttributes = array_merge($allowedAttributes, parent::getAllowedAttributes($class, $context, $attributesAsString));
}
}
return $allowedAttributes;
}
} }

View File

@ -15,8 +15,8 @@ use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
/** /**
* @DiscriminatorMap(typeProperty="type", mapping={ * @DiscriminatorMap(typeProperty="type", mapping={
* "first"="Symfony\Component\Serializer\Tests\Fixtures\AbstractDummyFirstChild", * "one"="Symfony\Component\Serializer\Tests\Fixtures\DummyMessageNumberOne",
* "second"="Symfony\Component\Serializer\Tests\Fixtures\AbstractDummySecondChild" * "two"="Symfony\Component\Serializer\Tests\Fixtures\DummyMessageNumberTwo"
* }) * })
* *
* @author Samuel Roze <samuel.roze@gmail.com> * @author Samuel Roze <samuel.roze@gmail.com>

View File

@ -11,10 +11,17 @@
namespace Symfony\Component\Serializer\Tests\Fixtures; namespace Symfony\Component\Serializer\Tests\Fixtures;
use Symfony\Component\Serializer\Annotation\Groups;
/** /**
* @author Samuel Roze <samuel.roze@gmail.com> * @author Samuel Roze <samuel.roze@gmail.com>
*/ */
class DummyMessageNumberOne implements DummyMessageInterface class DummyMessageNumberOne implements DummyMessageInterface
{ {
public $one; public $one;
/**
* @Groups({"two"})
*/
public $two;
} }

View File

@ -0,0 +1,19 @@
<?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 Samuel Roze <samuel.roze@gmail.com>
*/
class DummyMessageNumberTwo implements DummyMessageInterface
{
}

View File

@ -11,11 +11,14 @@
namespace Symfony\Component\Serializer\Tests; namespace Symfony\Component\Serializer\Tests;
use Doctrine\Common\Annotations\AnnotationReader;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping;
use Symfony\Component\Serializer\Mapping\ClassMetadata; use Symfony\Component\Serializer\Mapping\ClassMetadata;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
@ -398,11 +401,9 @@ class SerializerTest extends TestCase
$example = new DummyMessageNumberOne(); $example = new DummyMessageNumberOne();
$example->one = 1; $example->one = 1;
$jsonData = '{"message-type":"one","one":1}'; $jsonData = '{"type":"one","one":1,"two":null}';
$discriminatorResolver = new ClassDiscriminatorFromClassMetadata($this->metadataFactoryMockForDummyInterface());
$serializer = new Serializer(array(new ObjectNormalizer(null, null, null, null, $discriminatorResolver)), array('json' => new JsonEncoder()));
$serializer = $this->serializerWithClassDiscriminator();
$deserialized = $serializer->deserialize($jsonData, DummyMessageInterface::class, 'json'); $deserialized = $serializer->deserialize($jsonData, DummyMessageInterface::class, 'json');
$this->assertEquals($example, $deserialized); $this->assertEquals($example, $deserialized);
@ -410,51 +411,48 @@ class SerializerTest extends TestCase
$this->assertEquals($jsonData, $serialized); $this->assertEquals($jsonData, $serialized);
} }
public function testDeserializeAndSerializeInterfacedObjectsWithTheClassMetadataDiscriminatorResolverAndGroups()
{
$example = new DummyMessageNumberOne();
$example->two = 2;
$serializer = $this->serializerWithClassDiscriminator();
$deserialized = $serializer->deserialize('{"type":"one","one":1,"two":2}', DummyMessageInterface::class, 'json', array(
'groups' => array('two'),
));
$this->assertEquals($example, $deserialized);
$serialized = $serializer->serialize($deserialized, 'json', array(
'groups' => array('two'),
));
$this->assertEquals('{"two":2,"type":"one"}', $serialized);
}
/** /**
* @expectedException \Symfony\Component\Serializer\Exception\RuntimeException * @expectedException \Symfony\Component\Serializer\Exception\RuntimeException
* @expectedExceptionMessage The type "second" has no mapped class for the abstract object "Symfony\Component\Serializer\Tests\Fixtures\DummyMessageInterface" * @expectedExceptionMessage The type "second" has no mapped class for the abstract object "Symfony\Component\Serializer\Tests\Fixtures\DummyMessageInterface"
*/ */
public function testExceptionWhenTypeIsNotKnownInDiscriminator() public function testExceptionWhenTypeIsNotKnownInDiscriminator()
{ {
$discriminatorResolver = new ClassDiscriminatorFromClassMetadata($this->metadataFactoryMockForDummyInterface()); $this->serializerWithClassDiscriminator()->deserialize('{"type":"second","one":1}', DummyMessageInterface::class, 'json');
$serializer = new Serializer(array(new ObjectNormalizer(null, null, null, null, $discriminatorResolver)), array('json' => new JsonEncoder()));
$serializer->deserialize('{"message-type":"second","one":1}', DummyMessageInterface::class, 'json');
} }
/** /**
* @expectedException \Symfony\Component\Serializer\Exception\RuntimeException * @expectedException \Symfony\Component\Serializer\Exception\RuntimeException
* @expectedExceptionMessage Type property "message-type" not found for the abstract object "Symfony\Component\Serializer\Tests\Fixtures\DummyMessageInterface" * @expectedExceptionMessage Type property "type" not found for the abstract object "Symfony\Component\Serializer\Tests\Fixtures\DummyMessageInterface"
*/ */
public function testExceptionWhenTypeIsNotInTheBodyToDeserialiaze() public function testExceptionWhenTypeIsNotInTheBodyToDeserialiaze()
{ {
$discriminatorResolver = new ClassDiscriminatorFromClassMetadata($this->metadataFactoryMockForDummyInterface()); $this->serializerWithClassDiscriminator()->deserialize('{"one":1}', DummyMessageInterface::class, 'json');
$serializer = new Serializer(array(new ObjectNormalizer(null, null, null, null, $discriminatorResolver)), array('json' => new JsonEncoder()));
$serializer->deserialize('{"one":1}', DummyMessageInterface::class, 'json');
} }
private function metadataFactoryMockForDummyInterface() private function serializerWithClassDiscriminator()
{ {
$factoryMock = $this->getMockBuilder(ClassMetadataFactoryInterface::class)->getMock(); $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$factoryMock->method('hasMetadataFor')->will($this->returnValueMap(array(
array(
DummyMessageInterface::class,
true,
),
)));
$factoryMock->method('getMetadataFor')->will($this->returnValueMap(array( return new Serializer(array(new ObjectNormalizer($classMetadataFactory, null, null, null, new ClassDiscriminatorFromClassMetadata($classMetadataFactory))), array('json' => new JsonEncoder()));
array(
DummyMessageInterface::class,
new ClassMetadata(
DummyMessageInterface::class,
new ClassDiscriminatorMapping('message-type', array(
'one' => DummyMessageNumberOne::class,
))
),
),
)));
return $factoryMock;
} }
} }

View File

@ -546,7 +546,8 @@ class CliDumper extends AbstractDumper
&& @sapi_windows_vt100_support($stream)) && @sapi_windows_vt100_support($stream))
|| false !== getenv('ANSICON') || false !== getenv('ANSICON')
|| 'ON' === getenv('ConEmuANSI') || 'ON' === getenv('ConEmuANSI')
|| 'xterm' === getenv('TERM'); || 'xterm' === getenv('TERM')
|| 'Hyper' === getenv('TERM_PROGRAM');
} }
if (function_exists('stream_isatty')) { if (function_exists('stream_isatty')) {
@ -575,7 +576,8 @@ class CliDumper extends AbstractDumper
{ {
$result = 183 <= getenv('ANSICON_VER') $result = 183 <= getenv('ANSICON_VER')
|| 'ON' === getenv('ConEmuANSI') || 'ON' === getenv('ConEmuANSI')
|| 'xterm' === getenv('TERM'); || 'xterm' === getenv('TERM')
|| 'Hyper' === getenv('TERM_PROGRAM');
if (!$result && PHP_VERSION_ID >= 70200) { if (!$result && PHP_VERSION_ID >= 70200) {
$version = sprintf( $version = sprintf(

View File

@ -30,10 +30,10 @@ class Event extends BaseEvent
private $workflowName; private $workflowName;
/** /**
* @param object $subject * @param object $subject
* @param Marking $marking * @param Marking $marking
* @param Transition $transition * @param Transition $transition
* @param Workflow $workflow * @param WorkflowInterface $workflow
*/ */
public function __construct($subject, Marking $marking, Transition $transition, $workflow = null) public function __construct($subject, Marking $marking, Transition $transition, $workflow = null)
{ {