Skip to content

Commit 6bf99c0

Browse files
committed
Merge branch '6.3' into 6.4
* 6.3: fix merge do not detect the deserialization_path context value twice fix detecting the database server version [Cache] Add url decoding of password in `RedisTrait` DSN [Serializer] Remove incompatible type declaration with PHP 7.2 [Serializer] Fix test Fix denormalizing empty string into object|null parameter [PropertyInfo] Fixed promoted property type detection for `PhpStanExtractor` [Serializer] Move discrimination to abstract [Serializer] Fix deserialization_path missing using contructor [Serializer] Fix access to private when Ignore [HttpKernel] Fix logging deprecations to the "php" channel when channel "deprecation" is not defined Fix message handlers with multiple from_transports fix detecting the server version with Doctrine DBAL 4 [Serializer] Fix constructor deserialization path [Serializer] Fix XML attributes not added on empty
2 parents b6404af + f6a425b commit 6bf99c0

30 files changed

+515
-79
lines changed

.github/workflows/integration-tests.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ jobs:
4848
image: redis:6.2.8
4949
ports:
5050
- 16379:6379
51+
redis-authenticated:
52+
image: redis:6.2.8
53+
ports:
54+
- 16380:6379
55+
env:
56+
REDIS_ARGS: "--requirepass p@ssword"
5157
redis-cluster:
5258
image: grokzen/redis-cluster:6.2.8
5359
ports:
@@ -170,6 +176,7 @@ jobs:
170176
run: ./phpunit --group integration -v
171177
env:
172178
REDIS_HOST: 'localhost:16379'
179+
REDIS_AUTHENTICATED_HOST: 'localhost:16380'
173180
REDIS_CLUSTER_HOSTS: 'localhost:7000 localhost:7001 localhost:7002 localhost:7003 localhost:7004 localhost:7005'
174181
REDIS_SENTINEL_HOSTS: 'unreachable-host:26379 localhost:26379 localhost:26379'
175182
REDIS_SENTINEL_SERVICE: redis_sentinel

src/Symfony/Bundle/FrameworkBundle/Resources/config/debug_prod.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
param('debug.error_handler.throw_at'),
2828
param('kernel.debug'),
2929
param('kernel.debug'),
30-
service('logger')->nullOnInvalid(),
30+
null, // Deprecation logger if different from the one above
3131
])
3232
->tag('monolog.logger', ['channel' => 'php'])
3333

src/Symfony/Component/Cache/Adapter/DoctrineDbalAdapter.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use Doctrine\DBAL\ParameterType;
2222
use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory;
2323
use Doctrine\DBAL\Schema\Schema;
24+
use Doctrine\DBAL\ServerVersionProvider;
2425
use Doctrine\DBAL\Tools\DsnParser;
2526
use Symfony\Component\Cache\Exception\InvalidArgumentException;
2627
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
@@ -385,13 +386,14 @@ private function getServerVersion(): string
385386
return $this->serverVersion;
386387
}
387388

389+
if ($this->conn instanceof ServerVersionProvider || $this->conn instanceof ServerInfoAwareConnection) {
390+
return $this->serverVersion = $this->conn->getServerVersion();
391+
}
392+
388393
// The condition should be removed once support for DBAL <3.3 is dropped
389394
$conn = method_exists($this->conn, 'getNativeConnection') ? $this->conn->getNativeConnection() : $this->conn->getWrappedConnection();
390-
if ($conn instanceof ServerInfoAwareConnection) {
391-
return $this->serverVersion = $conn->getServerVersion();
392-
}
393395

394-
return $this->serverVersion = '0';
396+
return $this->serverVersion = $conn->getAttribute(\PDO::ATTR_SERVER_VERSION);
395397
}
396398

397399
private function addTableToSchema(Schema $schema): void

src/Symfony/Component/Cache/Tests/Traits/RedisTraitTest.php

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,11 @@
1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\Cache\Traits\RedisTrait;
1616

17+
/**
18+
* @requires extension redis
19+
*/
1720
class RedisTraitTest extends TestCase
1821
{
19-
public static function setUpBeforeClass(): void
20-
{
21-
if (!getenv('REDIS_CLUSTER_HOSTS')) {
22-
self::markTestSkipped('REDIS_CLUSTER_HOSTS env var is not defined.');
23-
}
24-
}
25-
2622
/**
2723
* @dataProvider provideCreateConnection
2824
*/
@@ -41,6 +37,19 @@ public function testCreateConnection(string $dsn, string $expectedClass)
4137
self::assertInstanceOf($expectedClass, $connection);
4238
}
4339

40+
public function testUrlDecodeParameters()
41+
{
42+
if (!getenv('REDIS_AUTHENTICATED_HOST')) {
43+
self::markTestSkipped('REDIS_AUTHENTICATED_HOST env var is not defined.');
44+
}
45+
46+
$mock = self::getObjectForTrait(RedisTrait::class);
47+
$connection = $mock::createConnection('redis://:p%40ssword@'.getenv('REDIS_AUTHENTICATED_HOST'));
48+
49+
self::assertInstanceOf(\Redis::class, $connection);
50+
self::assertSame('p@ssword', $connection->getAuth());
51+
}
52+
4453
public static function provideCreateConnection(): array
4554
{
4655
$hosts = array_map(fn ($host) => sprintf('host[%s]', $host), explode(' ', getenv('REDIS_CLUSTER_HOSTS')));

src/Symfony/Component/Cache/Traits/RedisTrait.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,9 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra
101101
$params = preg_replace_callback('#^'.$scheme.':(//)?(?:(?:(?<user>[^:@]*+):)?(?<password>[^@]*+)@)?#', function ($m) use (&$auth) {
102102
if (isset($m['password'])) {
103103
if (\in_array($m['user'], ['', 'default'], true)) {
104-
$auth = $m['password'];
104+
$auth = rawurldecode($m['password']);
105105
} else {
106-
$auth = [$m['user'], $m['password']];
106+
$auth = [rawurldecode($m['user']), rawurldecode($m['password'])];
107107
}
108108

109109
if ('' === $auth) {

src/Symfony/Component/Messenger/Bridge/Redis/Transport/Connection.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,10 +294,10 @@ private static function parseDsn(string $dsn, array &$options): array
294294
$url = preg_replace_callback('#^'.$scheme.':(//)?(?:(?:(?<user>[^:@]*+):)?(?<password>[^@]*+)@)?#', function ($m) use (&$auth) {
295295
if (isset($m['password'])) {
296296
if (!\in_array($m['user'], ['', 'default'], true)) {
297-
$auth['user'] = $m['user'];
297+
$auth['user'] = rawurldecode($m['user']);
298298
}
299299

300-
$auth['pass'] = $m['password'];
300+
$auth['pass'] = rawurldecode($m['password']);
301301
}
302302

303303
return 'file:'.($m[1] ?? '');

src/Symfony/Component/Messenger/DependencyInjection/MessengerPass.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ private function registerHandlers(ContainerBuilder $container, array $busIds): v
106106
unset($options['handles']);
107107
$priority = $options['priority'] ?? 0;
108108
$method = $options['method'] ?? '__invoke';
109+
$fromTransport = $options['from_transport'] ?? '';
109110

110111
if (isset($options['bus'])) {
111112
if (!\in_array($options['bus'], $busIds)) {
@@ -131,10 +132,10 @@ private function registerHandlers(ContainerBuilder $container, array $busIds): v
131132
throw new RuntimeException(sprintf('Invalid handler service "%s": method "%s::%s()" does not exist.', $serviceId, $r->getName(), $method));
132133
}
133134

134-
if ('__invoke' !== $method) {
135+
if ('__invoke' !== $method || '' !== $fromTransport) {
135136
$wrapperDefinition = (new Definition('Closure'))->addArgument([new Reference($serviceId), $method])->setFactory('Closure::fromCallable');
136137

137-
$definitions[$definitionId = '.messenger.method_on_object_wrapper.'.ContainerBuilder::hash($message.':'.$priority.':'.$serviceId.':'.$method)] = $wrapperDefinition;
138+
$definitions[$definitionId = '.messenger.method_on_object_wrapper.'.ContainerBuilder::hash($message.':'.$priority.':'.$serviceId.':'.$method.':'.$fromTransport)] = $wrapperDefinition;
138139
} else {
139140
$definitionId = $serviceId;
140141
}

src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
use Symfony\Component\Messenger\Tests\Fixtures\SecondMessage;
5353
use Symfony\Component\Messenger\Tests\Fixtures\TaggedDummyHandler;
5454
use Symfony\Component\Messenger\Tests\Fixtures\TaggedDummyHandlerWithUnionTypes;
55+
use Symfony\Component\Messenger\Tests\Fixtures\ThirdMessage;
5556
use Symfony\Component\Messenger\Tests\Fixtures\UnionBuiltinTypeArgumentHandler;
5657
use Symfony\Component\Messenger\Tests\Fixtures\UnionTypeArgumentHandler;
5758
use Symfony\Component\Messenger\Tests\Fixtures\UnionTypeOneMessage;
@@ -102,7 +103,7 @@ public function testFromTransportViaTagAttribute()
102103
$container = $this->getContainerBuilder($busId = 'message_bus');
103104
$container
104105
->register(DummyHandler::class, DummyHandler::class)
105-
->addTag('messenger.message_handler', ['from_transport' => 'async'])
106+
->addTag('messenger.message_handler', ['from_transport' => 'async', 'method' => '__invoke'])
106107
;
107108

108109
(new MessengerPass())->process($container);
@@ -113,7 +114,7 @@ public function testFromTransportViaTagAttribute()
113114
$handlerDescriptionMapping = $handlersLocatorDefinition->getArgument(0);
114115
$this->assertCount(1, $handlerDescriptionMapping);
115116

116-
$this->assertHandlerDescriptor($container, $handlerDescriptionMapping, DummyMessage::class, [DummyHandler::class], [['from_transport' => 'async']]);
117+
$this->assertHandlerDescriptor($container, $handlerDescriptionMapping, DummyMessage::class, [[DummyHandler::class, '__invoke']], [['from_transport' => 'async']]);
117118
}
118119

119120
public function testHandledMessageTypeResolvedWithMethodAndNoHandlesViaTagAttributes()
@@ -178,7 +179,7 @@ public function testTaggedMessageHandler()
178179
$this->assertSame(HandlersLocator::class, $handlersLocatorDefinition->getClass());
179180

180181
$handlerDescriptionMapping = $handlersLocatorDefinition->getArgument(0);
181-
$this->assertCount(2, $handlerDescriptionMapping);
182+
$this->assertCount(3, $handlerDescriptionMapping);
182183

183184
$this->assertHandlerDescriptor($container, $handlerDescriptionMapping, DummyMessage::class, [TaggedDummyHandler::class], [[]]);
184185
$this->assertHandlerDescriptor(
@@ -187,6 +188,19 @@ public function testTaggedMessageHandler()
187188
SecondMessage::class,
188189
[[TaggedDummyHandler::class, 'handleSecondMessage']]
189190
);
191+
$this->assertHandlerDescriptor(
192+
$container,
193+
$handlerDescriptionMapping,
194+
ThirdMessage::class,
195+
[
196+
[TaggedDummyHandler::class, 'handleThirdMessage'],
197+
[TaggedDummyHandler::class, 'handleThirdMessage'],
198+
],
199+
[
200+
['from_transport' => 'a'],
201+
['from_transport' => 'b'],
202+
],
203+
);
190204
}
191205

192206
public function testTaggedMessageHandlerWithUnionTypes()

src/Symfony/Component/Messenger/Tests/Fixtures/TaggedDummyHandler.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,10 @@ public function __invoke(DummyMessage $message)
1515
public function handleSecondMessage(SecondMessage $message)
1616
{
1717
}
18+
19+
#[AsMessageHandler(fromTransport: 'a')]
20+
#[AsMessageHandler(fromTransport: 'b')]
21+
public function handleThirdMessage(ThirdMessage $message): void
22+
{
23+
}
1824
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Symfony\Component\Messenger\Tests\Fixtures;
4+
5+
class ThirdMessage
6+
{
7+
}

src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -178,9 +178,7 @@ private function getDocBlockFromConstructor(string $class, string $property): ?P
178178
return null;
179179
}
180180

181-
$tokens = new TokenIterator($this->lexer->tokenize($rawDocNode));
182-
$phpDocNode = $this->phpDocParser->parse($tokens);
183-
$tokens->consumeTokenType(Lexer::TOKEN_END);
181+
$phpDocNode = $this->getPhpDocNode($rawDocNode);
184182

185183
return $this->filterDocBlockParams($phpDocNode, $property);
186184
}
@@ -234,24 +232,27 @@ private function getDocBlockFromProperty(string $class, string $property): ?arra
234232
return null;
235233
}
236234

235+
// Type can be inside property docblock as `@var`
236+
$rawDocNode = $reflectionProperty->getDocComment();
237+
$phpDocNode = $rawDocNode ? $this->getPhpDocNode($rawDocNode) : null;
237238
$source = self::PROPERTY;
238239

239-
if ($reflectionProperty->isPromoted()) {
240+
if (!$phpDocNode?->getTagsByName('@var')) {
241+
$phpDocNode = null;
242+
}
243+
244+
// or in the constructor as `@param` for promoted properties
245+
if (!$phpDocNode && $reflectionProperty->isPromoted()) {
240246
$constructor = new \ReflectionMethod($class, '__construct');
241247
$rawDocNode = $constructor->getDocComment();
248+
$phpDocNode = $rawDocNode ? $this->getPhpDocNode($rawDocNode) : null;
242249
$source = self::MUTATOR;
243-
} else {
244-
$rawDocNode = $reflectionProperty->getDocComment();
245250
}
246251

247-
if (!$rawDocNode) {
252+
if (!$phpDocNode) {
248253
return null;
249254
}
250255

251-
$tokens = new TokenIterator($this->lexer->tokenize($rawDocNode));
252-
$phpDocNode = $this->phpDocParser->parse($tokens);
253-
$tokens->consumeTokenType(Lexer::TOKEN_END);
254-
255256
return [$phpDocNode, $source, $reflectionProperty->class];
256257
}
257258

@@ -291,10 +292,17 @@ private function getDocBlockFromMethod(string $class, string $ucFirstProperty, i
291292
return null;
292293
}
293294

295+
$phpDocNode = $this->getPhpDocNode($rawDocNode);
296+
297+
return [$phpDocNode, $prefix, $reflectionMethod->class];
298+
}
299+
300+
private function getPhpDocNode(string $rawDocNode): PhpDocNode
301+
{
294302
$tokens = new TokenIterator($this->lexer->tokenize($rawDocNode));
295303
$phpDocNode = $this->phpDocParser->parse($tokens);
296304
$tokens->consumeTokenType(Lexer::TOKEN_END);
297305

298-
return [$phpDocNode, $prefix, $reflectionMethod->class];
306+
return $phpDocNode;
299307
}
300308
}

src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,8 @@ public function testExtractPhp80Type(string $class, $property, array $type = nul
467467
public static function php80TypesProvider()
468468
{
469469
return [
470+
[Php80Dummy::class, 'promotedWithDocCommentAndType', [new Type(Type::BUILTIN_TYPE_INT)]],
471+
[Php80Dummy::class, 'promotedWithDocComment', [new Type(Type::BUILTIN_TYPE_STRING)]],
470472
[Php80Dummy::class, 'promotedAndMutated', [new Type(Type::BUILTIN_TYPE_STRING)]],
471473
[Php80Dummy::class, 'promoted', null],
472474
[Php80Dummy::class, 'collection', [new Type(Type::BUILTIN_TYPE_ARRAY, collection: true, collectionValueType: new Type(Type::BUILTIN_TYPE_STRING))]],

src/Symfony/Component/PropertyInfo/Tests/Fixtures/Php80Dummy.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,23 @@ class Php80Dummy
1717

1818
/**
1919
* @param string $promotedAndMutated
20+
* @param string $promotedWithDocComment
21+
* @param string $promotedWithDocCommentAndType
2022
* @param array<string> $collection
2123
*/
22-
public function __construct(private mixed $promoted, private mixed $promotedAndMutated, private array $collection)
24+
public function __construct(
25+
private mixed $promoted,
26+
private mixed $promotedAndMutated,
27+
/**
28+
* Comment without @var.
29+
*/
30+
private mixed $promotedWithDocComment,
31+
/**
32+
* @var int
33+
*/
34+
private mixed $promotedWithDocCommentAndType,
35+
private array $collection,
36+
)
2337
{
2438
}
2539

src/Symfony/Component/Serializer/Encoder/XmlEncoder.php

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -137,26 +137,22 @@ public function decode(string $data, string $format, array $context = []): mixed
137137
// todo: throw an exception if the root node name is not correctly configured (bc)
138138

139139
if ($rootNode->hasChildNodes()) {
140-
$xpath = new \DOMXPath($dom);
141-
$data = [];
142-
foreach ($xpath->query('namespace::*', $dom->documentElement) as $nsNode) {
143-
$data['@'.$nsNode->nodeName] = $nsNode->nodeValue;
140+
$data = $this->parseXml($rootNode, $context);
141+
if (\is_array($data)) {
142+
$data = $this->addXmlNamespaces($data, $rootNode, $dom);
144143
}
145144

146-
unset($data['@xmlns:xml']);
147-
148-
if (empty($data)) {
149-
return $this->parseXml($rootNode, $context);
150-
}
151-
152-
return array_merge($data, (array) $this->parseXml($rootNode, $context));
145+
return $data;
153146
}
154147

155148
if (!$rootNode->hasAttributes()) {
156149
return $rootNode->nodeValue;
157150
}
158151

159-
return array_merge($this->parseXmlAttributes($rootNode, $context), ['#' => $rootNode->nodeValue]);
152+
$data = array_merge($this->parseXmlAttributes($rootNode, $context), ['#' => $rootNode->nodeValue]);
153+
$data = $this->addXmlNamespaces($data, $rootNode, $dom);
154+
155+
return $data;
160156
}
161157

162158
public function supportsEncoding(string $format): bool
@@ -328,6 +324,19 @@ private function parseXmlValue(\DOMNode $node, array $context = []): array|strin
328324
return $value;
329325
}
330326

327+
private function addXmlNamespaces(array $data, \DOMNode $node, \DOMDocument $document): array
328+
{
329+
$xpath = new \DOMXPath($document);
330+
331+
foreach ($xpath->query('namespace::*', $node) as $nsNode) {
332+
$data['@'.$nsNode->nodeName] = $nsNode->nodeValue;
333+
}
334+
335+
unset($data['@xmlns:xml']);
336+
337+
return $data;
338+
}
339+
331340
/**
332341
* Parse the data and convert it to DOMElements.
333342
*

src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex
342342
$missingConstructorArguments = [];
343343
$params = [];
344344
$unsetKeys = [];
345+
345346
foreach ($constructorParameters as $constructorParameter) {
346347
$paramName = $constructorParameter->name;
347348
$attributeContext = $this->getAttributeDenormalizationContext($class, $paramName, $context);

0 commit comments

Comments
 (0)