Skip to content

Commit 7e80bae

Browse files
Merge branch '3.4'
* 3.4: Moved PhpExtractor and PhpStringTokenParser to Translation component [Asset] Provide default context [HttpKernel] Deprecate some compiler passes in favor of tagged iterator args Add exclusive Twig namespace for bundles path Share connection factories between cache and lock
2 parents 0257045 + 9d62908 commit 7e80bae

File tree

7 files changed

+544
-0
lines changed

7 files changed

+544
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ CHANGELOG
2020
* Added `TranslationWriterInterface`
2121
* Deprecated `TranslationWriter::writeTranslations` in favor of `TranslationWriter::write`
2222
* added support for adding custom message formatter and decoupling the default one.
23+
* Added `PhpExtractor`
24+
* Added `PhpStringTokenParser`
2325

2426
3.2.0
2527
-----

Extractor/PhpExtractor.php

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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 Symfony\Component\Finder\Finder;
15+
use Symfony\Component\Translation\MessageCatalogue;
16+
17+
/**
18+
* PhpExtractor extracts translation messages from a PHP template.
19+
*
20+
* @author Michel Salib <michelsalib@hotmail.com>
21+
*/
22+
class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
23+
{
24+
const MESSAGE_TOKEN = 300;
25+
const METHOD_ARGUMENTS_TOKEN = 1000;
26+
const DOMAIN_TOKEN = 1001;
27+
28+
/**
29+
* Prefix for new found message.
30+
*
31+
* @var string
32+
*/
33+
private $prefix = '';
34+
35+
/**
36+
* The sequence that captures translation messages.
37+
*
38+
* @var array
39+
*/
40+
protected $sequences = array(
41+
array(
42+
'->',
43+
'trans',
44+
'(',
45+
self::MESSAGE_TOKEN,
46+
',',
47+
self::METHOD_ARGUMENTS_TOKEN,
48+
',',
49+
self::DOMAIN_TOKEN,
50+
),
51+
array(
52+
'->',
53+
'transChoice',
54+
'(',
55+
self::MESSAGE_TOKEN,
56+
',',
57+
self::METHOD_ARGUMENTS_TOKEN,
58+
',',
59+
self::METHOD_ARGUMENTS_TOKEN,
60+
',',
61+
self::DOMAIN_TOKEN,
62+
),
63+
array(
64+
'->',
65+
'trans',
66+
'(',
67+
self::MESSAGE_TOKEN,
68+
),
69+
array(
70+
'->',
71+
'transChoice',
72+
'(',
73+
self::MESSAGE_TOKEN,
74+
),
75+
);
76+
77+
/**
78+
* {@inheritdoc}
79+
*/
80+
public function extract($resource, MessageCatalogue $catalog)
81+
{
82+
$files = $this->extractFiles($resource);
83+
foreach ($files as $file) {
84+
$this->parseTokens(token_get_all(file_get_contents($file)), $catalog);
85+
86+
// PHP 7 memory manager will not release after token_get_all(), see https://bugs.php.net/70098
87+
gc_mem_caches();
88+
}
89+
}
90+
91+
/**
92+
* {@inheritdoc}
93+
*/
94+
public function setPrefix($prefix)
95+
{
96+
$this->prefix = $prefix;
97+
}
98+
99+
/**
100+
* Normalizes a token.
101+
*
102+
* @param mixed $token
103+
*
104+
* @return string
105+
*/
106+
protected function normalizeToken($token)
107+
{
108+
if (isset($token[1]) && 'b"' !== $token) {
109+
return $token[1];
110+
}
111+
112+
return $token;
113+
}
114+
115+
/**
116+
* Seeks to a non-whitespace token.
117+
*/
118+
private function seekToNextRelevantToken(\Iterator $tokenIterator)
119+
{
120+
for (; $tokenIterator->valid(); $tokenIterator->next()) {
121+
$t = $tokenIterator->current();
122+
if (T_WHITESPACE !== $t[0]) {
123+
break;
124+
}
125+
}
126+
}
127+
128+
private function skipMethodArgument(\Iterator $tokenIterator)
129+
{
130+
$openBraces = 0;
131+
132+
for (; $tokenIterator->valid(); $tokenIterator->next()) {
133+
$t = $tokenIterator->current();
134+
135+
if ('[' === $t[0] || '(' === $t[0]) {
136+
++$openBraces;
137+
}
138+
139+
if (']' === $t[0] || ')' === $t[0]) {
140+
--$openBraces;
141+
}
142+
143+
if ((0 === $openBraces && ',' === $t[0]) || (-1 === $openBraces && ')' === $t[0])) {
144+
break;
145+
}
146+
}
147+
}
148+
149+
/**
150+
* Extracts the message from the iterator while the tokens
151+
* match allowed message tokens.
152+
*/
153+
private function getValue(\Iterator $tokenIterator)
154+
{
155+
$message = '';
156+
$docToken = '';
157+
158+
for (; $tokenIterator->valid(); $tokenIterator->next()) {
159+
$t = $tokenIterator->current();
160+
if (!isset($t[1])) {
161+
break;
162+
}
163+
164+
switch ($t[0]) {
165+
case T_START_HEREDOC:
166+
$docToken = $t[1];
167+
break;
168+
case T_ENCAPSED_AND_WHITESPACE:
169+
case T_CONSTANT_ENCAPSED_STRING:
170+
$message .= $t[1];
171+
break;
172+
case T_END_HEREDOC:
173+
return PhpStringTokenParser::parseDocString($docToken, $message);
174+
default:
175+
break 2;
176+
}
177+
}
178+
179+
if ($message) {
180+
$message = PhpStringTokenParser::parse($message);
181+
}
182+
183+
return $message;
184+
}
185+
186+
/**
187+
* Extracts trans message from PHP tokens.
188+
*
189+
* @param array $tokens
190+
* @param MessageCatalogue $catalog
191+
*/
192+
protected function parseTokens($tokens, MessageCatalogue $catalog)
193+
{
194+
$tokenIterator = new \ArrayIterator($tokens);
195+
196+
for ($key = 0; $key < $tokenIterator->count(); ++$key) {
197+
foreach ($this->sequences as $sequence) {
198+
$message = '';
199+
$domain = 'messages';
200+
$tokenIterator->seek($key);
201+
202+
foreach ($sequence as $sequenceKey => $item) {
203+
$this->seekToNextRelevantToken($tokenIterator);
204+
205+
if ($this->normalizeToken($tokenIterator->current()) === $item) {
206+
$tokenIterator->next();
207+
continue;
208+
} elseif (self::MESSAGE_TOKEN === $item) {
209+
$message = $this->getValue($tokenIterator);
210+
211+
if (count($sequence) === ($sequenceKey + 1)) {
212+
break;
213+
}
214+
} elseif (self::METHOD_ARGUMENTS_TOKEN === $item) {
215+
$this->skipMethodArgument($tokenIterator);
216+
} elseif (self::DOMAIN_TOKEN === $item) {
217+
$domain = $this->getValue($tokenIterator);
218+
219+
break;
220+
} else {
221+
break;
222+
}
223+
}
224+
225+
if ($message) {
226+
$catalog->set($message, $this->prefix.$message, $domain);
227+
break;
228+
}
229+
}
230+
}
231+
}
232+
233+
/**
234+
* @param string $file
235+
*
236+
* @return bool
237+
*
238+
* @throws \InvalidArgumentException
239+
*/
240+
protected function canBeExtracted($file)
241+
{
242+
return $this->isFile($file) && 'php' === pathinfo($file, PATHINFO_EXTENSION);
243+
}
244+
245+
/**
246+
* @param string|array $directory
247+
*
248+
* @return array
249+
*/
250+
protected function extractFromDirectory($directory)
251+
{
252+
$finder = new Finder();
253+
254+
return $finder->files()->name('*.php')->in($directory);
255+
}
256+
}

0 commit comments

Comments
 (0)