PhpAstExtractor.php 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  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. foreach ($this->visitors as $visitor) {
  46. $visitor->initialize($catalogue, $file, $this->prefix);
  47. $traverser->addVisitor($visitor);
  48. }
  49. $nodes = $this->parser->parse(file_get_contents($file));
  50. $traverser->traverse($nodes);
  51. }
  52. }
  53. public function setPrefix(string $prefix): void
  54. {
  55. $this->prefix = $prefix;
  56. }
  57. protected function canBeExtracted(string $file): bool
  58. {
  59. return 'php' === pathinfo($file, \PATHINFO_EXTENSION)
  60. && $this->isFile($file)
  61. && preg_match('/\bt\(|->trans\(|TranslatableMessage|Symfony\\\\Component\\\\Validator\\\\Constraints/i', file_get_contents($file));
  62. }
  63. protected function extractFromDirectory(array|string $resource): iterable|Finder
  64. {
  65. if (!class_exists(Finder::class)) {
  66. throw new \LogicException(\sprintf('You cannot use "%s" as the "symfony/finder" package is not installed. Try running "composer require symfony/finder".', static::class));
  67. }
  68. return (new Finder())->files()->name('*.php')->in($resource);
  69. }
  70. }