15
15
use App \Helpers \Swagger \AnnotationHelper ;
16
16
use App \Helpers \Swagger \ParenthesesBuilder ;
17
17
use App \V1Module \Presenters \BasePresenter ;
18
+ use ReflectionMethod ;
18
19
19
20
class AnnotationToAttributeConverter
20
21
{
@@ -100,6 +101,13 @@ private static function convertStandardRegexCapturesToDictionary(array $captures
100
101
return $ annotationParameters ;
101
102
}
102
103
104
+ /**
105
+ * Convers an associative array into an attribute string builder.
106
+ * @param array $annotationParameters An associative array with a subset of the following keys:
107
+ * type, name, validation, description, required, nullable.
108
+ * @throws \App\Exceptions\InternalServerException
109
+ * @return ParenthesesBuilder A string builder used to build the final attribute string.
110
+ */
103
111
private static function convertRegexCapturesToParenthesesBuilder (array $ annotationParameters )
104
112
{
105
113
// serialize the parameters to an attribute
@@ -120,6 +128,9 @@ private static function convertRegexCapturesToParenthesesBuilder(array $annotati
120
128
case "query " :
121
129
$ type = $ paramTypeClass . "::Query " ;
122
130
break ;
131
+ case "path " :
132
+ $ type = $ paramTypeClass . "::Path " ;
133
+ break ;
123
134
default :
124
135
throw new InternalServerException ("Unknown request type: $ typeStr " );
125
136
}
@@ -250,6 +261,7 @@ private static function convertAnnotationValidationToValidatorString(string $val
250
261
$ validatorClass = VEmail::class;
251
262
break ;
252
263
case "numericint " :
264
+ case "integer " :
253
265
$ validatorClass = VInt::class;
254
266
break ;
255
267
case "bool " :
@@ -305,47 +317,159 @@ private static function preprocessFile(string $path)
305
317
// join with presenter name from the file
306
318
$ className = $ namespace . "\\" . basename ($ path , ".php " );
307
319
320
+ // get endpoint metadata for this file
308
321
$ endpoints = array_filter (self ::$ routesMetadata , function ($ route ) use ($ className ) {
309
322
return $ route ["class " ] == $ className ;
310
323
});
311
324
325
+ // add info about where the method starts
326
+ foreach ($ endpoints as &$ endpoint ) {
327
+ $ reflectionMethod = new ReflectionMethod ($ endpoint ["class " ], $ endpoint ["method " ]);
328
+ // the method returns the line indexed from 1
329
+ $ endpoint ["startLine " ] = $ reflectionMethod ->getStartLine () - 1 ;
330
+ $ endpoint ["endLine " ] = $ reflectionMethod ->getEndLine () - 1 ;
331
+ }
332
+
333
+ // sort endpoint based on position in the file (so that the file preprocessing can be done top-down)
334
+ $ startLines = array_column ($ endpoints , "startLine " );
335
+ array_multisort ($ startLines , SORT_ASC , $ endpoints );
336
+
337
+ // get file lines
338
+ $ content = file_get_contents ($ path );
339
+ $ lines = self ::fileStringToLines ($ content );
340
+
341
+ // maps certain line indices to replacement annotation blocks and their extends
342
+ $ annotationReplacements = [];
343
+
312
344
foreach ($ endpoints as $ endpoint ) {
345
+ $ class = $ endpoint ["class " ];
346
+ $ method = $ endpoint ["method " ];
347
+ $ route = $ endpoint ["route " ];
348
+ $ startLine = $ endpoint ["startLine " ];
349
+
350
+ // get info about endpoint parameters and their types
313
351
$ annotationData = AnnotationHelper::extractAnnotationData (
314
- $ endpoint [ " class " ] ,
315
- $ endpoint [ " method " ] ,
316
- $ endpoint [ " route " ]
352
+ $ class ,
353
+ $ method ,
354
+ $ route
317
355
);
318
- var_dump ($ annotationData );
356
+
357
+ // find start and end lines of method annotations
358
+ $ annotationEndLine = $ startLine - 1 ;
359
+ $ annotationStartLine = -1 ;
360
+ for ($ i = $ annotationEndLine - 1 ; $ i >= 0 ; $ i --) {
361
+ if (str_contains ($ lines [$ i ], "/** " )) {
362
+ $ annotationStartLine = $ i ;
363
+ break ;
364
+ }
365
+ }
366
+ if ($ annotationStartLine == -1 ) {
367
+ throw new InternalServerException ("Could not find annotation start line " );
368
+ }
369
+
370
+ $ annotationLines = array_slice ($ lines , $ annotationStartLine , $ annotationEndLine - $ annotationStartLine + 1 );
371
+ $ params = $ annotationData ->getAllParams ();
372
+
373
+ /// attempt to remove param lines, but it is too complicated (handle missing param lines + multiline params)
374
+ // foreach ($params as $param) {
375
+ // // matches the line containing the parameter name with word boundaries
376
+ // $paramLineRegex = "/\\$\\b" . $param->name . "\\b/";
377
+ // $lineIdx = -1;
378
+ // for ($i = 0; $i < count($annotationLines); $i++) {
379
+ // if (preg_match($paramLineRegex, $annotationLines[$i]) == 1) {
380
+ // $lineIdx = $i;
381
+ // break;
382
+ // }
383
+ // }
384
+ // }
385
+
386
+ // crate an attribute from each parameter
387
+ foreach ($ params as $ param ) {
388
+ $ data = [
389
+ "name " => $ param ->name ,
390
+ "validation " => $ param ->swaggerType ,
391
+ "type " => $ param ->location ,
392
+ "required " => ($ param ->required ? "true " : "false " ),
393
+ "nullable " => ($ param ->nullable ? "true " : "false " ),
394
+ ];
395
+ if ($ param ->description != null ) {
396
+ $ data ["description " ] = $ param ->description ;
397
+ }
398
+
399
+ $ builder = self ::convertRegexCapturesToParenthesesBuilder ($ data );
400
+ $ paramAttributeClass = self ::shortenClass (Param::class);
401
+ $ attributeLine = " #[ {$ paramAttributeClass }{$ builder ->toString ()}] " ;
402
+ // change to multiline if the line is too long
403
+ if (strlen ($ attributeLine ) > 120 ) {
404
+ $ attributeLine = " #[ {$ paramAttributeClass }{$ builder ->toMultilineString (4 )}] " ;
405
+ }
406
+
407
+ // append the attribute line to the existing annotations
408
+ $ annotationLines [] = $ attributeLine ;
409
+ }
410
+
411
+ $ annotationReplacements [$ annotationStartLine ] = [
412
+ "annotations " => $ annotationLines ,
413
+ "originalAnnotationEndLine " => $ annotationEndLine ,
414
+ ];
319
415
}
320
416
417
+ $ newLines = [];
418
+ for ($ i = 0 ; $ i < count ($ lines ); $ i ++) {
419
+ // copy non-annotation lines
420
+ if (!array_key_exists ($ i , $ annotationReplacements )) {
421
+ $ newLines [] = $ lines [$ i ];
422
+ continue ;
423
+ }
424
+
425
+ // add new annotations
426
+ foreach ($ annotationReplacements [$ i ]["annotations " ] as $ replacementLine ) {
427
+ $ newLines [] = $ replacementLine ;
428
+ }
429
+ // move $i to the original annotation end line
430
+ $ i = $ annotationReplacements [$ i ]["originalAnnotationEndLine " ];
431
+ }
432
+
433
+ return self ::linesToFileString ($ newLines );
434
+ }
435
+
436
+ private static function fileStringToLines (string $ fileContent ): array
437
+ {
438
+ $ lines = preg_split ("/(( \r? \n)|( \r\n?))/ " , $ fileContent );
439
+ if ($ lines == false ) {
440
+ throw new InternalServerException ("File content cannot be split into lines " );
441
+ }
442
+ return $ lines ;
443
+ }
444
+
445
+ private static function linesToFileString (array $ lines ): string
446
+ {
447
+ return implode ("\n" , $ lines );
321
448
}
322
449
323
450
public static function convertFile (string $ path )
324
451
{
325
- self ::preprocessFile ($ path );
326
- return ;
327
- // read file and replace @Param annotations with attributes
328
- $ content = file_get_contents ($ path );
452
+ $ content = self ::preprocessFile ($ path );
329
453
// Array that contains parentheses builders of all future generated attributes.
330
454
// Filled dynamically with the preg_replace_callback callback.
331
455
$ standardCapturesList = [];
332
456
$ netteCapturesList = [];
457
+ // $withInterleavedAttributes = preg_replace_callback(
458
+ // self::$standardRegex,
459
+ // function ($matches) use (&$standardCapturesList) {
460
+ // return self::standardRegexCaptureToAttributeCallback($matches, $standardCapturesList);
461
+ // },
462
+ // $content,
463
+ // flags: PREG_UNMATCHED_AS_NULL
464
+ // );
333
465
$ withInterleavedAttributes = preg_replace_callback (
334
- self ::$ standardRegex ,
335
- function ($ matches ) use (&$ standardCapturesList ) {
336
- return self ::standardRegexCaptureToAttributeCallback ($ matches , $ standardCapturesList );
466
+ self ::$ netteRegex ,
467
+ function ($ matches ) use (&$ netteCapturesList ) {
468
+ return self ::netteRegexCaptureToAttributeCallback ($ matches , $ netteCapturesList );
337
469
},
338
470
$ content ,
339
471
flags: PREG_UNMATCHED_AS_NULL
340
472
);
341
- // $withInterleavedAttributes = preg_replace_callback(
342
- // self::$netteRegex,
343
- // function ($matches) use (&$netteCapturesList) {
344
- // return self::netteRegexCaptureToAttributeCallback($matches, $netteCapturesList);
345
- // },
346
- // $withInterleavedAttributes,
347
- // flags: PREG_UNMATCHED_AS_NULL
348
- // );
349
473
350
474
// move the attribute lines below the comment block
351
475
$ lines = [];
@@ -354,7 +478,7 @@ function ($matches) use (&$standardCapturesList) {
354
478
$ usingsAdded = false ;
355
479
$ paramAttributeClass = self ::shortenClass (Param::class);
356
480
$ paramTypeClass = self ::shortenClass (Type::class);
357
- foreach (preg_split ( " /(( \r ? \n )|( \r\n ?))/ " , $ withInterleavedAttributes ) as $ line ) {
481
+ foreach (self :: fileStringToLines ( $ withInterleavedAttributes ) as $ line ) {
358
482
// detected the initial "use" block, add usings for new types
359
483
if (!$ usingsAdded && strlen ($ line ) > 3 && substr ($ line , 0 , 3 ) === "use " ) {
360
484
$ lines [] = "use App \\Helpers \\MetaFormats \\Attributes \\{$ paramAttributeClass }; " ;
@@ -397,6 +521,6 @@ function ($matches) use (&$standardCapturesList) {
397
521
}
398
522
}
399
523
400
- return implode ( "\n" , $ lines );
524
+ return self :: linesToFileString ( $ lines );
401
525
}
402
526
}
0 commit comments