• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 15133993414

20 May 2025 09:30AM UTC coverage: 26.313% (-1.2%) from 27.493%
15133993414

Pull #7161

github

web-flow
Merge e2c03d45f into 5459ba375
Pull Request #7161: fix(metadata): infer parameter string type from schema

0 of 2 new or added lines in 1 file covered. (0.0%)

11019 existing lines in 363 files now uncovered.

12898 of 49018 relevant lines covered (26.31%)

34.33 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.09
/src/Metadata/Resource/Factory/PhpDocResourceMetadataCollectionFactory.php
1
<?php
2

3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <dunglas@gmail.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11

12
declare(strict_types=1);
13

14
namespace ApiPlatform\Metadata\Resource\Factory;
15

16
use ApiPlatform\Metadata\Operations;
17
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
18
use phpDocumentor\Reflection\DocBlockFactory;
19
use phpDocumentor\Reflection\DocBlockFactoryInterface;
20
use phpDocumentor\Reflection\Types\ContextFactory;
21
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
22
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
23
use PHPStan\PhpDocParser\Lexer\Lexer;
24
use PHPStan\PhpDocParser\Parser\ConstExprParser;
25
use PHPStan\PhpDocParser\Parser\PhpDocParser;
26
use PHPStan\PhpDocParser\Parser\TokenIterator;
27
use PHPStan\PhpDocParser\Parser\TypeParser;
28
use PHPStan\PhpDocParser\ParserConfig;
29

30
/**
31
 * Extracts descriptions from PHPDoc.
32
 *
33
 * @author Kévin Dunglas <dunglas@gmail.com>
34
 */
35
final class PhpDocResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface
36
{
37
    private readonly ?DocBlockFactoryInterface $docBlockFactory;
38
    private readonly ?ContextFactory $contextFactory;
39
    private readonly ?PhpDocParser $phpDocParser;
40
    private readonly ?Lexer $lexer;
41

42
    /** @var array<string, PhpDocNode> */
43
    private array $docBlocks = [];
44

45
    public function __construct(private readonly ResourceMetadataCollectionFactoryInterface $decorated, ?DocBlockFactoryInterface $docBlockFactory = null)
46
    {
UNCOV
47
        $contextFactory = null;
978✔
UNCOV
48
        if ($docBlockFactory instanceof DocBlockFactoryInterface) {
978✔
49
            trigger_deprecation('api-platform/core', '3.1', 'Using a 2nd argument to PhpDocResourceMetadataCollectionFactory is deprecated.');
×
50
        }
UNCOV
51
        if (class_exists(DocBlockFactory::class) && class_exists(ContextFactory::class)) {
978✔
UNCOV
52
            $docBlockFactory = $docBlockFactory ?? DocBlockFactory::createInstance();
978✔
UNCOV
53
            $contextFactory = new ContextFactory();
978✔
54
        }
UNCOV
55
        $this->docBlockFactory = $docBlockFactory;
978✔
UNCOV
56
        $this->contextFactory = $contextFactory;
978✔
UNCOV
57
        if (class_exists(DocBlockFactory::class) && !class_exists(PhpDocParser::class)) {
978✔
58
            trigger_deprecation('api-platform/core', '3.1', 'Using phpdocumentor/reflection-docblock is deprecated. Require phpstan/phpdoc-parser instead.');
×
59
        }
UNCOV
60
        $phpDocParser = null;
978✔
UNCOV
61
        $lexer = null;
978✔
UNCOV
62
        if (class_exists(PhpDocParser::class) && class_exists(ParserConfig::class)) {
978✔
UNCOV
63
            $config = new ParserConfig([]);
978✔
UNCOV
64
            $phpDocParser = new PhpDocParser($config, new TypeParser($config, new ConstExprParser($config)), new ConstExprParser($config));
978✔
UNCOV
65
            $lexer = new Lexer($config);
978✔
66
        } elseif (class_exists(PhpDocParser::class)) {
×
67
            $phpDocParser = new PhpDocParser(new TypeParser(new ConstExprParser()), new ConstExprParser()); // @phpstan-ignore-line
×
68
            $lexer = new Lexer(); // @phpstan-ignore-line
×
69
        }
UNCOV
70
        $this->phpDocParser = $phpDocParser;
978✔
UNCOV
71
        $this->lexer = $lexer;
978✔
72
    }
73

74
    /**
75
     * {@inheritdoc}
76
     */
77
    public function create(string $resourceClass): ResourceMetadataCollection
78
    {
UNCOV
79
        $resourceMetadataCollection = $this->decorated->create($resourceClass);
69✔
80

UNCOV
81
        foreach ($resourceMetadataCollection as $key => $resourceMetadata) {
69✔
UNCOV
82
            if (null !== $resourceMetadata->getDescription()) {
65✔
UNCOV
83
                continue;
5✔
84
            }
85

UNCOV
86
            $description = null;
63✔
87

88
            // Deprecated path. To remove in API Platform 4.
UNCOV
89
            if (!$this->phpDocParser instanceof PhpDocParser && $this->docBlockFactory instanceof DocBlockFactoryInterface && $this->contextFactory) {
63✔
90
                $reflectionClass = new \ReflectionClass($resourceClass);
×
91

92
                try {
93
                    $docBlock = $this->docBlockFactory->create($reflectionClass, $this->contextFactory->createFromReflector($reflectionClass));
×
94
                    $description = $docBlock->getSummary();
×
95
                } catch (\InvalidArgumentException) {
×
96
                    // Ignore empty DocBlocks
97
                }
98
            } else {
UNCOV
99
                $description = $this->getShortDescription($resourceClass);
63✔
100
            }
101

UNCOV
102
            if (!$description) {
63✔
UNCOV
103
                return $resourceMetadataCollection;
50✔
104
            }
105

UNCOV
106
            $resourceMetadataCollection[$key] = $resourceMetadata->withDescription($description);
19✔
107

UNCOV
108
            $operations = $resourceMetadata->getOperations() ?? new Operations();
19✔
UNCOV
109
            foreach ($operations as $operationName => $operation) {
19✔
UNCOV
110
                if (null !== $operation->getDescription()) {
19✔
111
                    continue;
×
112
                }
113

UNCOV
114
                $operations->add($operationName, $operation->withDescription($description));
19✔
115
            }
116

UNCOV
117
            $resourceMetadataCollection[$key] = $resourceMetadataCollection[$key]->withOperations($operations);
19✔
118

UNCOV
119
            if (!$resourceMetadata->getGraphQlOperations()) {
19✔
UNCOV
120
                continue;
10✔
121
            }
122

UNCOV
123
            foreach ($graphQlOperations = $resourceMetadata->getGraphQlOperations() as $operationName => $operation) {
19✔
UNCOV
124
                if (null !== $operation->getDescription()) {
19✔
UNCOV
125
                    continue;
19✔
126
                }
127

UNCOV
128
                $graphQlOperations[$operationName] = $operation->withDescription($description);
19✔
129
            }
130

UNCOV
131
            $resourceMetadataCollection[$key] = $resourceMetadataCollection[$key]->withGraphQlOperations($graphQlOperations);
19✔
132
        }
133

UNCOV
134
        return $resourceMetadataCollection;
27✔
135
    }
136

137
    /**
138
     * Gets the short description of the class.
139
     */
140
    private function getShortDescription(string $class): ?string
141
    {
UNCOV
142
        if (!$docBlock = $this->getDocBlock($class)) {
63✔
UNCOV
143
            return null;
50✔
144
        }
145

UNCOV
146
        foreach ($docBlock->children as $docChild) {
19✔
UNCOV
147
            if ($docChild instanceof PhpDocTextNode && !empty($docChild->text)) {
19✔
UNCOV
148
                return $docChild->text;
19✔
149
            }
150
        }
151

152
        return null;
1✔
153
    }
154

155
    private function getDocBlock(string $class): ?PhpDocNode
156
    {
UNCOV
157
        if (isset($this->docBlocks[$class])) {
63✔
UNCOV
158
            return $this->docBlocks[$class];
10✔
159
        }
160

161
        try {
UNCOV
162
            $reflectionClass = new \ReflectionClass($class);
63✔
163
        } catch (\ReflectionException) {
×
164
            return null;
×
165
        }
166

UNCOV
167
        $rawDocNode = $reflectionClass->getDocComment();
63✔
168

UNCOV
169
        if (!$rawDocNode) {
63✔
UNCOV
170
            return null;
50✔
171
        }
172

UNCOV
173
        $tokens = new TokenIterator($this->lexer->tokenize($rawDocNode));
19✔
UNCOV
174
        $phpDocNode = $this->phpDocParser->parse($tokens);
19✔
UNCOV
175
        $tokens->consumeTokenType(Lexer::TOKEN_END);
19✔
176

UNCOV
177
        return $this->docBlocks[$class] = $phpDocNode;
19✔
178
    }
179
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc