Skip to content

Commit 144351d

Browse files
committed
Merge branch '6.4' into 7.0
* 6.4: fix merge fix syntax for PHP 7.2 [Security] Fix Danish translations [Messenger] Improve deadlock handling on `ack()` and `reject()` [DomCrawler] Encode html entities only if nessecary [Serializer] reset backed_enum priority, and re-prioritise translatable [Validator] Accept `Stringable` in `ExecutionContext::build/addViolation()` [Serializer] Ignore when using #[Ignore] on a non-accessor [Filesystem] Strengthen the check of file permissions in `dumpFile` [Serializer] Fix XML scalar to object denormalization [HttpClient][EventSourceHttpClient] Fix consuming SSEs with \r\n separator
2 parents 524c703 + b293ffe commit 144351d

File tree

19 files changed

+442
-46
lines changed

19 files changed

+442
-46
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@
115115

116116
->set('serializer.normalizer.translatable', TranslatableNormalizer::class)
117117
->args(['$translator' => service('translator')])
118-
->tag('serializer.normalizer', ['priority' => -890])
118+
->tag('serializer.normalizer', ['priority' => -920])
119119

120120
->set('serializer.normalizer.form_error', FormErrorNormalizer::class)
121121
->tag('serializer.normalizer', ['priority' => -915])
@@ -212,6 +212,6 @@
212212
])
213213

214214
->set('serializer.normalizer.backed_enum', BackedEnumNormalizer::class)
215-
->tag('serializer.normalizer', ['priority' => -880])
215+
->tag('serializer.normalizer', ['priority' => -915])
216216
;
217217
};

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
6767
use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
6868
use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;
69+
use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer;
6970
use Symfony\Component\Serializer\Normalizer\ConstraintViolationListNormalizer;
7071
use Symfony\Component\Serializer\Normalizer\DataUriNormalizer;
7172
use Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer;
@@ -1539,10 +1540,24 @@ public function testTranslatableNormalizerRegistered()
15391540
$tag = $definition->getTag('serializer.normalizer');
15401541

15411542
$this->assertSame(TranslatableNormalizer::class, $definition->getClass());
1542-
$this->assertSame(-890, $tag[0]['priority']);
1543+
$this->assertSame(-920, $tag[0]['priority']);
15431544
$this->assertEquals(new Reference('translator'), $definition->getArgument('$translator'));
15441545
}
15451546

1547+
/**
1548+
* @see https://github.com/symfony/symfony/issues/54478
1549+
*/
1550+
public function testBackedEnumNormalizerRegistered()
1551+
{
1552+
$container = $this->createContainerFromFile('full');
1553+
1554+
$definition = $container->getDefinition('serializer.normalizer.backed_enum');
1555+
$tag = $definition->getTag('serializer.normalizer');
1556+
1557+
$this->assertSame(BackedEnumNormalizer::class, $definition->getClass());
1558+
$this->assertSame(-915, $tag[0]['priority']);
1559+
}
1560+
15461561
public function testSerializerCacheActivated()
15471562
{
15481563
$container = $this->createContainerFromFile('serializer_enabled');

src/Symfony/Component/DomCrawler/Crawler.php

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,12 +1061,30 @@ protected function sibling(\DOMNode $node, string $siblingDir = 'nextSibling'):
10611061

10621062
private function parseHtml5(string $htmlContent, string $charset = 'UTF-8'): \DOMDocument
10631063
{
1064-
return $this->html5Parser->parse($this->convertToHtmlEntities($htmlContent, $charset));
1064+
if (!$this->supportsEncoding($charset)) {
1065+
$htmlContent = $this->convertToHtmlEntities($htmlContent, $charset);
1066+
$charset = 'UTF-8';
1067+
}
1068+
1069+
return $this->html5Parser->parse($htmlContent, ['encoding' => $charset]);
1070+
}
1071+
1072+
private function supportsEncoding(string $encoding): bool
1073+
{
1074+
try {
1075+
return '' === @mb_convert_encoding('', $encoding, 'UTF-8');
1076+
} catch (\Throwable $e) {
1077+
return false;
1078+
}
10651079
}
10661080

10671081
private function parseXhtml(string $htmlContent, string $charset = 'UTF-8'): \DOMDocument
10681082
{
1069-
$htmlContent = $this->convertToHtmlEntities($htmlContent, $charset);
1083+
if ('UTF-8' === $charset && preg_match('//u', $htmlContent)) {
1084+
$htmlContent = '<?xml encoding="UTF-8">'.$htmlContent;
1085+
} else {
1086+
$htmlContent = $this->convertToHtmlEntities($htmlContent, $charset);
1087+
}
10701088

10711089
$internalErrors = libxml_use_internal_errors(true);
10721090

src/Symfony/Component/DomCrawler/Tests/AbstractCrawlerTestCase.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,10 @@ public function testAddContent()
184184
$crawler = $this->createCrawler();
185185
$crawler->addContent($this->getDoctype().'<html><meta http-equiv="Content-Type" content="text/html; charset=unicode" /><div class="foo"></html></html>');
186186
$this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() ignores bad charset');
187+
188+
$crawler = $this->createCrawler();
189+
$crawler->addContent($this->getDoctype().'<html><script>var foo = "bär";</script></html>', 'text/html; charset=UTF-8');
190+
$this->assertEquals('var foo = "bär";', $crawler->filterXPath('//script')->text(), '->addContent() does not interfere with script content');
187191
}
188192

189193
/**

src/Symfony/Component/Filesystem/Filesystem.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -662,7 +662,7 @@ public function dumpFile(string $filename, $content): void
662662
throw new IOException(sprintf('Failed to write file "%s": ', $filename).self::$lastError, 0, null, $filename);
663663
}
664664

665-
self::box('chmod', $tmpFile, file_exists($filename) ? fileperms($filename) : 0666 & ~umask());
665+
self::box('chmod', $tmpFile, @fileperms($filename) ?: 0666 & ~umask());
666666

667667
$this->rename($tmpFile, $filename, true);
668668
} finally {

src/Symfony/Component/HttpClient/EventSourceHttpClient.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ public function request(string $method, string $url, array $options = []): Respo
121121
return;
122122
}
123123

124-
$rx = '/((?:\r\n|[\r\n]){2,})/';
124+
$rx = '/((?:\r\n){2,}|\r{2,}|\n{2,})/';
125125
$content = $state->buffer.$chunk->getContent();
126126

127127
if ($chunk->isLast()) {

src/Symfony/Component/HttpClient/Tests/EventSourceHttpClientTest.php

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,14 @@
2727
*/
2828
class EventSourceHttpClientTest extends TestCase
2929
{
30-
public function testGetServerSentEvents()
30+
/**
31+
* @testWith ["\n"]
32+
* ["\r"]
33+
* ["\r\n"]
34+
*/
35+
public function testGetServerSentEvents(string $sep)
3136
{
32-
$data = <<<TXT
37+
$rawData = <<<TXT
3338
event: builderror
3439
id: 46
3540
data: {"foo": "bar"}
@@ -58,6 +63,7 @@ public function testGetServerSentEvents()
5863
id: 60
5964
data
6065
TXT;
66+
$data = str_replace("\n", $sep, $rawData);
6167

6268
$chunk = new DataChunk(0, $data);
6369
$response = new MockResponse('', ['canceled' => false, 'http_method' => 'GET', 'url' => 'http://localhost:8080/events', 'response_headers' => ['content-type: text/event-stream']]);
@@ -83,11 +89,11 @@ public function testGetServerSentEvents()
8389

8490
$expected = [
8591
new FirstChunk(),
86-
new ServerSentEvent("event: builderror\nid: 46\ndata: {\"foo\": \"bar\"}\n\n"),
87-
new ServerSentEvent("event: reload\nid: 47\ndata: {}\n\n"),
88-
new ServerSentEvent("event: reload\nid: 48\ndata: {}\n\n"),
89-
new ServerSentEvent("data: test\ndata:test\nid: 49\nevent: testEvent\n\n\n"),
90-
new ServerSentEvent("id: 50\ndata: <tag>\ndata\ndata: <foo />\ndata\ndata: </tag>\n\n"),
92+
new ServerSentEvent(str_replace("\n", $sep, "event: builderror\nid: 46\ndata: {\"foo\": \"bar\"}\n\n")),
93+
new ServerSentEvent(str_replace("\n", $sep, "event: reload\nid: 47\ndata: {}\n\n")),
94+
new ServerSentEvent(str_replace("\n", $sep, "event: reload\nid: 48\ndata: {}\n\n")),
95+
new ServerSentEvent(str_replace("\n", $sep, "data: test\ndata:test\nid: 49\nevent: testEvent\n\n\n")),
96+
new ServerSentEvent(str_replace("\n", $sep, "id: 50\ndata: <tag>\ndata\ndata: <foo />\ndata\ndata: </tag>\n\n")),
9197
];
9298
$i = 0;
9399

src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Doctrine\DBAL\Platforms\OraclePlatform;
2121
use Doctrine\DBAL\Platforms\SQLServer2012Platform;
2222
use Doctrine\DBAL\Platforms\SQLServerPlatform;
23+
use Doctrine\DBAL\Query\ForUpdate\ConflictResolutionMode;
2324
use Doctrine\DBAL\Query\QueryBuilder;
2425
use Doctrine\DBAL\Result;
2526
use Doctrine\DBAL\Schema\AbstractSchemaManager;
@@ -99,6 +100,82 @@ public function testGetWithNoPendingMessageWillReturnNull()
99100
$this->assertNull($doctrineEnvelope);
100101
}
101102

103+
public function testGetWithSkipLockedWithForUpdateMethod()
104+
{
105+
if (!method_exists(QueryBuilder::class, 'forUpdate')) {
106+
$this->markTestSkipped('This test is for when forUpdate method exists.');
107+
}
108+
109+
$queryBuilder = $this->getQueryBuilderMock();
110+
$driverConnection = $this->getDBALConnectionMock();
111+
$stmt = $this->getResultMock(false);
112+
113+
$queryBuilder
114+
->method('getParameters')
115+
->willReturn([]);
116+
$queryBuilder
117+
->method('getParameterTypes')
118+
->willReturn([]);
119+
$queryBuilder
120+
->method('forUpdate')
121+
->with(ConflictResolutionMode::SKIP_LOCKED)
122+
->willReturn($queryBuilder);
123+
$queryBuilder
124+
->method('getSQL')
125+
->willReturn('SELECT FOR UPDATE SKIP LOCKED');
126+
$driverConnection->expects($this->once())
127+
->method('createQueryBuilder')
128+
->willReturn($queryBuilder);
129+
$driverConnection->expects($this->never())
130+
->method('update');
131+
$driverConnection
132+
->method('executeQuery')
133+
->with($this->callback(function ($sql) {
134+
return str_contains($sql, 'SKIP LOCKED');
135+
}))
136+
->willReturn($stmt);
137+
138+
$connection = new Connection(['skip_locked' => true], $driverConnection);
139+
$doctrineEnvelope = $connection->get();
140+
$this->assertNull($doctrineEnvelope);
141+
}
142+
143+
public function testGetWithSkipLockedWithoutForUpdateMethod()
144+
{
145+
if (method_exists(QueryBuilder::class, 'forUpdate')) {
146+
$this->markTestSkipped('This test is for when forUpdate method does not exist.');
147+
}
148+
149+
$queryBuilder = $this->getQueryBuilderMock();
150+
$driverConnection = $this->getDBALConnectionMock();
151+
$stmt = $this->getResultMock(false);
152+
153+
$queryBuilder
154+
->method('getParameters')
155+
->willReturn([]);
156+
$queryBuilder
157+
->method('getParameterTypes')
158+
->willReturn([]);
159+
$queryBuilder
160+
->method('getSQL')
161+
->willReturn('SELECT');
162+
$driverConnection->expects($this->once())
163+
->method('createQueryBuilder')
164+
->willReturn($queryBuilder);
165+
$driverConnection->expects($this->never())
166+
->method('update');
167+
$driverConnection
168+
->method('executeQuery')
169+
->with($this->callback(function ($sql) {
170+
return str_contains($sql, 'SKIP LOCKED');
171+
}))
172+
->willReturn($stmt);
173+
174+
$connection = new Connection(['skip_locked' => true], $driverConnection);
175+
$doctrineEnvelope = $connection->get();
176+
$this->assertNull($doctrineEnvelope);
177+
}
178+
102179
public function testItThrowsATransportExceptionIfItCannotAcknowledgeMessage()
103180
{
104181
$this->expectException(TransportException::class);
@@ -496,20 +573,20 @@ class_exists(MySQLPlatform::class) ? new MySQLPlatform() : new MySQL57Platform()
496573

497574
yield 'SQL Server' => [
498575
class_exists(SQLServerPlatform::class) && !class_exists(SQLServer2012Platform::class) ? new SQLServerPlatform() : new SQLServer2012Platform(),
499-
'SELECT m.* FROM messenger_messages m WITH (UPDLOCK, ROWLOCK) WHERE (m.queue_name = ?) AND (m.delivered_at is null OR m.delivered_at < ?) AND (m.available_at <= ?) ORDER BY available_at ASC OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY ',
576+
'SELECT m.* FROM messenger_messages m WITH (UPDLOCK, ROWLOCK, READPAST) WHERE (m.queue_name = ?) AND (m.delivered_at is null OR m.delivered_at < ?) AND (m.available_at <= ?) ORDER BY available_at ASC OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY ',
500577
];
501578

502579
if (!class_exists(MySQL57Platform::class)) {
503580
// DBAL >= 4
504581
yield 'Oracle' => [
505582
new OraclePlatform(),
506-
'SELECT w.id AS "id", w.body AS "body", w.headers AS "headers", w.queue_name AS "queue_name", w.created_at AS "created_at", w.available_at AS "available_at", w.delivered_at AS "delivered_at" FROM messenger_messages w WHERE w.id IN (SELECT m.id FROM messenger_messages m WHERE (m.queue_name = ?) AND (m.delivered_at is null OR m.delivered_at < ?) AND (m.available_at <= ?) ORDER BY available_at ASC FETCH NEXT 1 ROWS ONLY) FOR UPDATE',
583+
'SELECT w.id AS "id", w.body AS "body", w.headers AS "headers", w.queue_name AS "queue_name", w.created_at AS "created_at", w.available_at AS "available_at", w.delivered_at AS "delivered_at" FROM messenger_messages w WHERE w.id IN (SELECT m.id FROM messenger_messages m WHERE (m.queue_name = ?) AND (m.delivered_at is null OR m.delivered_at < ?) AND (m.available_at <= ?) ORDER BY available_at ASC FETCH NEXT 1 ROWS ONLY) FOR UPDATE SKIP LOCKED',
507584
];
508585
} else {
509586
// DBAL < 4
510587
yield 'Oracle' => [
511588
new OraclePlatform(),
512-
'SELECT w.id AS "id", w.body AS "body", w.headers AS "headers", w.queue_name AS "queue_name", w.created_at AS "created_at", w.available_at AS "available_at", w.delivered_at AS "delivered_at" FROM messenger_messages w WHERE w.id IN (SELECT a.id FROM (SELECT m.id FROM messenger_messages m WHERE (m.queue_name = ?) AND (m.delivered_at is null OR m.delivered_at < ?) AND (m.available_at <= ?) ORDER BY available_at ASC) a WHERE ROWNUM <= 1) FOR UPDATE',
589+
'SELECT w.id AS "id", w.body AS "body", w.headers AS "headers", w.queue_name AS "queue_name", w.created_at AS "created_at", w.available_at AS "available_at", w.delivered_at AS "delivered_at" FROM messenger_messages w WHERE w.id IN (SELECT a.id FROM (SELECT m.id FROM messenger_messages m WHERE (m.queue_name = ?) AND (m.delivered_at is null OR m.delivered_at < ?) AND (m.available_at <= ?) ORDER BY available_at ASC) a WHERE ROWNUM <= 1) FOR UPDATE SKIP LOCKED',
513590
];
514591
}
515592
}

src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrinePostgreSqlIntegrationTest.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,17 @@ public function testPostgreSqlConnectionSendAndGet()
6464

6565
$this->assertNull($this->connection->get());
6666
}
67+
68+
public function testSkipLocked()
69+
{
70+
$connection = new PostgreSqlConnection(['table_name' => 'queue_table', 'skip_locked' => true], $this->driverConnection);
71+
72+
$connection->send('{"message": "Hi"}', ['type' => DummyMessage::class]);
73+
74+
$encoded = $connection->get();
75+
$this->assertEquals('{"message": "Hi"}', $encoded['body']);
76+
$this->assertEquals(['type' => DummyMessage::class], $encoded['headers']);
77+
78+
$this->assertNull($connection->get());
79+
}
6780
}

src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/DoctrinePostgreSqlRegularIntegrationTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,20 @@ public function testSendAndGetWithAutoSetupEnabledAndSetupAlready()
5757
$this->assertNull($this->connection->get());
5858
}
5959

60+
public function testSendAndGetWithSkipLockedEnabled()
61+
{
62+
$connection = new Connection(['table_name' => 'queue_table', 'skip_locked' => true], $this->driverConnection);
63+
$connection->setup();
64+
65+
$connection->send('{"message": "Hi"}', ['type' => DummyMessage::class]);
66+
67+
$encoded = $connection->get();
68+
$this->assertSame('{"message": "Hi"}', $encoded['body']);
69+
$this->assertSame(['type' => DummyMessage::class], $encoded['headers']);
70+
71+
$this->assertNull($this->connection->get());
72+
}
73+
6074
protected function setUp(): void
6175
{
6276
if (!$host = getenv('POSTGRES_HOST')) {

0 commit comments

Comments
 (0)