Skip to content

Commit 4d28eb2

Browse files
committed
Cover missing cases
1 parent 500ea96 commit 4d28eb2

File tree

9 files changed

+111
-82
lines changed

9 files changed

+111
-82
lines changed

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

Lines changed: 5 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;
@@ -1335,10 +1336,11 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder
13351336
$container->removeDefinition('translation.locale_switcher');
13361337
}
13371338

1338-
if (!ContainerBuilder::willBeAvailable('nikic/php-parser', Parser::class, ['symfony/translation'])) {
1339-
$container->removeDefinition('translation.extractor.php_ast');
1340-
} else {
1339+
if (ContainerBuilder::willBeAvailable('nikic/php-parser', Parser::class, ['symfony/translation'])
1340+
&& ContainerBuilder::willBeAvailable('symfony/translation', PhpAstExtractor::class, ['symfony/framework-bundle'])) {
13411341
$container->removeDefinition('translation.extractor.php');
1342+
} else {
1343+
$container->removeDefinition('translation.extractor.php_ast');
13421344
}
13431345

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

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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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

Lines 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/Extractor/PhpAstExtractorTest.php

Lines changed: 13 additions & 4 deletions
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

Lines changed: 22 additions & 6 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy