Skip to content

Commit 4dcc5c1

Browse files
committed
WIP swagger:annotate can now extract annotations from all routed methods
1 parent 0ce4b0e commit 4dcc5c1

File tree

1 file changed

+98
-35
lines changed

1 file changed

+98
-35
lines changed

app/commands/SwaggerAnnotator.php

Lines changed: 98 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
use App\Model\Repository\AssignmentSolutions;
77
use App\Model\Entity\Group;
88
use App\Model\Entity\User;
9+
use App\V1Module\Router\MethodRoute;
10+
use Nette\Routing\RouteList;
911
use Symfony\Component\Console\Command\Command;
1012
use Symfony\Component\Console\Input\InputArgument;
1113
use Symfony\Component\Console\Input\InputInterface;
@@ -26,12 +28,85 @@ protected function configure(): void
2628

2729
protected function execute(InputInterface $input, OutputInterface $output): int
2830
{
29-
$r = new AnnotationHelper('App\V1Module\Presenters\UsersPresenter');
30-
$data = $r->extractMethodData('actionUpdateUiData');
31-
var_dump($data);
31+
$namespacePrefix = 'App\V1Module\Presenters\\';
32+
33+
$routes = $this->getRoutes();
34+
foreach ($routes as $route) {
35+
$metadata = $this->extractMetadata($route);
36+
$route = $this->extractRoute($route);
37+
38+
$className = $namespacePrefix . $metadata['class'];
39+
$annotationData = AnnotationHelper::extractAnnotationData($className, $metadata['method']);
40+
}
3241

3342
return Command::SUCCESS;
3443
}
44+
45+
function getRoutes(): array {
46+
$router = \App\V1Module\RouterFactory::createRouter();
47+
48+
# find all route object using a queue
49+
$queue = [$router];
50+
$routes = [];
51+
while (count($queue) != 0) {
52+
$cursor = array_shift($queue);
53+
54+
if ($cursor instanceof RouteList) {
55+
foreach ($cursor->getRouters() as $item) {
56+
# lists contain routes or nested lists
57+
if ($item instanceof RouteList) {
58+
array_push($queue, $item);
59+
}
60+
else {
61+
# the first route is special and holds no useful information for annotation
62+
if (get_parent_class($item) !== MethodRoute::class)
63+
continue;
64+
65+
$routes[] = $this->getPropertyValue($item, "route");
66+
}
67+
}
68+
}
69+
}
70+
71+
return $routes;
72+
}
73+
74+
private function extractRoute($routeObj) {
75+
$mask = self::getPropertyValue($routeObj, "mask");
76+
return $mask;
77+
}
78+
79+
private function extractMetadata($routeObj) {
80+
$metadata = self::getPropertyValue($routeObj, "metadata");
81+
$presenter = $metadata["presenter"]["value"];
82+
$action = $metadata["action"]["value"];
83+
84+
# if the name is empty, the method will be called 'actionDefault'
85+
if ($action === null)
86+
$action = "default";
87+
88+
return [
89+
"class" => $presenter . "Presenter",
90+
"method" => "action" . ucfirst($action),
91+
];
92+
}
93+
94+
private static function getPropertyValue($object, string $propertyName): mixed
95+
{
96+
$class = new \ReflectionClass($object);
97+
98+
do {
99+
try {
100+
$property = $class->getProperty($propertyName);
101+
} catch (\ReflectionException $exception) {
102+
$class = $class->getParentClass();
103+
$property = null;
104+
}
105+
} while ($property === null && $class !== null);
106+
107+
$property->setAccessible(true);
108+
return $property->getValue($object);
109+
}
35110
}
36111

37112
enum HttpMethods: string {
@@ -58,14 +133,14 @@ public function __construct(
58133
}
59134

60135
class AnnotationParameterData {
61-
public string $dataType;
136+
public string|null $dataType;
62137
public string $name;
63-
public string $description;
138+
public string|null $description;
64139

65140
public function __construct(
66-
string $dataType,
141+
string|null $dataType,
67142
string $name,
68-
string $description
143+
string|null $description
69144
) {
70145
$this->dataType = $dataType;
71146
$this->name = $name;
@@ -74,25 +149,12 @@ public function __construct(
74149
}
75150

76151
class AnnotationHelper {
77-
private string $className;
78-
private \ReflectionClass $class;
79-
80-
/**
81-
* Constructor
82-
* @param string $className Name of the class.
83-
*/
84-
public function __construct(
85-
string $className
86-
) {
87-
$this->className = $className;
88-
$this->class = new \ReflectionClass($this->className);
152+
private static function getMethod(string $className, string $methodName): \ReflectionMethod {
153+
$class = new \ReflectionClass($className);
154+
return $class->getMethod($methodName);
89155
}
90156

91-
public function getMethod(string $methodName): \ReflectionMethod {
92-
return $this->class->getMethod($methodName);
93-
}
94-
95-
function extractAnnotationHttpMethod(array $annotations): HttpMethods|null {
157+
private static function extractAnnotationHttpMethod(array $annotations): HttpMethods|null {
96158
# get string values of backed enumeration
97159
$cases = HttpMethods::cases();
98160
$methods = [];
@@ -110,7 +172,7 @@ function extractAnnotationHttpMethod(array $annotations): HttpMethods|null {
110172
return null;
111173
}
112174

113-
function extractAnnotationQueryParams(array $annotations): array {
175+
private static function extractAnnotationQueryParams(array $annotations): array {
114176
$queryParams = [];
115177
foreach ($annotations as $annotation) {
116178
# assumed that all query parameters have a @param annotation
@@ -128,7 +190,7 @@ function extractAnnotationQueryParams(array $annotations): array {
128190
return $queryParams;
129191
}
130192

131-
function extractBodyParams(array $expressions): array {
193+
private static function extractBodyParams(array $expressions): array {
132194
$dict = [];
133195
#sample: [ name="uiData", validation="array|null" ]
134196
foreach ($expressions as $expression) {
@@ -141,7 +203,7 @@ function extractBodyParams(array $expressions): array {
141203
return $dict;
142204
}
143205

144-
function extractAnnotationBodyParams(array $annotations): array {
206+
private static function extractAnnotationBodyParams(array $annotations): array {
145207
$bodyParams = [];
146208
$prefix = "@Param";
147209
foreach ($annotations as $annotation) {
@@ -151,7 +213,7 @@ function extractAnnotationBodyParams(array $annotations): array {
151213
# remove '@Param(' from the start and ')' from the end
152214
$body = substr($annotation, strlen($prefix) + 1, -1);
153215
$tokens = explode(", ", $body);
154-
$values = $this->extractBodyParams($tokens);
216+
$values = self::extractBodyParams($tokens);
155217
$descriptor = new AnnotationParameterData($values["validation"],
156218
$values["name"], $values["description"]);
157219
$bodyParams[] = $descriptor;
@@ -160,8 +222,8 @@ function extractAnnotationBodyParams(array $annotations): array {
160222
return $bodyParams;
161223
}
162224

163-
function getMethodAnnotations(string $methodName): array {
164-
$annotations = $this->getMethod($methodName)->getDocComment();
225+
private static function getMethodAnnotations(string $className, string $methodName): array {
226+
$annotations = self::getMethod($className, $methodName)->getDocComment();
165227
$lines = preg_split("/\r\n|\n|\r/", $annotations);
166228

167229
# trims whitespace and asterisks
@@ -196,11 +258,12 @@ function getMethodAnnotations(string $methodName): array {
196258
return $merged;
197259
}
198260

199-
public function extractMethodData($methodName): AnnotationData {
200-
$methodAnnotations = $this->getMethodAnnotations($methodName);
201-
$httpMethod = $this->extractAnnotationHttpMethod($methodAnnotations);
202-
$queryParams = $this->extractAnnotationQueryParams($methodAnnotations);
203-
$bodyParams = $this->extractAnnotationBodyParams($methodAnnotations);
261+
public static function extractAnnotationData(string $className, string $methodName): AnnotationData {
262+
$methodAnnotations = self::getMethodAnnotations($className, $methodName);
263+
264+
$httpMethod = self::extractAnnotationHttpMethod($methodAnnotations);
265+
$queryParams = self::extractAnnotationQueryParams($methodAnnotations);
266+
$bodyParams = self::extractAnnotationBodyParams($methodAnnotations);
204267
$data = new AnnotationData($httpMethod, $queryParams, $bodyParams);
205268
return $data;
206269
}

0 commit comments

Comments
 (0)