Skip to content

Commit

Permalink
improved code comments
Browse files Browse the repository at this point in the history
  • Loading branch information
eceltov committed Oct 17, 2024
1 parent 5398ab7 commit 01f0705
Showing 1 changed file with 57 additions and 17 deletions.
74 changes: 57 additions & 17 deletions app/commands/SwaggerAnnotator.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
class SwaggerAnnotator extends Command
{
protected static $defaultName = 'swagger:annotate';
private static $presenterNamespace = 'App\V1Module\Presenters\\';
private static $autogeneratedAnnotationFilePath = 'app/V1Module/presenters/annotations.php';

protected function configure(): void
{
Expand All @@ -28,26 +30,33 @@ protected function configure(): void

protected function execute(InputInterface $input, OutputInterface $output): int
{
$namespacePrefix = 'App\V1Module\Presenters\\';
# create a temporary file containing transpiled annotations usable by the external library (Swagger-PHP)
$fileBuilder = new FileBuilder(self::$autogeneratedAnnotationFilePath);
$fileBuilder->startClass('__Autogenerated_Annotation_Controller__', '1.0', 'ReCodEx API');

$fileBuilder = new FileBuilder("app/V1Module/presenters/annotations.php");
$fileBuilder->startClass("AnnotationController");
# get all routes of the api
$routes = $this->getRoutes();
foreach ($routes as $route) {
$metadata = $this->extractMetadata($route);
$route = $this->extractRoute($route);
foreach ($routes as $routeObj) {
# extract class and method names of the endpoint
$metadata = $this->extractMetadata($routeObj);
$route = $this->extractRoute($routeObj);
$className = self::$presenterNamespace . $metadata['class'];

$className = $namespacePrefix . $metadata['class'];
# extract data from the existing annotations
$annotationData = AnnotationHelper::extractAnnotationData($className, $metadata['method'], $route);

# add an empty method to the file with the transpiled annotations
$fileBuilder->addAnnotatedMethod($metadata['method'], $annotationData->toSwaggerAnnotations($route));
}
$fileBuilder->endClass();


return Command::SUCCESS;
}

/**
* Finds all route objects of the API
* @return array Returns an array of all found route objects.
*/
function getRoutes(): array {
$router = \App\V1Module\RouterFactory::createRouter();

Expand Down Expand Up @@ -77,14 +86,23 @@ function getRoutes(): array {
return $routes;
}

private function extractRoute($routeObj) {
/**
* Extracts the route string from a route object. Replaces '<..>' in the route with '{...}'.
* @param mixed $routeObj
*/
private function extractRoute($routeObj): string {
$mask = self::getPropertyValue($routeObj, "mask");

# sample: replaces '/users/<id>' with '/users/{id}'
$mask = str_replace(["<", ">"], ["{", "}"], $mask);
return "/" . $mask;
}

/**
* Extracts the class and method names of the endpoint handler.
* @param mixed $routeObj The route object representing the endpoint.
* @return string[] Returns a dictionary [ "class" => ..., "method" => ...]
*/
private function extractMetadata($routeObj) {
$metadata = self::getPropertyValue($routeObj, "metadata");
$presenter = $metadata["presenter"]["value"];
Expand All @@ -100,6 +118,13 @@ private function extractMetadata($routeObj) {
];
}

/**
* Helper function that can extract a property value from an arbitrary object where
* the property can be private.
* @param mixed $object The object to extract from.
* @param string $propertyName The name of the property.
* @return mixed Returns the value of the property.
*/
private static function getPropertyValue($object, string $propertyName): mixed
{
$class = new \ReflectionClass($object);
Expand All @@ -118,6 +143,9 @@ private static function getPropertyValue($object, string $propertyName): mixed
}
}

/**
* Builder class that handles .php file creation.
*/
class FileBuilder {
private $file;
private $methodEntries;
Expand All @@ -132,16 +160,16 @@ public function __construct(
private function initFile(string $filename) {
$this->file = fopen($filename, "w");
fwrite($this->file, "<?php\n");
fwrite($this->file, "/// THIS FILE WAS AUTOGENERATED\n");
fwrite($this->file, "namespace App\V1Module\Presenters;\n");
fwrite($this->file, "use OpenApi\Annotations as OA;\n");
}

///TODO: hardcoded info
private function createInfoAnnotation() {
private function createInfoAnnotation(string $version, string $title) {
$head = "@OA\\Info";
$body = new ParenthesesBuilder();
$body->addKeyValue("version", "1.0");
$body->addKeyValue("title", "ReCodEx API");
$body->addKeyValue("version", $version);
$body->addKeyValue("title", $title);
return $head . $body->toString();
}

Expand All @@ -151,9 +179,8 @@ private function writeAnnotationLineWithComments(string $annotationLine) {
fwrite($this->file, "*/\n");
}

public function startClass(string $className) {
///TODO: hardcoded
$this->writeAnnotationLineWithComments($this->createInfoAnnotation());
public function startClass(string $className, string $version, string $title) {
$this->writeAnnotationLineWithComments($this->createInfoAnnotation($version, $title));
fwrite($this->file, "class {$className} {\n");
}

Expand Down Expand Up @@ -217,6 +244,11 @@ private function getBodyAnnotation(): string|null {
return $head . $body->toString() . "))";
}

/**
* Converts the extracted annotation data to a string parsable by the Swagger-PHP library.
* @param string $route The route of the handler this set of data represents.
* @return string Returns the transpiled annotations on a single line.
*/
public function toSwaggerAnnotations(string $route) {
$httpMethodAnnotation = $this->getHttpMethodAnnotation();
$body = new ParenthesesBuilder();
Expand All @@ -239,6 +271,9 @@ public function toSwaggerAnnotations(string $route) {
}
}

/**
* Builder class that can create strings of the schema: '(key1="value1", key2="value2", standalone1, standalone2, ...)'
*/
class ParenthesesBuilder {
private array $tokens;

Expand Down Expand Up @@ -269,6 +304,9 @@ public function toString(): string {
}
}

/**
* Contains data of a single annotation parameter.
*/
class AnnotationParameterData {
public string|null $dataType;
public string $name;
Expand Down Expand Up @@ -353,7 +391,6 @@ private function generateSchemaAnnotation(): string {

/**
* Converts the object to a @OA\Parameter(...) annotation string
* @param string $parameterLocation Where the parameter resides. Can be 'path', 'query', 'header' or 'cookie'.
*/
public function toParameterAnnotation(): string {
$head = "@OA\\Parameter";
Expand Down Expand Up @@ -381,6 +418,9 @@ public function toPropertyAnnotation(): string {
}
}

/**
* Parser that can parse the annotations of existing recodex endpoints
*/
class AnnotationHelper {
private static function getMethod(string $className, string $methodName): \ReflectionMethod {
$class = new \ReflectionClass($className);
Expand Down

0 comments on commit 01f0705

Please sign in to comment.