2021-08-27 06:46:27 -04:00
< ? php
/*
* This file is part of the Symfony package .
*
* ( c ) Fabien Potencier < fabien @ symfony . com >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
namespace Symfony\Component\Translation\Extractor ;
2023-02-24 06:26:40 -05:00
trigger_deprecation ( 'symfony/translation' , '6.2' , '"%s" is deprecated, use "%s" instead.' , PhpExtractor :: class , PhpAstExtractor :: class );
2021-08-27 06:46:27 -04:00
use Symfony\Component\Finder\Finder ;
use Symfony\Component\Translation\MessageCatalogue ;
/**
* PhpExtractor extracts translation messages from a PHP template .
*
* @ author Michel Salib < michelsalib @ hotmail . com >
2023-02-24 06:26:40 -05:00
*
* @ deprecated since Symfony 6.2 , use the PhpAstExtractor instead
2021-08-27 06:46:27 -04:00
*/
class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
{
public const MESSAGE_TOKEN = 300 ;
public const METHOD_ARGUMENTS_TOKEN = 1000 ;
public const DOMAIN_TOKEN = 1001 ;
/**
* Prefix for new found message .
*/
2022-03-14 16:22:30 -04:00
private string $prefix = '' ;
2021-08-27 06:46:27 -04:00
/**
* The sequence that captures translation messages .
*/
protected $sequences = [
[
'->' ,
'trans' ,
'(' ,
self :: MESSAGE_TOKEN ,
',' ,
self :: METHOD_ARGUMENTS_TOKEN ,
',' ,
self :: DOMAIN_TOKEN ,
],
[
'->' ,
'trans' ,
'(' ,
self :: MESSAGE_TOKEN ,
],
[
'new' ,
'TranslatableMessage' ,
'(' ,
self :: MESSAGE_TOKEN ,
',' ,
self :: METHOD_ARGUMENTS_TOKEN ,
',' ,
self :: DOMAIN_TOKEN ,
],
[
'new' ,
'TranslatableMessage' ,
'(' ,
self :: MESSAGE_TOKEN ,
],
[
'new' ,
'\\' ,
'Symfony' ,
'\\' ,
'Component' ,
'\\' ,
'Translation' ,
'\\' ,
'TranslatableMessage' ,
'(' ,
self :: MESSAGE_TOKEN ,
',' ,
self :: METHOD_ARGUMENTS_TOKEN ,
',' ,
self :: DOMAIN_TOKEN ,
],
[
'new' ,
'\Symfony\Component\Translation\TranslatableMessage' ,
'(' ,
self :: MESSAGE_TOKEN ,
',' ,
self :: METHOD_ARGUMENTS_TOKEN ,
',' ,
self :: DOMAIN_TOKEN ,
],
[
'new' ,
'\\' ,
'Symfony' ,
'\\' ,
'Component' ,
'\\' ,
'Translation' ,
'\\' ,
'TranslatableMessage' ,
'(' ,
self :: MESSAGE_TOKEN ,
],
[
'new' ,
'\Symfony\Component\Translation\TranslatableMessage' ,
'(' ,
self :: MESSAGE_TOKEN ,
],
[
't' ,
'(' ,
self :: MESSAGE_TOKEN ,
',' ,
self :: METHOD_ARGUMENTS_TOKEN ,
',' ,
self :: DOMAIN_TOKEN ,
],
[
't' ,
'(' ,
self :: MESSAGE_TOKEN ,
],
];
2022-03-14 16:22:30 -04:00
public function extract ( string | iterable $resource , MessageCatalogue $catalog )
2021-08-27 06:46:27 -04:00
{
$files = $this -> extractFiles ( $resource );
foreach ( $files as $file ) {
$this -> parseTokens ( token_get_all ( file_get_contents ( $file )), $catalog , $file );
gc_mem_caches ();
}
}
public function setPrefix ( string $prefix )
{
$this -> prefix = $prefix ;
}
/**
* Normalizes a token .
*/
2022-03-14 16:22:30 -04:00
protected function normalizeToken ( mixed $token ) : ? string
2021-08-27 06:46:27 -04:00
{
if ( isset ( $token [ 1 ]) && 'b"' !== $token ) {
return $token [ 1 ];
}
return $token ;
}
/**
* Seeks to a non - whitespace token .
*/
private function seekToNextRelevantToken ( \Iterator $tokenIterator )
{
for (; $tokenIterator -> valid (); $tokenIterator -> next ()) {
$t = $tokenIterator -> current ();
if ( \T_WHITESPACE !== $t [ 0 ]) {
break ;
}
}
}
private function skipMethodArgument ( \Iterator $tokenIterator )
{
$openBraces = 0 ;
for (; $tokenIterator -> valid (); $tokenIterator -> next ()) {
$t = $tokenIterator -> current ();
if ( '[' === $t [ 0 ] || '(' === $t [ 0 ]) {
++ $openBraces ;
}
if ( ']' === $t [ 0 ] || ')' === $t [ 0 ]) {
-- $openBraces ;
}
if (( 0 === $openBraces && ',' === $t [ 0 ]) || ( - 1 === $openBraces && ')' === $t [ 0 ])) {
break ;
}
}
}
/**
* Extracts the message from the iterator while the tokens
* match allowed message tokens .
*/
private function getValue ( \Iterator $tokenIterator )
{
$message = '' ;
$docToken = '' ;
$docPart = '' ;
for (; $tokenIterator -> valid (); $tokenIterator -> next ()) {
$t = $tokenIterator -> current ();
if ( '.' === $t ) {
// Concatenate with next token
continue ;
}
if ( ! isset ( $t [ 1 ])) {
break ;
}
switch ( $t [ 0 ]) {
case \T_START_HEREDOC :
$docToken = $t [ 1 ];
break ;
case \T_ENCAPSED_AND_WHITESPACE :
case \T_CONSTANT_ENCAPSED_STRING :
if ( '' === $docToken ) {
$message .= PhpStringTokenParser :: parse ( $t [ 1 ]);
} else {
$docPart = $t [ 1 ];
}
break ;
case \T_END_HEREDOC :
if ( $indentation = strspn ( $t [ 1 ], ' ' )) {
$docPartWithLineBreaks = $docPart ;
$docPart = '' ;
foreach ( preg_split ( '~(\r\n|\n|\r)~' , $docPartWithLineBreaks , - 1 , \PREG_SPLIT_DELIM_CAPTURE ) as $str ) {
if ( \in_array ( $str , [ " \r \n " , " \n " , " \r " ], true )) {
$docPart .= $str ;
} else {
$docPart .= substr ( $str , $indentation );
}
}
}
$message .= PhpStringTokenParser :: parseDocString ( $docToken , $docPart );
$docToken = '' ;
$docPart = '' ;
break ;
case \T_WHITESPACE :
break ;
default :
break 2 ;
}
}
return $message ;
}
/**
* Extracts trans message from PHP tokens .
*/
protected function parseTokens ( array $tokens , MessageCatalogue $catalog , string $filename )
{
$tokenIterator = new \ArrayIterator ( $tokens );
for ( $key = 0 ; $key < $tokenIterator -> count (); ++ $key ) {
foreach ( $this -> sequences as $sequence ) {
$message = '' ;
$domain = 'messages' ;
$tokenIterator -> seek ( $key );
foreach ( $sequence as $sequenceKey => $item ) {
$this -> seekToNextRelevantToken ( $tokenIterator );
if ( $this -> normalizeToken ( $tokenIterator -> current ()) === $item ) {
$tokenIterator -> next ();
continue ;
} elseif ( self :: MESSAGE_TOKEN === $item ) {
$message = $this -> getValue ( $tokenIterator );
if ( \count ( $sequence ) === ( $sequenceKey + 1 )) {
break ;
}
} elseif ( self :: METHOD_ARGUMENTS_TOKEN === $item ) {
$this -> skipMethodArgument ( $tokenIterator );
} elseif ( self :: DOMAIN_TOKEN === $item ) {
$domainToken = $this -> getValue ( $tokenIterator );
if ( '' !== $domainToken ) {
$domain = $domainToken ;
}
break ;
} else {
break ;
}
}
if ( $message ) {
$catalog -> set ( $message , $this -> prefix . $message , $domain );
$metadata = $catalog -> getMetadata ( $message , $domain ) ? ? [];
$normalizedFilename = preg_replace ( '{[\\\\/]+}' , '/' , $filename );
$metadata [ 'sources' ][] = $normalizedFilename . ':' . $tokens [ $key ][ 2 ];
$catalog -> setMetadata ( $message , $metadata , $domain );
break ;
}
}
}
}
/**
* @ throws \InvalidArgumentException
*/
2022-03-14 16:22:30 -04:00
protected function canBeExtracted ( string $file ) : bool
2021-08-27 06:46:27 -04:00
{
return $this -> isFile ( $file ) && 'php' === pathinfo ( $file , \PATHINFO_EXTENSION );
}
2022-03-14 16:22:30 -04:00
protected function extractFromDirectory ( string | array $directory ) : iterable
2021-08-27 06:46:27 -04:00
{
2022-03-14 16:22:30 -04:00
if ( ! class_exists ( Finder :: class )) {
throw new \LogicException ( sprintf ( 'You cannot use "%s" as the "symfony/finder" package is not installed. Try running "composer require symfony/finder".' , static :: class ));
}
2021-08-27 06:46:27 -04:00
$finder = new Finder ();
return $finder -> files () -> name ( '*.php' ) -> in ( $directory );
}
}