1818class SwaggerAnnotator extends Command
1919{
2020 protected static $ defaultName = 'swagger:annotate ' ;
21+ private static $ presenterNamespace = 'App\V1Module\Presenters \\' ;
22+ private static $ autogeneratedAnnotationFilePath = 'app/V1Module/presenters/annotations.php ' ;
2123
2224 protected function configure (): void
2325 {
@@ -28,26 +30,33 @@ protected function configure(): void
2830
2931 protected function execute (InputInterface $ input , OutputInterface $ output ): int
3032 {
31- $ namespacePrefix = 'App\V1Module\Presenters \\' ;
33+ # create a temporary file containing transpiled annotations usable by the external library (Swagger-PHP)
34+ $ fileBuilder = new FileBuilder (self ::$ autogeneratedAnnotationFilePath );
35+ $ fileBuilder ->startClass ('__Autogenerated_Annotation_Controller__ ' , '1.0 ' , 'ReCodEx API ' );
3236
33- $ fileBuilder = new FileBuilder ("app/V1Module/presenters/annotations.php " );
34- $ fileBuilder ->startClass ("AnnotationController " );
37+ # get all routes of the api
3538 $ routes = $ this ->getRoutes ();
36- foreach ($ routes as $ route ) {
37- $ metadata = $ this ->extractMetadata ($ route );
38- $ route = $ this ->extractRoute ($ route );
39+ foreach ($ routes as $ routeObj ) {
40+ # extract class and method names of the endpoint
41+ $ metadata = $ this ->extractMetadata ($ routeObj );
42+ $ route = $ this ->extractRoute ($ routeObj );
43+ $ className = self ::$ presenterNamespace . $ metadata ['class ' ];
3944
40- $ className = $ namespacePrefix . $ metadata [ ' class ' ];
45+ # extract data from the existing annotations
4146 $ annotationData = AnnotationHelper::extractAnnotationData ($ className , $ metadata ['method ' ], $ route );
4247
48+ # add an empty method to the file with the transpiled annotations
4349 $ fileBuilder ->addAnnotatedMethod ($ metadata ['method ' ], $ annotationData ->toSwaggerAnnotations ($ route ));
4450 }
4551 $ fileBuilder ->endClass ();
4652
47-
4853 return Command::SUCCESS ;
4954 }
5055
56+ /**
57+ * Finds all route objects of the API
58+ * @return array Returns an array of all found route objects.
59+ */
5160 function getRoutes (): array {
5261 $ router = \App \V1Module \RouterFactory::createRouter ();
5362
@@ -77,14 +86,23 @@ function getRoutes(): array {
7786 return $ routes ;
7887 }
7988
80- private function extractRoute ($ routeObj ) {
89+ /**
90+ * Extracts the route string from a route object. Replaces '<..>' in the route with '{...}'.
91+ * @param mixed $routeObj
92+ */
93+ private function extractRoute ($ routeObj ): string {
8194 $ mask = self ::getPropertyValue ($ routeObj , "mask " );
8295
8396 # sample: replaces '/users/<id>' with '/users/{id}'
8497 $ mask = str_replace (["< " , "> " ], ["{ " , "} " ], $ mask );
8598 return "/ " . $ mask ;
8699 }
87100
101+ /**
102+ * Extracts the class and method names of the endpoint handler.
103+ * @param mixed $routeObj The route object representing the endpoint.
104+ * @return string[] Returns a dictionary [ "class" => ..., "method" => ...]
105+ */
88106 private function extractMetadata ($ routeObj ) {
89107 $ metadata = self ::getPropertyValue ($ routeObj , "metadata " );
90108 $ presenter = $ metadata ["presenter " ]["value " ];
@@ -100,6 +118,13 @@ private function extractMetadata($routeObj) {
100118 ];
101119 }
102120
121+ /**
122+ * Helper function that can extract a property value from an arbitrary object where
123+ * the property can be private.
124+ * @param mixed $object The object to extract from.
125+ * @param string $propertyName The name of the property.
126+ * @return mixed Returns the value of the property.
127+ */
103128 private static function getPropertyValue ($ object , string $ propertyName ): mixed
104129 {
105130 $ class = new \ReflectionClass ($ object );
@@ -118,6 +143,9 @@ private static function getPropertyValue($object, string $propertyName): mixed
118143 }
119144}
120145
146+ /**
147+ * Builder class that handles .php file creation.
148+ */
121149class FileBuilder {
122150 private $ file ;
123151 private $ methodEntries ;
@@ -132,16 +160,16 @@ public function __construct(
132160 private function initFile (string $ filename ) {
133161 $ this ->file = fopen ($ filename , "w " );
134162 fwrite ($ this ->file , "<?php \n" );
163+ fwrite ($ this ->file , "/// THIS FILE WAS AUTOGENERATED \n" );
135164 fwrite ($ this ->file , "namespace App\V1Module\Presenters; \n" );
136165 fwrite ($ this ->file , "use OpenApi\Annotations as OA; \n" );
137166 }
138167
139- ///TODO: hardcoded info
140- private function createInfoAnnotation () {
168+ private function createInfoAnnotation (string $ version , string $ title ) {
141169 $ head = "@OA \\Info " ;
142170 $ body = new ParenthesesBuilder ();
143- $ body ->addKeyValue ("version " , " 1.0 " );
144- $ body ->addKeyValue ("title " , " ReCodEx API " );
171+ $ body ->addKeyValue ("version " , $ version );
172+ $ body ->addKeyValue ("title " , $ title );
145173 return $ head . $ body ->toString ();
146174 }
147175
@@ -151,9 +179,8 @@ private function writeAnnotationLineWithComments(string $annotationLine) {
151179 fwrite ($ this ->file , "*/ \n" );
152180 }
153181
154- public function startClass (string $ className ) {
155- ///TODO: hardcoded
156- $ this ->writeAnnotationLineWithComments ($ this ->createInfoAnnotation ());
182+ public function startClass (string $ className , string $ version , string $ title ) {
183+ $ this ->writeAnnotationLineWithComments ($ this ->createInfoAnnotation ($ version , $ title ));
157184 fwrite ($ this ->file , "class {$ className } { \n" );
158185 }
159186
@@ -217,6 +244,11 @@ private function getBodyAnnotation(): string|null {
217244 return $ head . $ body ->toString () . ")) " ;
218245 }
219246
247+ /**
248+ * Converts the extracted annotation data to a string parsable by the Swagger-PHP library.
249+ * @param string $route The route of the handler this set of data represents.
250+ * @return string Returns the transpiled annotations on a single line.
251+ */
220252 public function toSwaggerAnnotations (string $ route ) {
221253 $ httpMethodAnnotation = $ this ->getHttpMethodAnnotation ();
222254 $ body = new ParenthesesBuilder ();
@@ -239,6 +271,9 @@ public function toSwaggerAnnotations(string $route) {
239271 }
240272}
241273
274+ /**
275+ * Builder class that can create strings of the schema: '(key1="value1", key2="value2", standalone1, standalone2, ...)'
276+ */
242277class ParenthesesBuilder {
243278 private array $ tokens ;
244279
@@ -269,6 +304,9 @@ public function toString(): string {
269304 }
270305}
271306
307+ /**
308+ * Contains data of a single annotation parameter.
309+ */
272310class AnnotationParameterData {
273311 public string |null $ dataType ;
274312 public string $ name ;
@@ -353,7 +391,6 @@ private function generateSchemaAnnotation(): string {
353391
354392 /**
355393 * Converts the object to a @OA\Parameter(...) annotation string
356- * @param string $parameterLocation Where the parameter resides. Can be 'path', 'query', 'header' or 'cookie'.
357394 */
358395 public function toParameterAnnotation (): string {
359396 $ head = "@OA \\Parameter " ;
@@ -381,6 +418,9 @@ public function toPropertyAnnotation(): string {
381418 }
382419}
383420
421+ /**
422+ * Parser that can parse the annotations of existing recodex endpoints
423+ */
384424class AnnotationHelper {
385425 private static function getMethod (string $ className , string $ methodName ): \ReflectionMethod {
386426 $ class = new \ReflectionClass ($ className );
0 commit comments