PhpAstExtractor.php 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  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. use PhpParser\NodeTraverser;
  12. use PhpParser\NodeVisitor;
  13. use PhpParser\Parser;
  14. use PhpParser\ParserFactory;
  15. use Symfony\Component\Finder\Finder;
  16. use Symfony\Component\Translation\Extractor\Visitor\AbstractVisitor;
  17. use Symfony\Component\Translation\MessageCatalogue;
  18. /**
  19. * PhpAstExtractor extracts translation messages from a PHP AST.
  20. *
  21. * @author Mathieu Santostefano <msantostefano@protonmail.com>
  22. */
  23. final class PhpAstExtractor extends AbstractFileExtractor implements ExtractorInterface
  24. {
  25. private Parser $parser;
  26. public function __construct(
  27. /**
  28. * @param iterable<AbstractVisitor&NodeVisitor> $visitors
  29. */
  30. private readonly iterable $visitors,
  31. private string $prefix = '',
  32. ) {
  33. if (!class_exists(ParserFactory::class)) {
  34. throw new \LogicException(sprintf('You cannot use "%s" as the "nikic/php-parser" package is not installed. Try running "composer require nikic/php-parser".', static::class));
  35. }
  36. $this->parser = (new ParserFactory())->createForHostVersion();
  37. }
  38. public function extract(iterable|string $resource, MessageCatalogue $catalogue): void
  39. {
  40. foreach ($this->extractFiles($resource) as $file) {
  41. $traverser = new NodeTraverser();
  42. // This is needed to resolve namespaces in class methods/constants.
  43. $nameResolver = new NodeVisitor\NameResolver();
  44. $traverser->addVisitor($nameResolver);
  45. /** @var AbstractVisitor&NodeVisitor $visitor */
  46. foreach ($this->visitors as $visitor) {
  47. $visitor->initialize($catalogue, $file, $this->prefix);
  48. $traverser->addVisitor($visitor);
  49. }
  50. $nodes = $this->parser->parse(file_get_contents($file));
  51. $traverser->traverse($nodes);
  52. }
  53. }
  54. public function setPrefix(string $prefix): void
  55. {
  56. $this->prefix = $prefix;
  57. }
  58. protected function canBeExtracted(string $file): bool
  59. {
  60. return 'php' === pathinfo($file, \PATHINFO_EXTENSION)
  61. && $this->isFile($file)
  62. && preg_match('/\bt\(|->trans\(|TranslatableMessage|Symfony\\\\Component\\\\Validator\\\\Constraints/i', file_get_contents($file));
  63. }
  64. protected function extractFromDirectory(array|string $resource): iterable|Finder
  65. {
  66. if (!class_exists(Finder::class)) {
  67. throw new \LogicException(sprintf('You cannot use "%s" as the "symfony/finder" package is not installed. Try running "composer require symfony/finder".', static::class));
  68. }
  69. return (new Finder())->files()->name('*.php')->in($resource);
  70. }
  71. }