RouteDocBlocker.php 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. <?php
  2. namespace Mpociot\ApiDoc\Extracting;
  3. use Illuminate\Routing\Route;
  4. use Mpociot\ApiDoc\Tools\Utils;
  5. use Mpociot\Reflection\DocBlock;
  6. use ReflectionClass;
  7. /**
  8. * Class RouteDocBlocker
  9. * Utility class to help with retrieving doc blocks from route classes and methods.
  10. * Also caches them so repeated access is faster.
  11. */
  12. class RouteDocBlocker
  13. {
  14. protected static $docBlocks = [];
  15. /**
  16. * @param Route $route
  17. *
  18. * @throws \ReflectionException
  19. * @throws \Exception
  20. *
  21. * @return array<string, DocBlock> Method and class docblocks
  22. */
  23. public static function getDocBlocksFromRoute(Route $route): array
  24. {
  25. list($className, $methodName) = Utils::getRouteClassAndMethodNames($route);
  26. $normalizedClassName = static::normalizeClassName($className);
  27. $docBlocks = self::getCachedDocBlock($route, $normalizedClassName, $methodName);
  28. if ($docBlocks) {
  29. return $docBlocks;
  30. }
  31. $class = new ReflectionClass($className);
  32. if (! $class->hasMethod($methodName)) {
  33. throw new \Exception("Error while fetching docblock for route: Class $className does not contain method $methodName");
  34. }
  35. $method = Utils::reflectRouteMethod([$className, $methodName]);
  36. $docBlocks = [
  37. 'method' => new DocBlock($method->getDocComment() ?: ''),
  38. 'class' => new DocBlock($class->getDocComment() ?: ''),
  39. ];
  40. self::cacheDocBlocks($route, $normalizedClassName, $methodName, $docBlocks);
  41. return $docBlocks;
  42. }
  43. /**
  44. * @param string|object $classNameOrInstance
  45. *
  46. * @return string
  47. */
  48. protected static function normalizeClassName($classNameOrInstance): string
  49. {
  50. if (is_object($classNameOrInstance)) {
  51. // route handlers are not destroyed until the script
  52. // ends so this should be perfectly safe.
  53. $classNameOrInstance = get_class($classNameOrInstance) . '::' . spl_object_id($classNameOrInstance);
  54. }
  55. return $classNameOrInstance;
  56. }
  57. protected static function getCachedDocBlock(Route $route, string $className, string $methodName)
  58. {
  59. $routeId = self::getRouteCacheId($route, $className, $methodName);
  60. return self::$docBlocks[$routeId] ?? null;
  61. }
  62. protected static function cacheDocBlocks(Route $route, string $className, string $methodName, array $docBlocks)
  63. {
  64. $routeId = self::getRouteCacheId($route, $className, $methodName);
  65. self::$docBlocks[$routeId] = $docBlocks;
  66. }
  67. private static function getRouteCacheId(Route $route, string $className, string $methodName): string
  68. {
  69. return $route->uri()
  70. . ':'
  71. . implode(array_diff($route->methods(), ['HEAD']))
  72. . $className
  73. . $methodName;
  74. }
  75. }