Skip to content

Commit c190b5f

Browse files
committed
added support for @param annotation to attribute conversion
1 parent 1498e76 commit c190b5f

File tree

3 files changed

+151
-21
lines changed

3 files changed

+151
-21
lines changed

app/helpers/MetaFormats/AnnotationToAttributeConverter.php

Lines changed: 145 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use App\Helpers\Swagger\AnnotationHelper;
1616
use App\Helpers\Swagger\ParenthesesBuilder;
1717
use App\V1Module\Presenters\BasePresenter;
18+
use ReflectionMethod;
1819

1920
class AnnotationToAttributeConverter
2021
{
@@ -100,6 +101,13 @@ private static function convertStandardRegexCapturesToDictionary(array $captures
100101
return $annotationParameters;
101102
}
102103

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+
*/
103111
private static function convertRegexCapturesToParenthesesBuilder(array $annotationParameters)
104112
{
105113
// serialize the parameters to an attribute
@@ -120,6 +128,9 @@ private static function convertRegexCapturesToParenthesesBuilder(array $annotati
120128
case "query":
121129
$type = $paramTypeClass . "::Query";
122130
break;
131+
case "path":
132+
$type = $paramTypeClass . "::Path";
133+
break;
123134
default:
124135
throw new InternalServerException("Unknown request type: $typeStr");
125136
}
@@ -250,6 +261,7 @@ private static function convertAnnotationValidationToValidatorString(string $val
250261
$validatorClass = VEmail::class;
251262
break;
252263
case "numericint":
264+
case "integer":
253265
$validatorClass = VInt::class;
254266
break;
255267
case "bool":
@@ -305,47 +317,159 @@ private static function preprocessFile(string $path)
305317
// join with presenter name from the file
306318
$className = $namespace . "\\" . basename($path, ".php");
307319

320+
// get endpoint metadata for this file
308321
$endpoints = array_filter(self::$routesMetadata, function ($route) use ($className) {
309322
return $route["class"] == $className;
310323
});
311324

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+
312344
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
313351
$annotationData = AnnotationHelper::extractAnnotationData(
314-
$endpoint["class"],
315-
$endpoint["method"],
316-
$endpoint["route"]
352+
$class,
353+
$method,
354+
$route
317355
);
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+
];
319415
}
320416

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);
321448
}
322449

323450
public static function convertFile(string $path)
324451
{
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);
329453
// Array that contains parentheses builders of all future generated attributes.
330454
// Filled dynamically with the preg_replace_callback callback.
331455
$standardCapturesList = [];
332456
$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+
// );
333465
$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);
337469
},
338470
$content,
339471
flags: PREG_UNMATCHED_AS_NULL
340472
);
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-
// );
349473

350474
// move the attribute lines below the comment block
351475
$lines = [];
@@ -354,7 +478,7 @@ function ($matches) use (&$standardCapturesList) {
354478
$usingsAdded = false;
355479
$paramAttributeClass = self::shortenClass(Param::class);
356480
$paramTypeClass = self::shortenClass(Type::class);
357-
foreach (preg_split("/((\r?\n)|(\r\n?))/", $withInterleavedAttributes) as $line) {
481+
foreach (self::fileStringToLines($withInterleavedAttributes) as $line) {
358482
// detected the initial "use" block, add usings for new types
359483
if (!$usingsAdded && strlen($line) > 3 && substr($line, 0, 3) === "use") {
360484
$lines[] = "use App\\Helpers\\MetaFormats\\Attributes\\{$paramAttributeClass};";
@@ -397,6 +521,6 @@ function ($matches) use (&$standardCapturesList) {
397521
}
398522
}
399523

400-
return implode("\n", $lines);
524+
return self::linesToFileString($lines);
401525
}
402526
}

app/helpers/MetaFormats/Type.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ enum Type
1010
{
1111
case Post;
1212
case Query;
13+
case Path;
1314
}
1415
// @codingStandardsIgnoreEnd

app/helpers/Swagger/AnnotationData.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ public function __construct(
2828
$this->endpointDescription = $endpointDescription;
2929
}
3030

31+
public function getAllParams(): array
32+
{
33+
return array_merge($this->pathParams, $this->queryParams, $this->bodyParams);
34+
}
35+
3136
/**
3237
* Creates a method annotation string parsable by the swagger generator.
3338
* Example: if the method name is 'Put', the method will return '@OA\\PUT'.

0 commit comments

Comments
 (0)