MethodAstParser.php 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. <?php
  2. namespace Knuckles\Scribe\Extracting;
  3. use Exception;
  4. use PhpParser\Node;
  5. use PhpParser\NodeFinder;
  6. use PhpParser\ParserFactory;
  7. use ReflectionFunctionAbstract;
  8. use Throwable;
  9. /**
  10. * MethodAstParser
  11. * Utility class to help with retrieving (and caching) ASTs of route methods.
  12. */
  13. class MethodAstParser
  14. {
  15. protected static array $methodAsts = [];
  16. protected static array $classAsts = [];
  17. public static function getMethodAst(ReflectionFunctionAbstract $method)
  18. {
  19. $methodName = $method->name;
  20. $fileName = $method->getFileName();
  21. $methodAst = self::getCachedMethodAst($fileName, $methodName);
  22. if ($methodAst) {
  23. return $methodAst;
  24. }
  25. $classAst = self::getClassAst($fileName);
  26. $methodAst = self::findMethodInClassAst($classAst, $methodName);
  27. self::cacheMethodAst($fileName, $methodName, $methodAst);
  28. return $methodAst;
  29. }
  30. /**
  31. * @param string $sourceCode
  32. *
  33. * @return \PhpParser\Node\Stmt[]|null
  34. */
  35. protected static function parseClassSourceCode(string $sourceCode): ?array
  36. {
  37. $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
  38. try {
  39. $ast = $parser->parse($sourceCode);
  40. } catch (Throwable $error) {
  41. throw new Exception("Parse error: {$error->getMessage()}");
  42. }
  43. return $ast;
  44. }
  45. /**
  46. * @param \PhpParser\Node\Stmt[] $ast
  47. * @param string $methodName
  48. *
  49. * @return Node|null
  50. */
  51. protected static function findMethodInClassAst(array $ast, string $methodName)
  52. {
  53. $nodeFinder = new NodeFinder;
  54. return $nodeFinder->findFirst($ast, function(Node $node) use ($methodName) {
  55. // Todo handle closures
  56. return $node instanceof Node\Stmt\ClassMethod
  57. && $node->name->toString() === $methodName;
  58. });
  59. }
  60. protected static function getCachedMethodAst(string $fileName, string $methodName)
  61. {
  62. $key = self::getAstCacheId($fileName, $methodName);
  63. return self::$methodAsts[$key] ?? null;
  64. }
  65. protected static function cacheMethodAst(string $fileName, string $methodName, Node $methodAst)
  66. {
  67. $key = self::getAstCacheId($fileName, $methodName);
  68. self::$methodAsts[$key] = $methodAst;
  69. }
  70. private static function getAstCacheId(string $fileName, string $methodName): string
  71. {
  72. return $fileName . "///". $methodName;
  73. }
  74. private static function getClassAst(string $fileName)
  75. {
  76. $classAst = self::$classAsts[$fileName]
  77. ?? self::parseClassSourceCode(file_get_contents($fileName));
  78. return self::$classAsts[$fileName] = $classAst;
  79. }
  80. }