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

Skip to content

Commit 16d5c42

Browse files
committed
Add PhpAstExtractor
1 parent 2add2f2 commit 16d5c42

19 files changed

+983
-1
lines changed

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

+7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Http\Client\HttpClient;
1818
use phpDocumentor\Reflection\DocBlockFactoryInterface;
1919
use phpDocumentor\Reflection\Types\ContextFactory;
20+
use PhpParser\Parser;
2021
use PHPStan\PhpDocParser\Parser\PhpDocParser;
2122
use Psr\Cache\CacheItemPoolInterface;
2223
use Psr\Container\ContainerInterface as PsrContainerInterface;
@@ -1311,6 +1312,12 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder
13111312
$container->removeDefinition('translation.locale_switcher');
13121313
}
13131314

1315+
if (ContainerBuilder::willBeAvailable('nikic/php-parser', Parser::class, ['symfony/translation'])) {
1316+
$container->removeDefinition('translation.extractor.php');
1317+
} else {
1318+
$container->removeDefinition('translation.extractor.php_ast');
1319+
}
1320+
13141321
$loader->load('translation_providers.php');
13151322

13161323
// Use the "real" translator instead of the identity default

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

+4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use Symfony\Component\Translation\Dumper\YamlFileDumper;
2727
use Symfony\Component\Translation\Extractor\ChainExtractor;
2828
use Symfony\Component\Translation\Extractor\ExtractorInterface;
29+
use Symfony\Component\Translation\Extractor\PhpAstExtractor;
2930
use Symfony\Component\Translation\Extractor\PhpExtractor;
3031
use Symfony\Component\Translation\Formatter\MessageFormatter;
3132
use Symfony\Component\Translation\Loader\CsvFileLoader;
@@ -151,6 +152,9 @@
151152
->set('translation.extractor.php', PhpExtractor::class)
152153
->tag('translation.extractor', ['alias' => 'php'])
153154

155+
->set('translation.extractor.php_ast', PhpAstExtractor::class)
156+
->tag('translation.extractor', ['alias' => 'php_ast'])
157+
154158
->set('translation.reader', TranslationReader::class)
155159
->alias(TranslationReaderInterface::class, 'translation.reader')
156160

src/Symfony/Component/Translation/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
6.2
5+
---
6+
7+
* Add `PhpAstExtractor`
8+
49
6.1
510
---
611

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Translation\Extractor;
13+
14+
use PhpParser\NodeTraverser;
15+
use PhpParser\Parser;
16+
use PhpParser\ParserFactory;
17+
use Symfony\Component\Finder\Finder;
18+
use Symfony\Component\Translation\Extractor\Visitor\ConstraintVisitor;
19+
use Symfony\Component\Translation\Extractor\Visitor\TranslatableMessageVisitor;
20+
use Symfony\Component\Translation\Extractor\Visitor\TransMethodVisitor;
21+
use Symfony\Component\Translation\Extractor\Visitor\Visitor;
22+
use Symfony\Component\Translation\MessageCatalogue;
23+
24+
/**
25+
* PhpAstExtractor extracts translation messages from a PHP AST.
26+
*
27+
* @author Mathieu Santostefano <[email protected]>
28+
*/
29+
class PhpAstExtractor extends AbstractFileExtractor implements ExtractorInterface
30+
{
31+
private string $prefix = '';
32+
private Parser $parser;
33+
/**
34+
* @var Visitor[]
35+
*/
36+
private array $visitors;
37+
38+
public function __construct()
39+
{
40+
$this->parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
41+
$this->visitors = [
42+
new TransMethodVisitor(),
43+
new TranslatableMessageVisitor(),
44+
new ConstraintVisitor(),
45+
];
46+
}
47+
48+
public function extract(iterable|string $resource, MessageCatalogue $catalogue)
49+
{
50+
$files = $this->extractFiles($resource);
51+
foreach ($files as $file) {
52+
$traverser = new NodeTraverser();
53+
foreach ($this->visitors as $visitor) {
54+
$visitor->initialize($catalogue, $file, $this->prefix);
55+
$traverser->addVisitor($visitor);
56+
}
57+
58+
$nodes = $this->parser->parse(file_get_contents($file));
59+
$traverser->traverse($nodes);
60+
}
61+
}
62+
63+
public function setPrefix(string $prefix)
64+
{
65+
$this->prefix = $prefix;
66+
}
67+
68+
protected function canBeExtracted(string $file): bool
69+
{
70+
return $this->isFile($file) && 'php' === pathinfo($file, \PATHINFO_EXTENSION);
71+
}
72+
73+
protected function extractFromDirectory(array|string $resource): iterable|Finder
74+
{
75+
if (!class_exists(Finder::class)) {
76+
throw new \LogicException(sprintf('You cannot use "%s" as the "symfony/finder" package is not installed. Try running "composer require symfony/finder".', static::class));
77+
}
78+
79+
$finder = new Finder();
80+
81+
return $finder->files()->name('*.php')->in($resource);
82+
}
83+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Translation\Extractor\Visitor;
13+
14+
use PhpParser\Node;
15+
use Symfony\Component\Translation\MessageCatalogue;
16+
17+
/**
18+
* @author Mathieu Santostefano <[email protected]>
19+
*/
20+
abstract class AbstractVisitor
21+
{
22+
protected MessageCatalogue $catalogue;
23+
protected \SplFileInfo $file;
24+
protected string $messagePrefix;
25+
26+
public function initialize(MessageCatalogue $catalogue, \SplFileInfo $file, string $messagePrefix): void
27+
{
28+
$this->catalogue = $catalogue;
29+
$this->file = $file;
30+
$this->messagePrefix = $messagePrefix;
31+
}
32+
33+
protected function addMessageToCatalogue(string $message, ?string $domain, int $line): void
34+
{
35+
if (null === $domain) {
36+
$domain = 'messages';
37+
}
38+
39+
$this->catalogue->set($message, $this->messagePrefix.$message, $domain);
40+
$metadata = $this->catalogue->getMetadata($message, $domain) ?? [];
41+
$normalizedFilename = preg_replace('{[\\\\/]+}', '/', $this->file);
42+
$metadata['sources'][] = $normalizedFilename.':'.$line;
43+
$this->catalogue->setMetadata($message, $metadata, $domain);
44+
}
45+
46+
protected function getStringArgument(Node\Expr\CallLike|Node\Attribute $node, int|string $index): ?string
47+
{
48+
if (\is_string($index)) {
49+
return $this->getStringNamedArgument($node, $index);
50+
}
51+
52+
if (!\array_key_exists($index, $node->args)) {
53+
return null;
54+
}
55+
56+
$result = $this->getStringValue($node->args[$index]->value);
57+
58+
if ('' === $result) {
59+
return null;
60+
}
61+
62+
return $result;
63+
}
64+
65+
protected function hasNodeNamedArguments(Node\Expr\CallLike|Node\Attribute $node): bool
66+
{
67+
foreach ($node->args as $arg) {
68+
if (null !== $arg->name) {
69+
return true;
70+
}
71+
}
72+
73+
return false;
74+
}
75+
76+
private function getStringNamedArgument(Node\Expr\CallLike|Node\Attribute $node, string $argumentName): ?string
77+
{
78+
foreach ($node->args as $arg) {
79+
if ($arg->name?->toString() === $argumentName) {
80+
return $this->getStringValue($arg->value);
81+
}
82+
}
83+
84+
return null;
85+
}
86+
87+
private function getStringValue(Node $node): ?string
88+
{
89+
if ($node instanceof Node\Scalar\String_) {
90+
return $node->value;
91+
}
92+
93+
if ($node instanceof Node\Expr\BinaryOp\Concat) {
94+
$left = $this->getStringValue($node->left);
95+
if (null === $left) {
96+
return null;
97+
}
98+
99+
$right = $this->getStringValue($node->right);
100+
if (null === $right) {
101+
return null;
102+
}
103+
104+
return $left.$right;
105+
}
106+
107+
if ($node instanceof Node\Expr\Assign) {
108+
if ($node->expr instanceof Node\Scalar\String_) {
109+
return $node->expr->value;
110+
}
111+
}
112+
113+
return null;
114+
}
115+
}

0 commit comments

Comments
 (0)