18
18
use phpDocumentor \Reflection \DocBlockFactory ;
19
19
use phpDocumentor \Reflection \DocBlockFactoryInterface ;
20
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 ;
21
28
22
29
/**
23
30
* Extracts descriptions from PHPDoc.
26
33
*/
27
34
final class PhpDocResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface
28
35
{
29
- private readonly DocBlockFactoryInterface $ docBlockFactory ;
30
- private readonly ContextFactory $ contextFactory ;
36
+ private readonly ?DocBlockFactoryInterface $ docBlockFactory ;
37
+ private readonly ?ContextFactory $ contextFactory ;
38
+ private readonly ?PhpDocParser $ phpDocParser ;
39
+ private readonly ?Lexer $ lexer ;
31
40
32
- public function __construct (private readonly ResourceMetadataCollectionFactoryInterface $ decorated , DocBlockFactoryInterface $ docBlockFactory = null )
41
+ /** @var array<string, PhpDocNode> */
42
+ private array $ docBlocks = [];
43
+
44
+ public function __construct (private readonly ResourceMetadataCollectionFactoryInterface $ decorated , ?DocBlockFactoryInterface $ docBlockFactory = null )
33
45
{
34
- $ this ->docBlockFactory = $ docBlockFactory ?: DocBlockFactory::createInstance ();
35
- $ this ->contextFactory = new ContextFactory ();
46
+ $ contextFactory = null ;
47
+ if ($ docBlockFactory instanceof DocBlockFactoryInterface) {
48
+ trigger_deprecation ('api-platform/core ' , '3.1 ' , 'Using a 2nd argument to PhpDocResourceMetadataCollectionFactory is deprecated. ' );
49
+ }
50
+ if (class_exists (DocBlockFactory::class) && class_exists (ContextFactory::class)) {
51
+ $ docBlockFactory = $ docBlockFactory ?? DocBlockFactory::createInstance ();
52
+ $ contextFactory = new ContextFactory ();
53
+ }
54
+ $ this ->docBlockFactory = $ docBlockFactory ;
55
+ $ this ->contextFactory = $ contextFactory ;
56
+ if (class_exists (DocBlockFactory::class) && !class_exists (PhpDocParser::class)) {
57
+ trigger_deprecation ('api-platform/core ' , '3.1 ' , 'Using phpdocumentor/reflection-docblock is deprecated. Require phpstan/phpdoc-parser instead. ' );
58
+ }
59
+ if (class_exists (PhpDocParser::class)) {
60
+ $ this ->phpDocParser = new PhpDocParser (new TypeParser (new ConstExprParser ()), new ConstExprParser ());
61
+ $ this ->lexer = new Lexer ();
62
+ }
36
63
}
37
64
38
65
/**
@@ -47,41 +74,97 @@ public function create(string $resourceClass): ResourceMetadataCollection
47
74
continue ;
48
75
}
49
76
50
- $ reflectionClass = new \ ReflectionClass ( $ resourceClass ) ;
77
+ $ description = null ;
51
78
52
- try {
53
- $ docBlock = $ this ->docBlockFactory -> create ( $ reflectionClass , $ this ->contextFactory -> createFromReflector ( $ reflectionClass ));
54
- $ resourceMetadataCollection [ $ key ] = $ resourceMetadata -> withDescription ( $ docBlock -> getSummary () );
79
+ // Deprecated path. To remove in API Platform 4.
80
+ if (! $ this -> phpDocParser instanceof PhpDocParser && $ this ->docBlockFactory instanceof DocBlockFactoryInterface && $ this ->contextFactory ) {
81
+ $ reflectionClass = new \ ReflectionClass ( $ resourceClass );
55
82
56
- $ operations = $ resourceMetadata ->getOperations () ?? new Operations ();
57
- foreach ($ operations as $ operationName => $ operation ) {
58
- if (null !== $ operation ->getDescription ()) {
59
- continue ;
60
- }
61
-
62
- $ operations ->add ($ operationName , $ operation ->withDescription ($ docBlock ->getSummary ()));
83
+ try {
84
+ $ docBlock = $ this ->docBlockFactory ->create ($ reflectionClass , $ this ->contextFactory ->createFromReflector ($ reflectionClass ));
85
+ $ description = $ docBlock ->getSummary ();
86
+ } catch (\InvalidArgumentException ) {
87
+ // Ignore empty DocBlocks
63
88
}
89
+ } else {
90
+ $ description = $ this ->getShortDescription ($ resourceClass );
91
+ }
64
92
65
- $ resourceMetadataCollection [$ key ] = $ resourceMetadataCollection [$ key ]->withOperations ($ operations );
93
+ if (!$ description ) {
94
+ return $ resourceMetadataCollection ;
95
+ }
66
96
67
- if (!$ resourceMetadata ->getGraphQlOperations ()) {
97
+ $ resourceMetadataCollection [$ key ] = $ resourceMetadata ->withDescription ($ description );
98
+
99
+ $ operations = $ resourceMetadata ->getOperations () ?? new Operations ();
100
+ foreach ($ operations as $ operationName => $ operation ) {
101
+ if (null !== $ operation ->getDescription ()) {
68
102
continue ;
69
103
}
70
104
71
- foreach ($ graphQlOperations = $ resourceMetadata ->getGraphQlOperations () as $ operationName => $ operation ) {
72
- if (null !== $ operation ->getDescription ()) {
73
- continue ;
74
- }
105
+ $ operations ->add ($ operationName , $ operation ->withDescription ($ description ));
106
+ }
75
107
76
- $ graphQlOperations [$ operationName ] = $ operation ->withDescription ($ docBlock ->getSummary ());
108
+ $ resourceMetadataCollection [$ key ] = $ resourceMetadataCollection [$ key ]->withOperations ($ operations );
109
+
110
+ if (!$ resourceMetadata ->getGraphQlOperations ()) {
111
+ continue ;
112
+ }
113
+
114
+ foreach ($ graphQlOperations = $ resourceMetadata ->getGraphQlOperations () as $ operationName => $ operation ) {
115
+ if (null !== $ operation ->getDescription ()) {
116
+ continue ;
77
117
}
78
118
79
- $ resourceMetadataCollection [$ key ] = $ resourceMetadataCollection [$ key ]->withGraphQlOperations ($ graphQlOperations );
80
- } catch (\InvalidArgumentException ) {
81
- // Ignore empty DocBlocks
119
+ $ graphQlOperations [$ operationName ] = $ operation ->withDescription ($ description );
82
120
}
121
+
122
+ $ resourceMetadataCollection [$ key ] = $ resourceMetadataCollection [$ key ]->withGraphQlOperations ($ graphQlOperations );
83
123
}
84
124
85
125
return $ resourceMetadataCollection ;
86
126
}
127
+
128
+ /**
129
+ * Gets the short description of the class.
130
+ */
131
+ private function getShortDescription (string $ class ): ?string
132
+ {
133
+ if (!$ docBlock = $ this ->getDocBlock ($ class )) {
134
+ return null ;
135
+ }
136
+
137
+ foreach ($ docBlock ->children as $ docChild ) {
138
+ if ($ docChild instanceof PhpDocTextNode && !empty ($ docChild ->text )) {
139
+ return $ docChild ->text ;
140
+ }
141
+ }
142
+
143
+ return null ;
144
+ }
145
+
146
+ private function getDocBlock (string $ class ): ?PhpDocNode
147
+ {
148
+ if (isset ($ this ->docBlocks [$ class ])) {
149
+ return $ this ->docBlocks [$ class ];
150
+ }
151
+
152
+ try {
153
+ $ reflectionClass = new \ReflectionClass ($ class );
154
+ } catch (\ReflectionException ) {
155
+ return null ;
156
+ }
157
+
158
+ $ rawDocNode = $ reflectionClass ->getDocComment ();
159
+
160
+ if (!$ rawDocNode ) {
161
+ return null ;
162
+ }
163
+
164
+ $ tokens = new TokenIterator ($ this ->lexer ->tokenize ($ rawDocNode ));
165
+ $ phpDocNode = $ this ->phpDocParser ->parse ($ tokens );
166
+ $ tokens ->consumeTokenType (Lexer::TOKEN_END );
167
+
168
+ return $ this ->docBlocks [$ class ] = $ phpDocNode ;
169
+ }
87
170
}
0 commit comments