Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit c044b85

Browse filesBrowse files
committed
Cover missing cases
1 parent dcac174 commit c044b85
Copy full SHA for c044b85

File tree

Expand file treeCollapse file tree

12 files changed

+158
-93
lines changed
Filter options
Expand file treeCollapse file tree

12 files changed

+158
-93
lines changed

‎UPGRADE-6.2.md

Copy file name to clipboardExpand all lines: UPGRADE-6.2.md
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ Serializer
9696
* Change the signature of `AttributeMetadataInterface::setSerializedName()` to `setSerializedName(?string)`
9797
* Change the signature of `ClassMetadataInterface::setClassDiscriminatorMapping()` to `setClassDiscriminatorMapping(?ClassDiscriminatorMapping)`
9898

99+
Translation
100+
-----------
101+
102+
* Deprecate `PhpExtractor` in favor of `PhpAstExtractor`
103+
* Add `PhpAstExtractor` (requires [nikic/php-parser](https://github.com/nikic/php-parser) to be installed)
104+
99105
Validator
100106
---------
101107

‎src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+6-3Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@
219219
use Symfony\Component\Translation\Bridge\Loco\LocoProviderFactory;
220220
use Symfony\Component\Translation\Bridge\Lokalise\LokaliseProviderFactory;
221221
use Symfony\Component\Translation\Command\XliffLintCommand as BaseXliffLintCommand;
222+
use Symfony\Component\Translation\Extractor\PhpAstExtractor;
222223
use Symfony\Component\Translation\LocaleSwitcher;
223224
use Symfony\Component\Translation\PseudoLocalizationTranslator;
224225
use Symfony\Component\Translation\Translator;
@@ -1336,10 +1337,12 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder
13361337
$container->removeDefinition('translation.locale_switcher');
13371338
}
13381339

1339-
if (!ContainerBuilder::willBeAvailable('nikic/php-parser', Parser::class, ['symfony/translation'])) {
1340-
$container->removeDefinition('translation.extractor.php_ast');
1341-
} else {
1340+
if (ContainerBuilder::willBeAvailable('nikic/php-parser', Parser::class, ['symfony/translation'])
1341+
&& ContainerBuilder::willBeAvailable('symfony/translation', PhpAstExtractor::class, ['symfony/framework-bundle'])
1342+
) {
13421343
$container->removeDefinition('translation.extractor.php');
1344+
} else {
1345+
$container->removeDefinition('translation.extractor.php_ast');
13431346
}
13441347

13451348
$loader->load('translation_providers.php');

‎src/Symfony/Component/Translation/DependencyInjection/TranslatorPass.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Translation/DependencyInjection/TranslatorPass.php
+13-11Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,19 @@ public function process(ContainerBuilder $container)
4949
->replaceArgument(3, $loaders)
5050
;
5151

52+
if ($container->hasDefinition('validator') && $container->hasDefinition('translation.extractor.visitor.constraint')) {
53+
$constraintVisitorDefinition = $container->getDefinition('translation.extractor.visitor.constraint');
54+
$constraintClassNames = [];
55+
56+
foreach ($container->findTaggedServiceIds('validator.constraint_validator', true) as $id => $attributes) {
57+
$serviceDefinition = $container->getDefinition($id);
58+
// Extraction of the constraint class name from the Constraint Validator FQCN
59+
$constraintClassNames[] = str_replace('Validator', '', substr(strrchr($serviceDefinition->getClass(), '\\'), 1));
60+
}
61+
62+
$constraintVisitorDefinition->setArgument(0, $constraintClassNames);
63+
}
64+
5265
if (!$container->hasParameter('twig.default_path')) {
5366
return;
5467
}
@@ -70,16 +83,5 @@ public function process(ContainerBuilder $container)
7083
$definition->replaceArgument(7, $paths);
7184
}
7285
}
73-
74-
if ($container->hasDefinition('validator') && $container->hasDefinition('translation.extractor.visitor.constraint')) {
75-
$definition = $container->findDefinition('translation.extractor.visitor.constraint');
76-
$constraintClassNames = [];
77-
78-
foreach (array_keys($container->findTaggedServiceIds('validator.constraint_validator', true)) as $serviceId) {
79-
$constraintClassNames[] = str_replace('Validator', '', $serviceId);
80-
}
81-
82-
$definition->setArgument(0, $constraintClassNames);
83-
}
8486
}
8587
}

‎src/Symfony/Component/Translation/Extractor/PhpAstExtractor.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Translation/Extractor/PhpAstExtractor.php
+5-2Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
use PhpParser\Parser;
1717
use PhpParser\ParserFactory;
1818
use Symfony\Component\Finder\Finder;
19-
use Symfony\Component\Translation\Extractor\Visitor\Visitor;
19+
use Symfony\Component\Translation\Extractor\Visitor\AbstractVisitor;
2020
use Symfony\Component\Translation\MessageCatalogue;
2121

2222
/**
@@ -29,6 +29,9 @@ final class PhpAstExtractor extends AbstractFileExtractor implements ExtractorIn
2929
private Parser $parser;
3030

3131
public function __construct(
32+
/**
33+
* @param iterable<AbstractVisitor&NodeVisitor> $visitors
34+
*/
3235
private readonly iterable $visitors,
3336
private string $prefix = '',
3437
) {
@@ -43,7 +46,7 @@ public function extract(iterable|string $resource, MessageCatalogue $catalogue):
4346
{
4447
foreach ($this->extractFiles($resource) as $file) {
4548
$traverser = new NodeTraverser();
46-
/** @var Visitor&NodeVisitor $visitor */
49+
/** @var AbstractVisitor&NodeVisitor $visitor */
4750
foreach ($this->visitors as $visitor) {
4851
$visitor->initialize($catalogue, $file, $this->prefix);
4952
$traverser->addVisitor($visitor);

‎src/Symfony/Component/Translation/Extractor/PhpExtractor.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Translation/Extractor/PhpExtractor.php
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
* PhpExtractor extracts translation messages from a PHP template.
2121
*
2222
* @author Michel Salib <michelsalib@hotmail.com>
23+
*
2324
* @deprecated since Symfony 6.2, use the PhpAstExtractor instead
2425
*/
2526
class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface

‎src/Symfony/Component/Translation/Extractor/Visitor/AbstractVisitor.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Translation/Extractor/Visitor/AbstractVisitor.php
+21-15Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
*/
2020
abstract class AbstractVisitor
2121
{
22-
protected MessageCatalogue $catalogue;
23-
protected \SplFileInfo $file;
24-
protected string $messagePrefix;
22+
private MessageCatalogue $catalogue;
23+
private \SplFileInfo $file;
24+
private string $messagePrefix;
2525

2626
public function initialize(MessageCatalogue $catalogue, \SplFileInfo $file, string $messagePrefix): void
2727
{
@@ -40,29 +40,32 @@ protected function addMessageToCatalogue(string $message, ?string $domain, int $
4040
$this->catalogue->setMetadata($message, $metadata, $domain);
4141
}
4242

43-
protected function getStringArgument(Node\Expr\CallLike|Node\Attribute $node, int|string $index): ?string
43+
protected function getStringArguments(Node\Expr\CallLike|Node\Attribute|Node\Expr\New_ $node, int|string $index, bool $indexIsRegex = false): array
4444
{
4545
$args = $node instanceof Node\Expr\CallLike ? $node->getArgs() : $node->args;
4646

4747
if (\is_string($index)) {
48-
return $this->getStringNamedArgument($node, $index);
48+
return $this->getStringNamedArguments($node, $index, $indexIsRegex);
4949
}
5050

51-
if (!\array_key_exists($index, $args)) {
52-
return null;
51+
if (\count($args) < $index) {
52+
return [];
5353
}
5454

55-
if ('' === $result = $this->getStringValue($args[$index]->value)) {
56-
return null;
55+
/** @var Node\Arg $arg */
56+
$arg = $args[$index];
57+
if (!$result = $this->getStringValue($arg->value)) {
58+
return [];
5759
}
5860

59-
return $result;
61+
return [$result];
6062
}
6163

62-
protected function hasNodeNamedArguments(Node\Expr\CallLike|Node\Attribute $node): bool
64+
protected function hasNodeNamedArguments(Node\Expr\CallLike|Node\Attribute|Node\Expr\New_ $node): bool
6365
{
6466
$args = $node instanceof Node\Expr\CallLike ? $node->getArgs() : $node->args;
6567

68+
/** @var Node\Arg $arg */
6669
foreach ($args as $arg) {
6770
if (null !== $arg->name) {
6871
return true;
@@ -72,17 +75,20 @@ protected function hasNodeNamedArguments(Node\Expr\CallLike|Node\Attribute $node
7275
return false;
7376
}
7477

75-
private function getStringNamedArgument(Node\Expr\CallLike|Node\Attribute $node, string $argumentName): ?string
78+
private function getStringNamedArguments(Node\Expr\CallLike|Node\Attribute $node, string $argumentName = null, bool $isArgumentNamePattern = false): array
7679
{
7780
$args = $node instanceof Node\Expr\CallLike ? $node->getArgs() : $node->args;
81+
$argumentValues = [];
7882

7983
foreach ($args as $arg) {
80-
if ($arg->name?->toString() === $argumentName) {
81-
return $this->getStringValue($arg->value);
84+
if (!$isArgumentNamePattern && $arg->name?->toString() === $argumentName) {
85+
$argumentValues[] = $this->getStringValue($arg->value);
86+
} elseif ($isArgumentNamePattern && preg_match($argumentName, $arg->name?->toString() ?? '') > 0) {
87+
$argumentValues[] = $this->getStringValue($arg->value);
8288
}
8389
}
8490

85-
return null;
91+
return array_filter($argumentValues);
8692
}
8793

8894
private function getStringValue(Node $node): ?string

‎src/Symfony/Component/Translation/Extractor/Visitor/ConstraintVisitor.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Translation/Extractor/Visitor/ConstraintVisitor.php
+34-46Lines changed: 34 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
*/
2222
final class ConstraintVisitor extends AbstractVisitor implements NodeVisitor
2323
{
24+
private const CONSTRAINT_VALIDATION_MESSAGE_PATTERN = '/[a-zA-Z]*message/i';
25+
2426
public function __construct(
2527
private readonly array $constraintClassNames = []
2628
) {
@@ -57,69 +59,55 @@ public function enterNode(Node $node): ?Node
5759
return null;
5860
}
5961

60-
if ($node instanceof Node\Expr\New_) {
61-
$this->extractFromConstraintClassInstantiation($node);
62-
} else {
63-
$this->extractFromConstraintAttribute($node);
64-
}
65-
66-
return null;
67-
}
68-
69-
public function leaveNode(Node $node): ?Node
70-
{
71-
return null;
72-
}
73-
74-
public function afterTraverse(array $nodes): ?Node
75-
{
76-
return null;
77-
}
78-
79-
private function extractFromConstraintClassInstantiation(Node\Expr\New_ $node): void
80-
{
8162
$arg = $node->args[0] ?? null;
8263
if (!$arg instanceof Node\Arg) {
83-
return;
64+
return null;
8465
}
8566

86-
$options = $arg->value;
87-
if (!$options instanceof Node\Expr\Array_) {
88-
return;
89-
}
67+
if ($this->hasNodeNamedArguments($node)) {
68+
$messages = $this->getStringArguments($node, self::CONSTRAINT_VALIDATION_MESSAGE_PATTERN, true);
69+
} else {
70+
if (!$arg->value instanceof Node\Expr\Array_) {
71+
// There is no way to guess which argument is a message to be translated.
72+
return null;
73+
}
9074

91-
$message = null;
75+
$options = $arg->value;
9276

93-
/** @var Node\Expr\ArrayItem $item */
94-
foreach ($options->items as $item) {
95-
if (!$item->key instanceof Node\Scalar\String_) {
96-
continue;
97-
}
77+
/** @var Node\Expr\ArrayItem $item */
78+
foreach ($options->items as $item) {
79+
if (!$item->key instanceof Node\Scalar\String_) {
80+
continue;
81+
}
9882

99-
if (false === stripos($item->key->value, 'message')) {
100-
continue;
101-
}
83+
if (!preg_match(self::CONSTRAINT_VALIDATION_MESSAGE_PATTERN, $item->key->value ?? '')) {
84+
continue;
85+
}
10286

103-
if (!$item->value instanceof Node\Scalar\String_) {
104-
continue;
105-
}
87+
if (!$item->value instanceof Node\Scalar\String_) {
88+
continue;
89+
}
10690

107-
$message = $item->value->value;
91+
$messages[] = $item->value->value;
10892

109-
break;
93+
break;
94+
}
11095
}
11196

112-
if (null !== $message) {
97+
foreach ($messages as $message) {
11398
$this->addMessageToCatalogue($message, 'validators', $node->getStartLine());
11499
}
100+
101+
return null;
115102
}
116103

117-
private function extractFromConstraintAttribute(Node\Attribute $node): void
104+
public function leaveNode(Node $node): ?Node
118105
{
119-
if (null === $message = $this->getStringArgument($node, $this->hasNodeNamedArguments($node) ? 'message' : 0)) {
120-
return;
121-
}
106+
return null;
107+
}
122108

123-
$this->addMessageToCatalogue($message, 'validators', $node->getStartLine());
109+
public function afterTraverse(array $nodes): ?Node
110+
{
111+
return null;
124112
}
125113
}

‎src/Symfony/Component/Translation/Extractor/Visitor/TransMethodVisitor.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Translation/Extractor/Visitor/TransMethodVisitor.php
+5-3Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,15 @@ public function enterNode(Node $node): ?Node
3838

3939
if ('trans' === $name || 't' === $name) {
4040
$nodeHasNamedArguments = $this->hasNodeNamedArguments($node);
41-
if (null === $message = $this->getStringArgument($node, $nodeHasNamedArguments ? 'message' : 0)) {
41+
if (!$messages = $this->getStringArguments($node, $nodeHasNamedArguments ? 'message' : 0)) {
4242
return null;
4343
}
4444

45-
$domain = $this->getStringArgument($node, $nodeHasNamedArguments ? 'domain' : 2);
45+
$domain = $this->getStringArguments($node, $nodeHasNamedArguments ? 'domain' : 2)[0] ?? null;
4646

47-
$this->addMessageToCatalogue($message, $domain, $node->getStartLine());
47+
foreach ($messages as $message) {
48+
$this->addMessageToCatalogue($message, $domain, $node->getStartLine());
49+
}
4850
}
4951

5052
return null;

‎src/Symfony/Component/Translation/Extractor/Visitor/TranslatableMessageVisitor.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Translation/Extractor/Visitor/TranslatableMessageVisitor.php
+5-3Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,15 @@ public function enterNode(Node $node): ?Node
4040

4141
$nodeHasNamedArguments = $this->hasNodeNamedArguments($node);
4242

43-
if (null === $message = $this->getStringArgument($node, $nodeHasNamedArguments ? 'message' : 0)) {
43+
if (!$messages = $this->getStringArguments($node, $nodeHasNamedArguments ? 'message' : 0)) {
4444
return null;
4545
}
4646

47-
$domain = $this->getStringArgument($node, $nodeHasNamedArguments ? 'domain' : 2);
47+
$domain = $this->getStringArguments($node, $nodeHasNamedArguments ? 'domain' : 2)[0] ?? null;
4848

49-
$this->addMessageToCatalogue($message, $domain, $node->getStartLine());
49+
foreach ($messages as $message) {
50+
$this->addMessageToCatalogue($message, $domain, $node->getStartLine());
51+
}
5052

5153
return null;
5254
}

‎src/Symfony/Component/Translation/Tests/DependencyInjection/TranslatorPassTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Translation/Tests/DependencyInjection/TranslatorPassTest.php
+27Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@
1717
use Symfony\Component\DependencyInjection\Definition;
1818
use Symfony\Component\DependencyInjection\Reference;
1919
use Symfony\Component\Translation\DependencyInjection\TranslatorPass;
20+
use Symfony\Component\Translation\Extractor\Visitor\ConstraintVisitor;
21+
use Symfony\Component\Validator\Constraints\Isbn;
22+
use Symfony\Component\Validator\Constraints\IsbnValidator;
23+
use Symfony\Component\Validator\Constraints\LengthValidator;
24+
use Symfony\Component\Validator\Constraints\NotBlank;
25+
use Symfony\Component\Validator\Constraints\NotBlankValidator;
26+
use Symfony\Component\Validator\Validator\ValidatorInterface;
2027

2128
class TranslatorPassTest extends TestCase
2229
{
@@ -119,4 +126,24 @@ public function testCommandsViewPathsArgumentsAreIgnoredWithOldServiceDefinition
119126
$this->assertSame('templates', $debugCommand->getArgument(4));
120127
$this->assertSame('templates', $updateCommand->getArgument(5));
121128
}
129+
130+
public function testValidPhpAstExtractorConstraintVisitorArguments()
131+
{
132+
$container = new ContainerBuilder();
133+
$container->register('translator.default')
134+
->setArguments([null, null, null, null]);
135+
$container->register('validator');
136+
$constraintVisitor = $container->register('translation.extractor.visitor.constraint', ConstraintVisitor::class);
137+
$container->register('validator.not_blank', NotBlankValidator::class)
138+
->addTag('validator.constraint_validator');
139+
$container->register('validator.isbn', IsbnValidator::class)
140+
->addTag('validator.constraint_validator');
141+
$container->register('validator.length', LengthValidator::class)
142+
->addTag('validator.constraint_validator');
143+
144+
$pass = new TranslatorPass();
145+
$pass->process($container);
146+
147+
$this->assertSame(['NotBlank', 'Isbn', 'Length'], $constraintVisitor->getArgument(0));
148+
}
122149
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.