Skip to content

Commit aeeb298

Browse files
committed
made the swagger generation code PSR12 compliant
1 parent 40f262d commit aeeb298

File tree

8 files changed

+485
-387
lines changed

8 files changed

+485
-387
lines changed

app/commands/GenerateSwagger.php

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,10 @@
22

33
namespace App\Console;
44

5-
use App\Helpers\Notifications\ReviewsEmailsSender;
6-
use App\Model\Repository\AssignmentSolutions;
7-
use App\Model\Entity\Group;
8-
use App\Model\Entity\User;
95
use Symfony\Component\Console\Command\Command;
10-
use Symfony\Component\Console\Input\InputArgument;
116
use Symfony\Component\Console\Input\InputInterface;
127
use Symfony\Component\Console\Output\OutputInterface;
13-
use DateTime;
8+
use \OpenApi\Generator;
149

1510
class GenerateSwagger extends Command
1611
{
@@ -26,11 +21,18 @@ protected function configure()
2621
protected function execute(InputInterface $input, OutputInterface $output)
2722
{
2823
$path = __DIR__ . '/../V1Module/presenters/_autogenerated_annotations_temp.php';
29-
$openapi = \OpenApi\Generator::scan([$path]);
24+
25+
// check if file exists
26+
if (!file_exists($path)) {
27+
$output->writeln("Error in GenerateSwagger: Temp annotation file not found.");
28+
return Command::FAILURE;
29+
}
30+
31+
$openapi = Generator::scan([$path]);
3032

3133
$output->writeln($openapi->toYaml());
3234

33-
# delete the temp file
35+
// delete the temp file
3436
unlink($path);
3537

3638
return Command::SUCCESS;

app/commands/SwaggerAnnotator.php

Lines changed: 48 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22

33
namespace App\Console;
44

5-
use App\Helpers\Swagger\FileBuilder;
5+
use App\Helpers\Swagger\TempAnnotationFileBuilder;
66
use App\Helpers\Swagger\AnnotationHelper;
77
use App\V1Module\Router\MethodRoute;
88
use Nette\Routing\RouteList;
99
use Symfony\Component\Console\Command\Command;
1010
use Symfony\Component\Console\Input\InputInterface;
1111
use Symfony\Component\Console\Output\OutputInterface;
12+
use Exception;
13+
use ReflectionException;
14+
use ReflectionClass;
1215

1316
class SwaggerAnnotator extends Command
1417
{
@@ -25,52 +28,59 @@ protected function configure(): void
2528

2629
protected function execute(InputInterface $input, OutputInterface $output): int
2730
{
28-
# create a temporary file containing transpiled annotations usable by the external library (Swagger-PHP)
29-
$fileBuilder = new FileBuilder(self::$autogeneratedAnnotationFilePath);
30-
$fileBuilder->startClass('__Autogenerated_Annotation_Controller__', '1.0', 'ReCodEx API');
31-
32-
# get all routes of the api
33-
$routes = $this->getRoutes();
34-
foreach ($routes as $routeObj) {
35-
# extract class and method names of the endpoint
36-
$metadata = $this->extractMetadata($routeObj);
37-
$route = $this->extractRoute($routeObj);
38-
$className = self::$presenterNamespace . $metadata['class'];
39-
40-
# extract data from the existing annotations
41-
$annotationData = AnnotationHelper::extractAnnotationData($className, $metadata['method'], $route);
42-
43-
# add an empty method to the file with the transpiled annotations
44-
$fileBuilder->addAnnotatedMethod($metadata['method'], $annotationData->toSwaggerAnnotations($route));
45-
}
46-
$fileBuilder->endClass();
31+
try {
32+
// create a temporary file containing transpiled annotations usable by the external library (Swagger-PHP)
33+
$fileBuilder = new TempAnnotationFileBuilder(self::$autogeneratedAnnotationFilePath);
34+
$fileBuilder->startClass('__Autogenerated_Annotation_Controller__', '1.0', 'ReCodEx API');
35+
36+
// get all routes of the api
37+
$routes = $this->getRoutes();
38+
foreach ($routes as $routeObj) {
39+
// extract class and method names of the endpoint
40+
$metadata = $this->extractMetadata($routeObj);
41+
$route = $this->extractRoute($routeObj);
42+
$className = self::$presenterNamespace . $metadata['class'];
43+
44+
// extract data from the existing annotations
45+
$annotationData = AnnotationHelper::extractAnnotationData($className, $metadata['method'], $route);
46+
47+
// add an empty method to the file with the transpiled annotations
48+
$fileBuilder->addAnnotatedMethod($metadata['method'], $annotationData->toSwaggerAnnotations($route));
49+
}
50+
$fileBuilder->endClass();
51+
52+
return Command::SUCCESS;
53+
} catch (Exception $e) {
54+
$output->writeln("Error in SwaggerAnnotator: {$e->getMessage()}");
4755

48-
return Command::SUCCESS;
56+
return Command::FAILURE;
57+
}
4958
}
5059

5160
/**
5261
* Finds all route objects of the API
5362
* @return array Returns an array of all found route objects.
5463
*/
55-
function getRoutes(): array {
64+
private function getRoutes(): array
65+
{
5666
$router = \App\V1Module\RouterFactory::createRouter();
5767

58-
# find all route object using a queue
68+
// find all route object using a queue
5969
$queue = [$router];
6070
$routes = [];
6171
while (count($queue) != 0) {
6272
$cursor = array_shift($queue);
6373

6474
if ($cursor instanceof RouteList) {
6575
foreach ($cursor->getRouters() as $item) {
66-
# lists contain routes or nested lists
76+
// lists contain routes or nested lists
6777
if ($item instanceof RouteList) {
6878
array_push($queue, $item);
69-
}
70-
else {
71-
# the first route is special and holds no useful information for annotation
72-
if (get_parent_class($item) !== MethodRoute::class)
79+
} else {
80+
// the first route is special and holds no useful information for annotation
81+
if (get_parent_class($item) !== MethodRoute::class) {
7382
continue;
83+
}
7484

7585
$routes[] = $this->getPropertyValue($item, "route");
7686
}
@@ -85,10 +95,11 @@ function getRoutes(): array {
8595
* Extracts the route string from a route object. Replaces '<..>' in the route with '{...}'.
8696
* @param mixed $routeObj
8797
*/
88-
private function extractRoute($routeObj): string {
98+
private function extractRoute($routeObj): string
99+
{
89100
$mask = self::getPropertyValue($routeObj, "mask");
90101

91-
# sample: replaces '/users/<id>' with '/users/{id}'
102+
// sample: replaces '/users/<id>' with '/users/{id}'
92103
$mask = str_replace(["<", ">"], ["{", "}"], $mask);
93104
return "/" . $mask;
94105
}
@@ -98,14 +109,16 @@ private function extractRoute($routeObj): string {
98109
* @param mixed $routeObj The route object representing the endpoint.
99110
* @return string[] Returns a dictionary [ "class" => ..., "method" => ...]
100111
*/
101-
private function extractMetadata($routeObj) {
112+
private function extractMetadata($routeObj)
113+
{
102114
$metadata = self::getPropertyValue($routeObj, "metadata");
103115
$presenter = $metadata["presenter"]["value"];
104116
$action = $metadata["action"]["value"];
105117

106-
# if the name is empty, the method will be called 'actionDefault'
107-
if ($action === null)
118+
// if the name is empty, the method will be called 'actionDefault'
119+
if ($action === null) {
108120
$action = "default";
121+
}
109122

110123
return [
111124
"class" => $presenter . "Presenter",
@@ -122,12 +135,12 @@ private function extractMetadata($routeObj) {
122135
*/
123136
private static function getPropertyValue($object, string $propertyName): mixed
124137
{
125-
$class = new \ReflectionClass($object);
138+
$class = new ReflectionClass($object);
126139

127140
do {
128141
try {
129142
$property = $class->getProperty($propertyName);
130-
} catch (\ReflectionException $exception) {
143+
} catch (ReflectionException $exception) {
131144
$class = $class->getParentClass();
132145
$property = null;
133146
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
namespace App\Helpers\Swagger;
4+
5+
/**
6+
* A data structure for endpoint signatures that can produce annotations parsable by a swagger generator.
7+
*/
8+
class AnnotationData
9+
{
10+
public HttpMethods $httpMethod;
11+
12+
public array $pathParams;
13+
public array $queryParams;
14+
public array $bodyParams;
15+
16+
public function __construct(
17+
HttpMethods $httpMethod,
18+
array $pathParams,
19+
array $queryParams,
20+
array $bodyParams
21+
) {
22+
$this->httpMethod = $httpMethod;
23+
$this->pathParams = $pathParams;
24+
$this->queryParams = $queryParams;
25+
$this->bodyParams = $bodyParams;
26+
}
27+
28+
private function getHttpMethodAnnotation(): string
29+
{
30+
// sample: converts 'PUT' to 'Put'
31+
$httpMethodString = ucfirst(strtolower($this->httpMethod->name));
32+
return "@OA\\" . $httpMethodString;
33+
}
34+
35+
private function getBodyAnnotation(): string | null
36+
{
37+
if (count($this->bodyParams) === 0) {
38+
return null;
39+
}
40+
41+
///TODO: only supports JSON
42+
$head = '@OA\RequestBody(@OA\MediaType(mediaType="application/json",@OA\Schema';
43+
$body = new ParenthesesBuilder();
44+
45+
foreach ($this->bodyParams as $bodyParam) {
46+
$body->addValue($bodyParam->toPropertyAnnotation());
47+
}
48+
49+
return $head . $body->toString() . "))";
50+
}
51+
52+
/**
53+
* Converts the extracted annotation data to a string parsable by the Swagger-PHP library.
54+
* @param string $route The route of the handler this set of data represents.
55+
* @return string Returns the transpiled annotations on a single line.
56+
*/
57+
public function toSwaggerAnnotations(string $route)
58+
{
59+
$httpMethodAnnotation = $this->getHttpMethodAnnotation();
60+
$body = new ParenthesesBuilder();
61+
$body->addKeyValue("path", $route);
62+
63+
foreach ($this->pathParams as $pathParam) {
64+
$body->addValue($pathParam->toParameterAnnotation());
65+
}
66+
foreach ($this->queryParams as $queryParam) {
67+
$body->addValue($queryParam->toParameterAnnotation());
68+
}
69+
70+
$jsonProperties = $this->getBodyAnnotation();
71+
if ($jsonProperties !== null) {
72+
$body->addValue($jsonProperties);
73+
}
74+
75+
///TODO: placeholder
76+
$body->addValue('@OA\Response(response="200",description="The data")');
77+
return $httpMethodAnnotation . $body->toString();
78+
}
79+
}

0 commit comments

Comments
 (0)