1616use Piwik \Plugin \Manager ;
1717use Piwik \Validators \BaseValidator ;
1818use Piwik \Validators \NotEmpty ;
19+ use PHPStan \PhpDocParser \Lexer \Lexer ;
20+ use PHPStan \PhpDocParser \Parser \PhpDocParser ;
21+ use PHPStan \PhpDocParser \Parser \TypeParser ;
22+ use PHPStan \PhpDocParser \Parser \ConstExprParser ;
23+ use PHPStan \PhpDocParser \Parser \TokenIterator ;
24+ use function Symfony \Component \String \s ;
1925
2026class AnnotationGenerator
2127{
@@ -69,13 +75,13 @@ public function generatePluginApiAnnotations(string $pluginName)
6975 $ methodName
7076 );
7177
72- $ paramRefs = $ this ->determineParameterReferences ($ rules , $ pluginName , $ methodName , $ reflectionMethod );
78+ $ params = $ this ->determineParameters ($ rules , $ pluginName , $ methodName , $ reflectionMethod );
7379 $ responses = $ this ->determineResponses ($ rules , $ pluginName , $ methodName );
7480
7581 $ isPost = !empty ($ rules ['plugins ' ][$ pluginName ]['methodsRequiringPost ' ])
7682 && in_array ($ methodName , $ rules ['plugins ' ][$ pluginName ]['methodsRequiringPost ' ]);
7783
78- $ annotations [] = $ this ->compileOperationLines ($ path , $ opId , $ pluginName , $ methodName , $ paramRefs , $ responses , $ isPost );
84+ $ annotations [] = $ this ->compileOperationLines ($ path , $ opId , $ pluginName , $ methodName , $ params , $ responses , $ isPost );
7985 }
8086
8187 if (empty ($ annotations )) {
@@ -85,12 +91,32 @@ public function generatePluginApiAnnotations(string $pluginName)
8591 return $ annotations ;
8692 }
8793
94+ function getParamInfoFromDocBlock (string $ docBlock ): array {
95+ $ lexer = new Lexer ();
96+ $ tokens = $ lexer ->tokenize ($ docBlock );
97+ $ expressionParser = new ConstExprParser ();
98+ $ parser = new PhpDocParser (new TypeParser ($ expressionParser ), $ expressionParser );
99+ $ node = $ parser ->parse (new TokenIterator ($ tokens ));
100+
101+ $ params = [];
102+ foreach ($ node ->getParamTagValues () as $ param ) {
103+ $ name = ltrim ($ param ->parameterName , '$ ' );
104+ $ params [$ name ] = [
105+ 'type ' => (string ) $ param ->type ,
106+ 'desc ' => $ param ->description ,
107+ 'byRef ' => $ param ->isReference ,
108+ 'variadic ' => $ param ->isVariadic ,
109+ ];
110+ }
111+ return $ params ;
112+ }
113+
88114 function buildVirtualPath (string $ virtualPathTemplate , string $ plugin , string $ method ): string
89115 {
90116 return str_replace ([ '{plugin} ' , '{method} ' ], [ $ plugin , $ method ], $ virtualPathTemplate );
91117 }
92118
93- function determineParameterReferences (array $ rules , string $ plugin , string $ method , \ReflectionMethod $ rm ): array
119+ function determineParameters (array $ rules , string $ plugin , string $ method , \ReflectionMethod $ reflectionMethod ): array
94120 {
95121 $ refs = [];
96122
@@ -102,33 +128,73 @@ function determineParameterReferences(array $rules, string $plugin, string $meth
102128 $ refs = array_merge ($ refs , $ rules ['plugins ' ][$ plugin ]['paramRefsByMethod ' ][$ method ]);
103129 }
104130
105- return array_values (array_unique ($ refs ));
131+ $ paramsMetadata = Proxy::getInstance ()->getParametersListWithTypes (Request::getClassNameAPI ($ plugin ), $ method );
132+ $ paramsInfo = $ this ->getParamInfoFromDocBlock ($ reflectionMethod ->getDocComment ());
133+
134+ $ customParams = [];
135+ foreach ($ paramsMetadata as $ name => $ paramMetadata ) {
136+ $ paramInfo = $ paramsInfo [$ name ] ?? [];
137+ // Skip references and variadic for now
138+ // TODO - determine whether these can be handled automatically or if they have to be manual
139+ if (!empty ($ paramInfo ['byRef ' ]) || !empty ($ paramInfo ['variadic ' ])) {
140+ continue ;
141+ }
142+
143+ $ type = $ paramMetadata ['type ' ] ?? $ paramInfo ['type ' ] ?? '' ;
144+ // TODO - Properly map the internal types to OpenAPI types
145+ switch (strtolower ($ type )) {
146+ case 'array ' :
147+ $ type = 'array ' ;
148+ break ;
149+ case 'int ' :
150+ $ type = 'integer ' ;
151+ break ;
152+ case 'bool ' :
153+ case 'boolean ' :
154+ $ type = 'boolean ' ;
155+ break ;
156+ default :
157+ $ type = 'string ' ;
158+ }
159+
160+ $ customParams [] = [
161+ 'name ' => $ name ,
162+ 'type ' => $ type ,
163+ 'description ' => $ paramInfo ['desc ' ] ?? '' ,
164+ 'required ' => empty ($ paramMetadata ['allowsNull ' ]) ? 'true ' : 'false ' ,
165+ ];
166+ }
167+
168+ return [
169+ 'refs ' => array_values (array_unique ($ refs )),
170+ 'custom ' => $ customParams ,
171+ ];
106172 }
107173
108174 function determineResponses (array $ rules , string $ plugin , string $ method ): array
109175 {
110- $ out = [];
176+ $ responses = [];
111177
112178 $ successRef = null ;
113179 if (isset ($ rules ['plugins ' ][$ plugin ]['successResponseByMethod ' ][$ method ])) {
114180 $ successRef = $ rules ['plugins ' ][$ plugin ]['successResponseByMethod ' ][$ method ];
115181 }
116182 if ($ successRef ) {
117- $ out [] = [ 'code ' => 200 , 'ref ' => $ successRef ];
183+ $ responses [] = [ 'code ' => 200 , 'ref ' => $ successRef ];
118184 } else {
119- $ out [] = [ 'code ' => 200 , ' desc ' => ' OK ' ];
185+ $ responses [] = [ 'code ' => 200 ];
120186 }
121187
122188 if (!empty ($ rules ['defaultErrorResponseRefs ' ])) {
123- foreach ($ rules ['defaultErrorResponseRefs ' ] as $ err ) {
124- $ out [] = $ err ; // ['code'=>..., 'ref'=>...]
189+ foreach ($ rules ['defaultErrorResponseRefs ' ] as $ errorRef ) {
190+ $ responses [] = $ errorRef ;
125191 }
126192 }
127193
128- return $ out ;
194+ return $ responses ;
129195 }
130196
131- function compileOperationLines (string $ path , string $ opId , string $ plugin , string $ method , array $ paramRefs , array $ responses , bool $ isPost = false ): array
197+ function compileOperationLines (string $ path , string $ opId , string $ plugin , string $ method , array $ params , array $ responses , bool $ isPost = false ): array
132198 {
133199 $ httpMethod = $ isPost ? 'Post ' : 'Get ' ;
134200 $ lines = [];
@@ -137,10 +203,22 @@ function compileOperationLines(string $path, string $opId, string $plugin, strin
137203 $ lines [] = ' operationId=" ' . $ opId . '", ' ;
138204 $ lines [] = ' tags={" ' . $ plugin . '"}, ' ;
139205
140- foreach ($ paramRefs as $ ref ) {
206+ foreach ($ params [ ' refs ' ] ?? [] as $ ref ) {
141207 $ lines [] = ' @OA\Parameter(ref=" ' . $ ref . '"), ' ;
142208 }
143209
210+ foreach ($ params ['custom ' ] ?? [] as $ param ) {
211+ // TODO - Finish implementing this
212+ $ lines [] = ' @OA\Parameter(), ' ;
213+ $ lines [] = ' name=" ' . $ param ['name ' ] . '", ' ;
214+ $ lines [] = ' in="query", ' ;
215+ $ lines [] = ' required=" ' . $ param ['required ' ] . '", ' ;
216+ $ lines [] = ' @OA\Schema( ' ;
217+ $ lines [] = ' type=" ' . $ param ['type ' ] . '", ' ;
218+ $ lines [] = ' ), ' ;
219+ $ lines [] = ' ), ' ;
220+ }
221+
144222 foreach ($ responses as $ response ) {
145223 if (isset ($ response ['ref ' ])) {
146224 $ code = $ response ['code ' ];
0 commit comments