vendor/api-platform/core/src/Metadata/Resource/Factory/PhpDocResourceMetadataCollectionFactory.php line 57

  1. <?php
  2. /*
  3.  * This file is part of the API Platform project.
  4.  *
  5.  * (c) Kévin Dunglas <dunglas@gmail.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. declare(strict_types=1);
  11. namespace ApiPlatform\Metadata\Resource\Factory;
  12. use ApiPlatform\Metadata\Operations;
  13. use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
  14. use phpDocumentor\Reflection\DocBlockFactory;
  15. use phpDocumentor\Reflection\DocBlockFactoryInterface;
  16. use phpDocumentor\Reflection\Types\ContextFactory;
  17. use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
  18. use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
  19. use PHPStan\PhpDocParser\Lexer\Lexer;
  20. use PHPStan\PhpDocParser\Parser\ConstExprParser;
  21. use PHPStan\PhpDocParser\Parser\PhpDocParser;
  22. use PHPStan\PhpDocParser\Parser\TokenIterator;
  23. use PHPStan\PhpDocParser\Parser\TypeParser;
  24. /**
  25.  * Extracts descriptions from PHPDoc.
  26.  *
  27.  * @author Kévin Dunglas <dunglas@gmail.com>
  28.  */
  29. final class PhpDocResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface
  30. {
  31.     private readonly ?DocBlockFactoryInterface $docBlockFactory;
  32.     private readonly ?ContextFactory $contextFactory;
  33.     private readonly ?PhpDocParser $phpDocParser;
  34.     private readonly ?Lexer $lexer;
  35.     /** @var array<string, PhpDocNode> */
  36.     private array $docBlocks = [];
  37.     public function __construct(private readonly ResourceMetadataCollectionFactoryInterface $decorated, ?DocBlockFactoryInterface $docBlockFactory null)
  38.     {
  39.         $contextFactory null;
  40.         if ($docBlockFactory instanceof DocBlockFactoryInterface) {
  41.             trigger_deprecation('api-platform/core''3.1''Using a 2nd argument to PhpDocResourceMetadataCollectionFactory is deprecated.');
  42.         }
  43.         if (class_exists(DocBlockFactory::class) && class_exists(ContextFactory::class)) {
  44.             $docBlockFactory $docBlockFactory ?? DocBlockFactory::createInstance();
  45.             $contextFactory = new ContextFactory();
  46.         }
  47.         $this->docBlockFactory $docBlockFactory;
  48.         $this->contextFactory $contextFactory;
  49.         if (class_exists(DocBlockFactory::class) && !class_exists(PhpDocParser::class)) {
  50.             trigger_deprecation('api-platform/core''3.1''Using phpdocumentor/reflection-docblock is deprecated. Require phpstan/phpdoc-parser instead.');
  51.         }
  52.         $phpDocParser null;
  53.         $lexer null;
  54.         if (class_exists(PhpDocParser::class)) {
  55.             $phpDocParser = new PhpDocParser(new TypeParser(new ConstExprParser()), new ConstExprParser());
  56.             $lexer = new Lexer();
  57.         }
  58.         $this->phpDocParser $phpDocParser;
  59.         $this->lexer $lexer;
  60.     }
  61.     /**
  62.      * {@inheritdoc}
  63.      */
  64.     public function create(string $resourceClass): ResourceMetadataCollection
  65.     {
  66.         $resourceMetadataCollection $this->decorated->create($resourceClass);
  67.         foreach ($resourceMetadataCollection as $key => $resourceMetadata) {
  68.             if (null !== $resourceMetadata->getDescription()) {
  69.                 continue;
  70.             }
  71.             $description null;
  72.             // Deprecated path. To remove in API Platform 4.
  73.             if (!$this->phpDocParser instanceof PhpDocParser && $this->docBlockFactory instanceof DocBlockFactoryInterface && $this->contextFactory) {
  74.                 $reflectionClass = new \ReflectionClass($resourceClass);
  75.                 try {
  76.                     $docBlock $this->docBlockFactory->create($reflectionClass$this->contextFactory->createFromReflector($reflectionClass));
  77.                     $description $docBlock->getSummary();
  78.                 } catch (\InvalidArgumentException) {
  79.                     // Ignore empty DocBlocks
  80.                 }
  81.             } else {
  82.                 $description $this->getShortDescription($resourceClass);
  83.             }
  84.             if (!$description) {
  85.                 return $resourceMetadataCollection;
  86.             }
  87.             $resourceMetadataCollection[$key] = $resourceMetadata->withDescription($description);
  88.             $operations $resourceMetadata->getOperations() ?? new Operations();
  89.             foreach ($operations as $operationName => $operation) {
  90.                 if (null !== $operation->getDescription()) {
  91.                     continue;
  92.                 }
  93.                 $operations->add($operationName$operation->withDescription($description));
  94.             }
  95.             $resourceMetadataCollection[$key] = $resourceMetadataCollection[$key]->withOperations($operations);
  96.             if (!$resourceMetadata->getGraphQlOperations()) {
  97.                 continue;
  98.             }
  99.             foreach ($graphQlOperations $resourceMetadata->getGraphQlOperations() as $operationName => $operation) {
  100.                 if (null !== $operation->getDescription()) {
  101.                     continue;
  102.                 }
  103.                 $graphQlOperations[$operationName] = $operation->withDescription($description);
  104.             }
  105.             $resourceMetadataCollection[$key] = $resourceMetadataCollection[$key]->withGraphQlOperations($graphQlOperations);
  106.         }
  107.         return $resourceMetadataCollection;
  108.     }
  109.     /**
  110.      * Gets the short description of the class.
  111.      */
  112.     private function getShortDescription(string $class): ?string
  113.     {
  114.         if (!$docBlock $this->getDocBlock($class)) {
  115.             return null;
  116.         }
  117.         foreach ($docBlock->children as $docChild) {
  118.             if ($docChild instanceof PhpDocTextNode && !empty($docChild->text)) {
  119.                 return $docChild->text;
  120.             }
  121.         }
  122.         return null;
  123.     }
  124.     private function getDocBlock(string $class): ?PhpDocNode
  125.     {
  126.         if (isset($this->docBlocks[$class])) {
  127.             return $this->docBlocks[$class];
  128.         }
  129.         try {
  130.             $reflectionClass = new \ReflectionClass($class);
  131.         } catch (\ReflectionException) {
  132.             return null;
  133.         }
  134.         $rawDocNode $reflectionClass->getDocComment();
  135.         if (!$rawDocNode) {
  136.             return null;
  137.         }
  138.         $tokens = new TokenIterator($this->lexer->tokenize($rawDocNode));
  139.         $phpDocNode $this->phpDocParser->parse($tokens);
  140.         $tokens->consumeTokenType(Lexer::TOKEN_END);
  141.         return $this->docBlocks[$class] = $phpDocNode;
  142.     }
  143. }