vendor/symfony/translation/Extractor/PhpExtractor.php line 14

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\Translation\Extractor;
  11. trigger_deprecation('symfony/translation''6.2''"%s" is deprecated, use "%s" instead.'PhpExtractor::class, PhpAstExtractor::class);
  12. use Symfony\Component\Finder\Finder;
  13. use Symfony\Component\Translation\MessageCatalogue;
  14. /**
  15.  * PhpExtractor extracts translation messages from a PHP template.
  16.  *
  17.  * @author Michel Salib <michelsalib@hotmail.com>
  18.  *
  19.  * @deprecated since Symfony 6.2, use the PhpAstExtractor instead
  20.  */
  21. class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
  22. {
  23.     public const MESSAGE_TOKEN 300;
  24.     public const METHOD_ARGUMENTS_TOKEN 1000;
  25.     public const DOMAIN_TOKEN 1001;
  26.     /**
  27.      * Prefix for new found message.
  28.      */
  29.     private string $prefix '';
  30.     /**
  31.      * The sequence that captures translation messages.
  32.      */
  33.     protected $sequences = [
  34.         [
  35.             '->',
  36.             'trans',
  37.             '(',
  38.             self::MESSAGE_TOKEN,
  39.             ',',
  40.             self::METHOD_ARGUMENTS_TOKEN,
  41.             ',',
  42.             self::DOMAIN_TOKEN,
  43.         ],
  44.         [
  45.             '->',
  46.             'trans',
  47.             '(',
  48.             self::MESSAGE_TOKEN,
  49.         ],
  50.         [
  51.             'new',
  52.             'TranslatableMessage',
  53.             '(',
  54.             self::MESSAGE_TOKEN,
  55.             ',',
  56.             self::METHOD_ARGUMENTS_TOKEN,
  57.             ',',
  58.             self::DOMAIN_TOKEN,
  59.         ],
  60.         [
  61.             'new',
  62.             'TranslatableMessage',
  63.             '(',
  64.             self::MESSAGE_TOKEN,
  65.         ],
  66.         [
  67.             'new',
  68.             '\\',
  69.             'Symfony',
  70.             '\\',
  71.             'Component',
  72.             '\\',
  73.             'Translation',
  74.             '\\',
  75.             'TranslatableMessage',
  76.             '(',
  77.             self::MESSAGE_TOKEN,
  78.             ',',
  79.             self::METHOD_ARGUMENTS_TOKEN,
  80.             ',',
  81.             self::DOMAIN_TOKEN,
  82.         ],
  83.         [
  84.             'new',
  85.             '\Symfony\Component\Translation\TranslatableMessage',
  86.             '(',
  87.             self::MESSAGE_TOKEN,
  88.             ',',
  89.             self::METHOD_ARGUMENTS_TOKEN,
  90.             ',',
  91.             self::DOMAIN_TOKEN,
  92.         ],
  93.         [
  94.             'new',
  95.             '\\',
  96.             'Symfony',
  97.             '\\',
  98.             'Component',
  99.             '\\',
  100.             'Translation',
  101.             '\\',
  102.             'TranslatableMessage',
  103.             '(',
  104.             self::MESSAGE_TOKEN,
  105.         ],
  106.         [
  107.             'new',
  108.             '\Symfony\Component\Translation\TranslatableMessage',
  109.             '(',
  110.             self::MESSAGE_TOKEN,
  111.         ],
  112.         [
  113.             't',
  114.             '(',
  115.             self::MESSAGE_TOKEN,
  116.             ',',
  117.             self::METHOD_ARGUMENTS_TOKEN,
  118.             ',',
  119.             self::DOMAIN_TOKEN,
  120.         ],
  121.         [
  122.             't',
  123.             '(',
  124.             self::MESSAGE_TOKEN,
  125.         ],
  126.     ];
  127.     public function extract(string|iterable $resourceMessageCatalogue $catalog)
  128.     {
  129.         $files $this->extractFiles($resource);
  130.         foreach ($files as $file) {
  131.             $this->parseTokens(token_get_all(file_get_contents($file)), $catalog$file);
  132.             gc_mem_caches();
  133.         }
  134.     }
  135.     public function setPrefix(string $prefix)
  136.     {
  137.         $this->prefix $prefix;
  138.     }
  139.     /**
  140.      * Normalizes a token.
  141.      */
  142.     protected function normalizeToken(mixed $token): ?string
  143.     {
  144.         if (isset($token[1]) && 'b"' !== $token) {
  145.             return $token[1];
  146.         }
  147.         return $token;
  148.     }
  149.     /**
  150.      * Seeks to a non-whitespace token.
  151.      */
  152.     private function seekToNextRelevantToken(\Iterator $tokenIterator)
  153.     {
  154.         for (; $tokenIterator->valid(); $tokenIterator->next()) {
  155.             $t $tokenIterator->current();
  156.             if (\T_WHITESPACE !== $t[0]) {
  157.                 break;
  158.             }
  159.         }
  160.     }
  161.     private function skipMethodArgument(\Iterator $tokenIterator)
  162.     {
  163.         $openBraces 0;
  164.         for (; $tokenIterator->valid(); $tokenIterator->next()) {
  165.             $t $tokenIterator->current();
  166.             if ('[' === $t[0] || '(' === $t[0]) {
  167.                 ++$openBraces;
  168.             }
  169.             if (']' === $t[0] || ')' === $t[0]) {
  170.                 --$openBraces;
  171.             }
  172.             if ((=== $openBraces && ',' === $t[0]) || (-=== $openBraces && ')' === $t[0])) {
  173.                 break;
  174.             }
  175.         }
  176.     }
  177.     /**
  178.      * Extracts the message from the iterator while the tokens
  179.      * match allowed message tokens.
  180.      */
  181.     private function getValue(\Iterator $tokenIterator)
  182.     {
  183.         $message '';
  184.         $docToken '';
  185.         $docPart '';
  186.         for (; $tokenIterator->valid(); $tokenIterator->next()) {
  187.             $t $tokenIterator->current();
  188.             if ('.' === $t) {
  189.                 // Concatenate with next token
  190.                 continue;
  191.             }
  192.             if (!isset($t[1])) {
  193.                 break;
  194.             }
  195.             switch ($t[0]) {
  196.                 case \T_START_HEREDOC:
  197.                     $docToken $t[1];
  198.                     break;
  199.                 case \T_ENCAPSED_AND_WHITESPACE:
  200.                 case \T_CONSTANT_ENCAPSED_STRING:
  201.                     if ('' === $docToken) {
  202.                         $message .= PhpStringTokenParser::parse($t[1]);
  203.                     } else {
  204.                         $docPart $t[1];
  205.                     }
  206.                     break;
  207.                 case \T_END_HEREDOC:
  208.                     if ($indentation strspn($t[1], ' ')) {
  209.                         $docPartWithLineBreaks $docPart;
  210.                         $docPart '';
  211.                         foreach (preg_split('~(\r\n|\n|\r)~'$docPartWithLineBreaks, -1\PREG_SPLIT_DELIM_CAPTURE) as $str) {
  212.                             if (\in_array($str, ["\r\n""\n""\r"], true)) {
  213.                                 $docPart .= $str;
  214.                             } else {
  215.                                 $docPart .= substr($str$indentation);
  216.                             }
  217.                         }
  218.                     }
  219.                     $message .= PhpStringTokenParser::parseDocString($docToken$docPart);
  220.                     $docToken '';
  221.                     $docPart '';
  222.                     break;
  223.                 case \T_WHITESPACE:
  224.                     break;
  225.                 default:
  226.                     break 2;
  227.             }
  228.         }
  229.         return $message;
  230.     }
  231.     /**
  232.      * Extracts trans message from PHP tokens.
  233.      */
  234.     protected function parseTokens(array $tokensMessageCatalogue $catalogstring $filename)
  235.     {
  236.         $tokenIterator = new \ArrayIterator($tokens);
  237.         for ($key 0$key $tokenIterator->count(); ++$key) {
  238.             foreach ($this->sequences as $sequence) {
  239.                 $message '';
  240.                 $domain 'messages';
  241.                 $tokenIterator->seek($key);
  242.                 foreach ($sequence as $sequenceKey => $item) {
  243.                     $this->seekToNextRelevantToken($tokenIterator);
  244.                     if ($this->normalizeToken($tokenIterator->current()) === $item) {
  245.                         $tokenIterator->next();
  246.                         continue;
  247.                     } elseif (self::MESSAGE_TOKEN === $item) {
  248.                         $message $this->getValue($tokenIterator);
  249.                         if (\count($sequence) === ($sequenceKey 1)) {
  250.                             break;
  251.                         }
  252.                     } elseif (self::METHOD_ARGUMENTS_TOKEN === $item) {
  253.                         $this->skipMethodArgument($tokenIterator);
  254.                     } elseif (self::DOMAIN_TOKEN === $item) {
  255.                         $domainToken $this->getValue($tokenIterator);
  256.                         if ('' !== $domainToken) {
  257.                             $domain $domainToken;
  258.                         }
  259.                         break;
  260.                     } else {
  261.                         break;
  262.                     }
  263.                 }
  264.                 if ($message) {
  265.                     $catalog->set($message$this->prefix.$message$domain);
  266.                     $metadata $catalog->getMetadata($message$domain) ?? [];
  267.                     $normalizedFilename preg_replace('{[\\\\/]+}''/'$filename);
  268.                     $metadata['sources'][] = $normalizedFilename.':'.$tokens[$key][2];
  269.                     $catalog->setMetadata($message$metadata$domain);
  270.                     break;
  271.                 }
  272.             }
  273.         }
  274.     }
  275.     /**
  276.      * @throws \InvalidArgumentException
  277.      */
  278.     protected function canBeExtracted(string $file): bool
  279.     {
  280.         return $this->isFile($file) && 'php' === pathinfo($file\PATHINFO_EXTENSION);
  281.     }
  282.     protected function extractFromDirectory(string|array $directory): iterable
  283.     {
  284.         if (!class_exists(Finder::class)) {
  285.             throw new \LogicException(sprintf('You cannot use "%s" as the "symfony/finder" package is not installed. Try running "composer require symfony/finder".', static::class));
  286.         }
  287.         $finder = new Finder();
  288.         return $finder->files()->name('*.php')->in($directory);
  289.     }
  290. }