8000 Merge branch '7.2' into 7.3 · symfony/symfony@ba6b2ff · GitHub
[go: up one dir, main page]

Skip to content

Commit ba6b2ff

Browse files
Merge branch '7.2' into 7.3
* 7.2: - [Console] Table counts wrong column width when using colspan and `setColumnMaxWidth()` [Console] Table counts wrong number of padding symbols in `renderCell()` method when cell contain unicode variant selector [Cache] Fix using a `ChainAdapter` as an adapter for a pool [Serializer] Fix collect_denormalization_errors flag in defaultContext [TypeInfo] Fix handling `ConstFetchNode` [VarDumper] Avoid deprecated call in PgSqlCaster Fix command option mode (InputOption::VALUE_REQUIRED) use an EOL-agnostic approach to parse class uses [Uid] Improve entropy of the increment for UUIDv7 [HttpKernel] Fix `#[MapUploadedFile]` handling for optional file uploads
2 parents d56ecc9 + d527640 commit ba6b2ff

File tree

30 files changed

+380
-52
lines changed

30 files changed

+380
-52
lines changed

src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ protected function configure(): void
6969
->setDefinition([
7070
new InputArgument('locale', InputArgument::REQUIRED, 'The locale'),
7171
new InputArgument('bundle', InputArgument::OPTIONAL, 'The bundle name or directory where to load the messages'),
72-
new InputOption('domain', null, InputOption::VALUE_OPTIONAL, 'The messages domain'),
72+
new InputOption('domain', null, InputOption::VALUE_REQUIRED, 'The messages domain'),
7373
new InputOption('only-missing', null, InputOption::VALUE_NONE, 'Display only missing messages'),
7474
new InputOption('only-unused', null, InputOption::VALUE_NONE, 'Display only unused messages'),
7575
new InputOption('all', null, InputOption::VALUE_NONE, 'Load messages from all registered bundles'),

src/Symfony/Bundle/FrameworkBundle/Command/TranslationExtractCommand.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,15 @@ protected function configure(): void
7272
->setDefinition([
7373
new InputArgument('locale', InputArgument::REQUIRED, 'The locale'),
7474
new InputArgument('bundle', InputArgument::OPTIONAL, 'The bundle name or directory where to load the messages'),
75-
new InputOption('prefix', null, InputOption::VALUE_OPTIONAL, 'Override the default prefix', '__'),
75+
new InputOption('prefix', null, InputOption::VALUE_REQUIRED, 'Override the default prefix', '__'),
7676
new InputOption('no-fill', null, InputOption::VALUE_NONE, 'Extract translation keys without filling in values'),
77-
new InputOption('format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format', 'xlf12'),
77+
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'Override the default output format', 'xlf12'),
7878
new InputOption('dump-messages', null, InputOption::VALUE_NONE, 'Should the messages be dumped in the console'),
7979
new InputOption('force', null, InputOption::VALUE_NONE, 'Should the extract be done'),
8080
new InputOption('clean', null, InputOption::VALUE_NONE, 'Should clean not found messages'),
81-
new InputOption('domain', null, InputOption::VALUE_OPTIONAL, 'Specify the domain to extract'),
82-
new InputOption('sort', null, InputOption::VALUE_OPTIONAL, 'Return list of messages sorted alphabetically'),
83-
new InputOption('as-tree', null, InputOption::VALUE_OPTIONAL, 'Dump the messages as a tree-like structure: The given value defines the level where to switch to inline YAML'),
81+
new InputOption('domain', null, InputOption::VALUE_REQUIRED, 'Specify the domain to extract'),
82+
new InputOption('sort', null, InputOption::VALUE_REQUIRED, 'Return list of messages sorted alphabetically'),
83+
new InputOption('as-tree', null, InputOption::VALUE_REQUIRED, 'Dump the messages as a tree-like structure: The given value defines the level where to switch to inline YAML'),
8484
])
8585
->setHelp(<<<'EOF'
8686
The <info>%command.name%</info> command extracts translation strings from templates

src/Symfony/Component/Cache/DependencyInjection/CachePoolPass.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,11 @@ public function process(ContainerBuilder $container): void
5555
continue;
5656
}
5757
$class = $adapter->getClass();
58+
$providers = $adapter->getArguments();
5859
while ($adapter instanceof ChildDefinition) {
5960
$adapter = $container->findDefinition($adapter->getParent());
6061
$class = $class ?: $adapter->getClass();
62+
$providers += $adapter->getArguments();
6163
if ($t = $adapter->getTag('cache.pool')) {
6264
$tags[0] += $t[0];
6365
}
@@ -87,7 +89,7 @@ public function process(ContainerBuilder $container): void
8789

8890
if (ChainAdapter::class === $class) {
8991
$adapters = [];
90-
foreach ($adapter->getArgument(0) as $provider => $adapter) {
92+
foreach ($providers['index_0'] ?? $providers[0] as $provider => $adapter) {
9193
if ($adapter instanceof ChildDefinition) {
9294
$chainedPool = $adapter;
9395
} else {

src/Symfony/Component/Cache/Tests/DependencyInjection/CachePoolPassTest.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,8 @@ public function testChainAdapterPool()
209209
$container->register('cache.adapter.apcu', ApcuAdapter::class)
210210
->setArguments([null, 0, null])
211211
->addTag('cache.pool');
212-
$container->register('cache.chain', ChainAdapter::class)
212+
$container->register('cache.adapter.chain', ChainAdapter::class);
213+
$container->setDefinition('cache.chain', new ChildDefinition('cache.adapter.chain'))
213214
->addArgument(['cache.adapter.array', 'cache.adapter.apcu'])
214215
->addTag('cache.pool');
215216
$container->setDefinition('cache.app', new ChildDefinition('cache.chain'))
@@ -224,7 +225,7 @@ public function testChainAdapterPool()
224225
$this->assertSame('cache.chain', $appCachePool->getParent());
225226

226227
$chainCachePool = $container->getDefinition('cache.chain');
227-
$this->assertNotInstanceOf(ChildDefinition::class, $chainCachePool);
228+
$this->assertInstanceOf(ChildDefinition::class, $chainCachePool);
228229
$this->assertCount(2, $chainCachePool->getArgument(0));
229230
$this->assertInstanceOf(ChildDefinition::class, $chainCachePool->getArgument(0)[0]);
230231
$this->assertSame('cache.adapter.array', $chainCachePool->getArgument(0)[0]->getParent());

src/Symfony/Component/Console/Application.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1275,7 +1275,7 @@ private function splitStringByWidth(string $string, int $width): array
12751275

12761276
foreach (preg_split('//u', $m[0]) as $char) {
12771277
// test if $char could be appended to current line
1278-
if (mb_strwidth($line.$char, 'utf8') <= $width) {
1278+
if (Helper::width($line.$char) <= $width) {
12791279
$line .= $char;
12801280
continue;
12811281
}

src/Symfony/Component/Console/Helper/Helper.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ public static function width(?string $string): int
4242
$string ??= '';
4343

4444
if (preg_match('//u', $string)) {
45-
return (new UnicodeString($string))->width(false);
45+
$string = preg_replace('/[\p{Cc}\x7F]++/u', '', $string, -1, $count);
46+
47+
return (new UnicodeString($string))->width(false) + $count;
4648
}
4749

4850
if (false === $encoding = mb_detect_encoding($string, null, true)) {

src/Symfony/Component/Console/Helper/Table.php

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -561,10 +561,7 @@ private function renderCell(array $row, int $column, string $cellFormat): string
561561
}
562562

563563
// str_pad won't work properly with multi-byte strings, we need to fix the padding
564-
if (false !== $encoding = mb_detect_encoding($cell, null, true)) {
565-
$width += \strlen($cell) - mb_strwidth($cell, $encoding);
566-
}
567-
564+
$width += \strlen($cell) - Helper::width($cell) - substr_count($cell, "\0");
568565
$style = $this->getColumnStyle($column);
569566

570567
if ($cell instanceof TableSeparator) {
@@ -629,8 +626,48 @@ private function buildTableRows(array $rows): TableRows
629626
foreach ($rows[$rowKey] as $column => $cell) {
630627
$colspan = $cell instanceof TableCell ? $cell->getColspan() : 1;
631628

632-
if (isset($this->columnMaxWidths[$column]) && Helper::width(Helper::removeDecoration($formatter, $cell)) > $this->columnMaxWidths[$column]) {
633-
$cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column] * $colspan);
629+
$minWrappedWidth = 0;
630+
$widthApplied = [];
631+
$lengthColumnBorder = $this->getColumnSeparatorWidth() + Helper::width($this->style->getCellRowContentFormat()) - 2;
632+
for ($i = $column; $i < ($column + $colspan); ++$i) {
633+
if (isset($this->columnMaxWidths[$i])) {
634+
$minWrappedWidth += $this->columnMaxWidths[$i];
635+
$widthApplied[] = ['type' => 'max', 'column' => $i];
636+
} elseif (($this->columnWidths[$i] ?? 0) > 0 && $colspan > 1) {
637+
$minWrappedWidth += $this->columnWidths[$i];
638+
$widthApplied[] = ['type' => 'min', 'column' => $i];
639+
}
640+
}
641+
if (1 === \count($widthApplied)) {
642+
if ($colspan > 1) {
643+
$minWrappedWidth *= $colspan; // previous logic
644+
}
645+
} elseif (\count($widthApplied) > 1) {
646+
$minWrappedWidth += (\count($widthApplied) - 1) * $lengthColumnBorder;
647+
}
648+
649+
$cellWidth = Helper::width(Helper::removeDecoration($formatter, $cell));
650+
if ($minWrappedWidth && $cellWidth > $minWrappedWidth) {
651+
$cell = $formatter->formatAndWrap($cell, $minWrappedWidth);
652+
}
653+
// update minimal columnWidths for spanned columns
654+
if ($colspan > 1 && $minWrappedWidth > 0) {
655+
$columnsMinWidthProcessed = [];
656+
$cellWidth = min($cellWidth, $minWrappedWidth);
657+
foreach ($widthApplied as $item) {
658+
if ('max' === $item['type'] && $cellWidth >= $this->columnMaxWidths[$item['column']]) {
659+
$minWidthColumn = $this->columnMaxWidths[$item['column']];
660+
$this->columnWidths[$item['column']] = $minWidthColumn;
661+
$columnsMinWidthProcessed[$item['column']] = true;
662+
$cellWidth -= $minWidthColumn + $lengthColumnBorder;
663+
}
664+
}
665+
for ($i = $column; $i < ($column + $colspan); ++$i) {
666+
if (isset($columnsMinWidthProcessed[$i])) {
667+
continue;
668+
}
669+
$this->columnWidths[$i] = $cellWidth + $lengthColumnBorder;
670+
}
634671
}
635672
if (!str_contains($cell ?? '', "\n")) {
636673
continue;

src/Symfony/Component/Console/Tests/Helper/TableTest.php

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,9 +1308,9 @@ public static function renderSetTitle()
13081308
'footer',
13091309
'default',
13101310
<<<'TABLE'
1311-
+---------------+---- Multiline
1311+
+---------------+--- Multiline
13121312
header
1313-
here -+------------------+
1313+
here +------------------+
13141314
| ISBN | Title | Author |
13151315
+---------------+--------------------------+------------------+
13161316
| 99921-58-10-7 | Divine Comedy | Dante Alighieri |
@@ -1590,17 +1590,17 @@ public function testWithColspanAndMaxWith()
15901590
$expected =
15911591
<<<TABLE
15921592
+-----------------+-----------------+-----------------+
1593-
| Lorem ipsum dolor sit amet, consectetur adipi |
1594-
| scing elit, sed do eiusmod tempor |
1593+
| Lorem ipsum dolor sit amet, consectetur adipiscing |
1594+
| elit, sed do eiusmod tempor |
15951595
+-----------------+-----------------+-----------------+
1596-
| Lorem ipsum dolor sit amet, consectetur |
1597-
| adipiscing elit, sed do eiusmod tempor |
1596+
| Lorem ipsum dolor sit amet, consectetur adipiscing |
1597+
| elit, sed do eiusmod tempor |
15981598
+-----------------+-----------------+-----------------+
1599-
| Lorem ipsum dolor sit amet, co | hello world |
1600-
| nsectetur | |
1599+
| Lorem ipsum dolor sit amet, conse | hello world |
1600+
| ctetur | |
16011601
+-----------------+-----------------+-----------------+
1602-
| hello world | Lorem ipsum dolor sit amet, co |
1603-
| | nsectetur adipiscing elit |
1602+
| hello world | Lorem ipsum dolor sit amet, conse |
1603+
| | ctetur adipiscing elit |
16041604
+-----------------+-----------------+-----------------+
16051605
| hello | world | Lorem ipsum |
16061606
| | | dolor sit amet, |
@@ -2092,4 +2092,36 @@ public function testGithubIssue52101HorizontalFalse()
20922092
$this->getOutputContent($output)
20932093
);
20942094
}
2095+
2096+
public function testGithubIssue60038WidthOfCellWithEmoji()
2097+
{
2098+
$table = (new Table($output = $this->getOutputStream()))
2099+
->setHeaderTitle('Test Title')
2100+
->setHeaders(['Title', 'Author'])
2101+
->setRows([
2102+
["🎭 💫 ☯"." Divine Comedy", "Dante Alighieri"],
2103+
// the snowflake (e2 9d 84 ef b8 8f) has a variant selector
2104+
["👑 ❄️ 🗡"." Game of Thrones", "George R.R. Martin"],
2105+
// the snowflake in text style (e2 9d 84 ef b8 8e) has a variant selector
2106+
["❄︎❄︎❄︎ snowflake in text style ❄︎❄︎❄︎", ""],
2107+
["And a very long line to show difference in previous lines", ""],
2108+
])
2109+
;
2110+
$table->render();
2111+
2112+
$this->assertSame(<<<TABLE
2113+
+---------------------------------- Test Title -------------+--------------------+
2114+
| Title | Author |
2115+
+-----------------------------------------------------------+--------------------+
2116+
| 🎭 💫 ☯ Divine Comedy | Dante Alighieri |
2117+
| 👑 ❄️ 🗡 Game of Thrones | George R.R. Martin |
2118+
| ❄︎❄︎❄︎ snowflake in text style ❄︎❄︎❄︎ | |
2119+
| And a very long line to show difference in previous lines | |
2120+
+-----------------------------------------------------------+--------------------+
2121+
2122+
TABLE
2123+
,
2124+
$this->getOutputContent($output)
2125+
);
2126+
}
20952127
}

src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,10 @@ private function mapRequestPayload(Request $request, ArgumentMetadata $argument,
232232

233233
private function mapUploadedFile(Request $request, ArgumentMetadata $argument, MapUploadedFile $attribute): UploadedFile|array|null
234234
{
235-
return $request->files->get($attribute->name ?? $argument->getName(), []);
235+
if (!($files = $request->files->get($attribute->name ?? $argument->getName(), [])) && ($argument->isNullable() || $argument->hasDefaultValue())) {
236+
return null;
237+
}
238+
239+
return $files;
236240
}
237241
}

src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/UploadedFileValueResolverTest.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,66 @@ static function () {},
307307
$resolver->onKernelControllerArguments($event);
308308
}
309309

310+
/**
311+
* @dataProvider provideContext
312+
*/
313+
public function testShouldAllowEmptyWhenNullable(RequestPayloadValueResolver $resolver, Request $request)
314+
{
315+
$attribute = new MapUploadedFile();
316+
$argument = new ArgumentMetadata(
317+
'qux',
318+
57AE UploadedFile::class,
319+
false,
320+
false,
321+
null,
322+
true,
323+
[$attribute::class => $attribute]
324+
);
325+
/** @var HttpKernelInterface&MockObject $httpKernel */
326+
$httpKernel = $this->createMock(HttpKernelInterface::class);
327+
$event = new ControllerArgumentsEvent(
328+
$httpKernel,
329+
static function () {},
330+
$resolver->resolve($request, $argument),
331+
$request,
332+
HttpKernelInterface::MAIN_REQUEST
333+
);
334+
$resolver->onKernelControllerArguments($event);
335+
$data = $event->getArguments()[0];
336+
337+
$this->assertNull($data);
338+
}
339+
340+
/**
341+
* @dataProvider provideContext
342+
*/
343+
public function testShouldAllowEmptyWhenHasDefaultValue(RequestPayloadValueResolver $resolver, Request $request)
344+
{
345+
$attribute = new MapUploadedFile();
346+
$argument = new ArgumentMetadata(
347+
'qux',
348+
UploadedFile::class,
349+
false,
350+
true,
351+
'default-value',
352+
false,
353+
[$attribute::class => $attribute]
354+
);
355+
/** @var HttpKernelInterface&MockObject $httpKernel */
356+
$httpKernel = $this->createMock(HttpKernelInterface::class);
357+
$event = new ControllerArgumentsEvent(
358+
$httpKernel,
359+
static function () {},
360+
$resolver->resolve($request, $argument),
361+
$request,
362+
HttpKernelInterface::MAIN_REQUEST
363+
);
364+
$resolver->onKernelControllerArguments($event);
365+
$data = $event->getArguments()[0];
366+
367+
$this->assertSame('default-value', $data);
368+
}
369+
310370
public static function provideContext(): iterable
311371
{
312372
$resolver = new RequestPayloadValueResolver(

src/Symfony/Component/Mailer/Command/MailerTestCommand.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ protected function configure(): void
3535
{
3636
$this
3737
->addArgument('to', InputArgument::REQUIRED, 'The recipient of the message')
38-
->addOption('from', null, InputOption::VALUE_OPTIONAL, 'The sender of the message', 'from@example.org')
39-
->addOption('subject', null, InputOption::VALUE_OPTIONAL, 'The subject of the message', 'Testing transport')
40-
->addOption('body', null, InputOption::VALUE_OPTIONAL, 'The body of the message', 'Testing body')
41-
->addOption('transport', null, InputOption::VALUE_OPTIONAL, 'The transport to be used')
38+
->addOption('from', null, InputOption::VALUE_REQUIRED, 'The sender of the message', 'from@example.org')
39+
->addOption('subject', null, InputOption::VALUE_REQUIRED, 'The subject of the message', 'Testing transport')
40+
->addOption('body', null, InputOption::VALUE_REQUIRED, 'The body of the message', 'Testing body')
41+
->addOption('transport', null, InputOption::VALUE_REQUIRED, 'The transport to be used')
4242
->setHelp(<<<'EOF'
4343
The <info>%command.name%</info> command tests a Mailer transport by sending a simple email message:
4444

src/Symfony/Component/Messenger/Command/FailedMessagesRemoveCommand.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ protected function configure(): void
3535
new InputArgument('id', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Specific message id(s) to remove'),
3636
new InputOption('all', null, InputOption::VALUE_NONE, 'Remove all failed messages from the transport'),
3737
new InputOption('force', null, InputOption::VALUE_NONE, 'Force the operation without confirmation'),
38-
new InputOption('transport', null, InputOption::VALUE_OPTIONAL, 'Use a specific failure transport', self::DEFAULT_TRANSPORT_OPTION),
38+
new InputOption('transport', null, InputOption::VALUE_REQUIRED, 'Use a specific failure transport', self::DEFAULT_TRANSPORT_OPTION),
3939
new InputOption('show-messages', null, InputOption::VALUE_NONE, 'Display messages before removing it (if multiple ids are given)'),
4040
9EA1 new InputOption('class-filter', null, InputOption::VALUE_REQUIRED, 'Filter by a specific class name'),
4141
])

src/Symfony/Component/Messenger/Command/FailedMessagesRetryCommand.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ protected function configure(): void
6565
->setDefinition([
6666
new InputArgument('id', InputArgument::IS_ARRAY, 'Specific message id(s) to retry'),
6767
new InputOption('force', null, InputOption::VALUE_NONE, 'Force action without confirmation'),
68-
new InputOption('transport', null, InputOption::VALUE_OPTIONAL, 'Use a specific failure transport', self::DEFAULT_TRANSPORT_OPTION),
69-
new InputOption('keepalive', null, InputOption::VALUE_OPTIONAL, 'Whether to use the transport\'s keepalive mechanism if implemented', self::DEFAULT_KEEPALIVE_INTERVAL),
68+
new InputOption('transport', null, InputOption::VALUE_REQUIRED, 'Use a specific failure transport', self::DEFAULT_TRANSPORT_OPTION),
69+
new InputOption('keepalive', null, InputOption::VALUE_REQUIRED, 'Whether to use the transport\'s keepalive mechanism if implemented', self::DEFAULT_KEEPALIVE_INTERVAL),
7070
])
7171
->setHelp(<<<'EOF'
7272
The <info>%command.name%</info> retries message in the failure transport.

0 commit comments

Comments
 (0)
0