Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0c6c5f0

Browse files
committed
Cover missing cases
1 parent 4ea06cc commit 0c6c5f0

File tree

9 files changed

+111
-82
lines changed

9 files changed

+111
-82
lines changed

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

+5-3
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,11 @@ 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'])) {
13421342
$container->removeDefinition('translation.extractor.php');
1343+
} else {
1344+
$container->removeDefinition('translation.extractor.php_ast');
13431345
}
13441346

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

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

+5-2
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

+1
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 <[email protected]>
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

+21-15
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

+34-46
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

+5-3
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

+5-3
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/Extractor/PhpAstExtractorTest.php

+13-4
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public function testExtraction(iterable|string $resource)
3232
'NotBlank',
3333
'Isbn',
3434
'Length',
35-
]),
35+
], new TranslatableMessageVisitor()),
3636
]);
3737
$extractor->setPrefix('prefix');
3838
$catalogue = new MessageCatalogue('en');
@@ -122,8 +122,17 @@ public function testExtraction(iterable|string $resource)
122122
],
123123
'validators' => [
124124
'message-in-constraint-attribute' => 'prefixmessage-in-constraint-attribute',
125-
'custom Isbn message' => 'prefixcustom Isbn message',
126-
'custom Length exact message' => 'prefixcustom Length exact message',
125+
// 'custom Isbn message from attribute' => 'prefixcustom Isbn message from attribute',
126+
'custom Isbn message from attribute with options as array' => 'prefixcustom Isbn message from attribute with options as array',
127+
'custom Length exact message from attribute from named argument' => 'prefixcustom Length exact message from attribute from named argument',
128+
'custom Length exact message from attribute from named argument 1/2' => 'prefixcustom Length exact message from attribute from named argument 1/2',
129+
'custom Length min message from attribute from named argument 2/2' => 'prefixcustom Length min message from attribute from named argument 2/2',
130+
// 'custom Isbn message' => 'prefixcustom Isbn message',
131+
'custom Isbn message with options as array' => 'prefixcustom Isbn message with options as array',
132+
'custom Isbn message from named argument' => 'prefixcustom Isbn message from named argument',
133+
'custom Length exact message from named argument' => 'prefixcustom Length exact message from named argument',
134+
'custom Length exact message from named argument 1/2' => 'prefixcustom Length exact message from named argument 1/2',
135+
'custom Length min message from named argument 2/2' => 'prefixcustom Length min message from named argument 2/2',
127136
],
128137
];
129138
$actualCatalogue = $catalogue->all();
@@ -158,7 +167,7 @@ public function testExtractionFromIndentedHeredocNowdoc()
158167
'NotBlank',
159168
'Isbn',
160169
'Length',
161-
]),
170+
], new TranslatableMessageVisitor()),
162171
]);
163172
$extractor->setPrefix('prefix');
164173
$extractor->extract(__DIR__.'/../fixtures/extractor-7.3/translation.html.php', $catalogue);

src/Symfony/Component/Translation/Tests/fixtures/extractor-ast/validator-constraints.php

+22-6
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,34 @@ class Foo
77
{
88
#[Assert\NotBlank(message: 'message-in-constraint-attribute')]
99
public string $bar;
10+
11+
#[Assert\Length(exactMessage: 'custom Length exact message from attribute from named argument')]
12+
public string $bar2;
13+
14+
#[Assert\Length(exactMessage: 'custom Length exact message from attribute from named argument 1/2', minMessage: 'custom Length min message from attribute from named argument 2/2')]
15+
public string $bar3;
16+
17+
#[Assert\Isbn('isbn10', 'custom Isbn message from attribute')] // no way to handle those arguments (not named, not in associative array).
18+
public string $isbn;
19+
20+
#[Assert\Isbn([
21+
'type' => 'isbn10',
22+
'message' => 'custom Isbn message from attribute with options as array',
23+
])]
24+
public string $isbn2;
1025
}
1126

1227
class Foo2
1328
{
1429
public function index()
1530
{
16-
$constraint1 = new Assert\Isbn([
17-
'message' => 'custom Isbn message',
18-
]);
19-
20-
$constraint2 = new Assert\Length([
21-
'exactMessage' => 'custom Length exact message',
31+
$constraint1 = new Assert\Isbn('isbn10', 'custom Isbn message'); // no way to handle those arguments (not named, not in associative array).
32+
$constraint2 = new Assert\Isbn([
33+
'type' => 'isbn10',
34+
'message' => 'custom Isbn message with options as array',
2235
]);
36+
$constraint3 = new Assert\Isbn(message: 'custom Isbn message from named argument');
37+
$constraint4 = new Assert\Length(exactMessage: 'custom Length exact message from named argument');
38+
$constraint5 = new Assert\Length(exactMessage: 'custom Length exact message from named argument 1/2', minMessage: 'custom Length min message from named argument 2/2');
2339
}
2440
}

0 commit comments

Comments
 (0)