Skip to content

Commit 8d9d52d

Browse files
committed
added attribute to substitue @param, added script to convert files with @param to attributes
1 parent 4bfa8f3 commit 8d9d52d

File tree

11 files changed

+228
-9
lines changed

11 files changed

+228
-9
lines changed

app/V1Module/presenters/RegistrationPresenter.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
use App\Helpers\EmailVerificationHelper;
2222
use App\Helpers\RegistrationConfig;
2323
use App\Helpers\InvitationHelper;
24-
use App\Helpers\MetaFormats\FormatAttribute;
24+
use App\Helpers\MetaFormats\Attributes\FormatAttribute;
25+
use App\Helpers\MetaFormats\FormatDefinitions\UserFormat;
26+
use App\Helpers\MetaFormats\Attributes\ParamAttribute;
2527
use App\Security\Roles;
2628
use App\Security\ACL\IUserPermissions;
2729
use App\Security\ACL\IGroupPermissions;
@@ -164,6 +166,7 @@ public function checkCreateAccount()
164166
* @throws InvalidArgumentException
165167
*/
166168
#[FormatAttribute("userRegistration")]
169+
#[ParamAttribute("email", "An email that will serve as a login name", validation: [ new UserFormat() ])]
167170
public function actionCreateAccount()
168171
{
169172
$req = $this->getMetaRequest();

app/V1Module/presenters/base/BasePresenter.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,9 @@ private function processParams(ReflectionMethod $reflection)
215215
{
216216
$format = MetaFormatHelper::extractFormatFromAttribute($reflection);
217217

218+
$this->logger->log(var_export(MetaFormatHelper::debugGetAttributes($reflection), true), ILogger::DEBUG);
219+
220+
218221
// ignore request that do not yet have the attribute
219222
if ($format === null) {
220223
return;

app/commands/MetaTester.php

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Console;
44

5+
use App\Helpers\MetaFormats\AnnotationToAttributeConverter;
56
use App\Helpers\MetaFormats\FormatDefinitions\GroupFormat;
67
use App\Helpers\MetaFormats\FormatDefinitions\UserFormat;
78
use Symfony\Component\Console\Command\Command;
@@ -40,7 +41,41 @@ public function test(string $arg)
4041
// $format = new GroupFormat();
4142
// var_dump($format->checkIfAssignable("primaryAdminsIds", [ "10000000-2000-4000-8000-160000000000", "10000000-2000-4000-8000-160000000000" ]));
4243

43-
$format = new UserFormat();
44-
var_dump($format->checkedAssign("email", "[email protected]"));
44+
// $format = new UserFormat();
45+
// var_dump($format->checkedAssign("email", "[email protected]"));
46+
47+
$inDir = __DIR__ . "/../V1Module/presenters";
48+
$outDir = __DIR__ . "/../V1Module/presenters2";
49+
50+
// create output folder
51+
if (!is_dir($outDir)) {
52+
mkdir($outDir);
53+
54+
// copy base subfolder
55+
$inBaseDir = $inDir . "/base";
56+
$outBaseDir = $outDir . "/base";
57+
mkdir($outBaseDir);
58+
$baseFilenames = scandir($inBaseDir);
59+
foreach ($baseFilenames as $filename) {
60+
if (!str_ends_with($filename, ".php")) {
61+
continue;
62+
}
63+
64+
copy($inBaseDir . "/" . $filename, $outBaseDir);
65+
}
66+
}
67+
68+
$filenames = scandir($inDir);
69+
foreach ($filenames as $filename) {
70+
if (!str_ends_with($filename, "Presenter.php")) {
71+
continue;
72+
}
73+
74+
$filepath = $inDir . "/" . $filename;
75+
$newContent = AnnotationToAttributeConverter::convertFile($filepath);
76+
$newFile = fopen($outDir . "/" . $filename, "w");
77+
fwrite($newFile, $newContent);
78+
fclose($newFile);
79+
}
4580
}
4681
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<?php
2+
3+
namespace App\Helpers\MetaFormats;
4+
5+
use App\Exceptions\InternalServerException;
6+
use App\Helpers\Swagger\ParenthesesBuilder;
7+
8+
class AnnotationToAttributeConverter
9+
{
10+
/**
11+
* A regex that matches @Param annotations and captures its parameters. Can capture up to 7 parameters.
12+
* Contains 6 copies of the following sub-regex: '(?:([a-z]+?=.+?),?\s*\*?\s*)?', which
13+
* matches 'name=value' assignments followed by an optional comma, whitespace,
14+
* star (multi-line annotation support), whitespace. The capture contains only 'name=value'.
15+
* The regex ends with '([a-z]+?=.+)\)', which is similar to the above, but instead of ending with
16+
* an optional comma etc., it ends with the closing parentheses of the @Param annotation.
17+
*/
18+
private static string $postRegex = "/\*\s*@Param\((?:([a-z]+?=.+?),?\s*\*?\s*)?(?:([a-z]+?=.+?),?\s*\*?\s*)?(?:([a-z]+?=.+?),?\s*\*?\s*)?(?:([a-z]+?=.+?),?\s*\*?\s*)?(?:([a-z]+?=.+?),?\s*\*?\s*)?(?:([a-z]+?=.+?),?\s*\*?\s*)?([a-z]+?=.+)\)/";
19+
20+
/**
21+
* Converts an array of preg_replace_callback matches to an attribute string.
22+
* @param array $matches An array of matches, with empty captures as NULL (PREG_UNMATCHED_AS_NULL flag).
23+
* @return string Returns an attribute string.
24+
*/
25+
private static function regexCaptureToAttributeCallback(array $matches)
26+
{
27+
// convert the string assignments in $matches to an associative array
28+
$annotationParameters = [];
29+
// the first element is the matched string
30+
for ($i = 1; $i < count($matches); $i++) {
31+
$capture = $matches[$i];
32+
if ($capture === null) {
33+
continue;
34+
}
35+
36+
// the regex extracts the key as the first capture, and the value as the second or third (depends
37+
// whether the value is enclosed in double quotes)
38+
$parseResult = preg_match('/([a-z]+)=(?:(?:"(.+?)")|(?:(.+)))/', $capture, $tokens, PREG_UNMATCHED_AS_NULL);
39+
if ($parseResult !== 1) {
40+
throw new InternalServerException("Unexpected assignment format: $capture");
41+
}
42+
43+
$key = $tokens[1];
44+
$value = $tokens[2] ?? $tokens[3];
45+
$annotationParameters[$key] = $value;
46+
}
47+
48+
// serialize the parameters to an attribute
49+
$parenthesesBuilder = new ParenthesesBuilder();
50+
51+
// add type
52+
$typeStr = $annotationParameters["type"];
53+
$type = null;
54+
switch ($typeStr) {
55+
case "post":
56+
$type = "RequestParamType::Post";
57+
break;
58+
case "query":
59+
$type = "RequestParamType::Query";
60+
break;
61+
default:
62+
throw new InternalServerException("Unknown request type: $typeStr");
63+
}
64+
$parenthesesBuilder->addValue($type);
65+
66+
// add name
67+
if (!array_key_exists("name", $annotationParameters)) {
68+
throw new InternalServerException("Missing name parameter.");
69+
}
70+
$parenthesesBuilder->addValue("\"{$annotationParameters["name"]}\"");
71+
72+
if (array_key_exists("description", $annotationParameters)) {
73+
$parenthesesBuilder->addValue("description: \"{$annotationParameters["description"]}\"");
74+
}
75+
76+
if (array_key_exists("validation", $annotationParameters)) {
77+
///TODO
78+
$parenthesesBuilder->addValue("validation: [ \"{$annotationParameters["validation"]}\" ]");
79+
}
80+
81+
if (array_key_exists("required", $annotationParameters)) {
82+
$parenthesesBuilder->addValue("required: " . $annotationParameters["required"]);
83+
}
84+
85+
if (!array_key_exists("type", $annotationParameters)) {
86+
throw new InternalServerException("Missing type parameter.");
87+
}
88+
89+
return "#[ParamAttribute{$parenthesesBuilder->toString()}]";
90+
}
91+
92+
public static function convertFile(string $path)
93+
{
94+
// read file and replace @Param annotations with attributes
95+
$content = file_get_contents($path);
96+
$withInterleavedAttributes = preg_replace_callback(self::$postRegex, function ($matches) {
97+
return self::regexCaptureToAttributeCallback($matches);
98+
}, $content, -1, $count, PREG_UNMATCHED_AS_NULL);
99+
100+
// move the attribute lines below the comment block
101+
$lines = [];
102+
$attributeLinesBuffer = [];
103+
$usingsAdded = false;
104+
foreach (preg_split("/((\r?\n)|(\r\n?))/", $withInterleavedAttributes) as $line) {
105+
// add usings for new types
106+
if (!$usingsAdded && strlen($line) > 3 && substr($line, 0, 3) === "use") {
107+
$lines[] = "use App\Helpers\MetaFormats\Attributes\ParamAttribute;";
108+
$lines[] = "use App\Helpers\MetaFormats\RequestParamType;";
109+
$lines[] = $line;
110+
$usingsAdded = true;
111+
// store attribute lines in the buffer and do not write them
112+
} elseif (preg_match("/#\[ParamAttribute/", $line) === 1) {
113+
$attributeLinesBuffer[] = $line;
114+
// flush attribute lines
115+
} elseif (trim($line) === "*/") {
116+
$lines[] = $line;
117+
foreach ($attributeLinesBuffer as $attributeLine) {
118+
// the attribute lines are shifted by one space to the right (due to the comment block origin)
119+
$lines[] = substr($attributeLine, 1);
120+
}
121+
$attributeLinesBuffer = [];
122+
} else {
123+
$lines[] = $line;
124+
}
125+
}
126+
127+
///TODO: add usings for used validators
128+
///TODO: handle too long lines
129+
return implode("\n", $lines);
130+
}
131+
}

app/helpers/MetaFormats/FormatAttribute.php renamed to app/helpers/MetaFormats/Attributes/FormatAttribute.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22

3-
namespace App\Helpers\MetaFormats;
3+
namespace App\Helpers\MetaFormats\Attributes;
44

55
use Attribute;
66

app/helpers/MetaFormats/FormatParameterAttribute.php renamed to app/helpers/MetaFormats/Attributes/FormatParameterAttribute.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
<?php
22

3-
namespace App\Helpers\MetaFormats;
3+
namespace App\Helpers\MetaFormats\Attributes;
44

5+
use App\Helpers\MetaFormats\RequestParamType;
56
use Attribute;
67

78
/**
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace App\Helpers\MetaFormats\Attributes;
4+
5+
use App\Helpers\MetaFormats\RequestParamType;
6+
use Attribute;
7+
8+
/**
9+
* Attribute used to annotate individual post or query parameters of endpoints.
10+
*/
11+
#[Attribute]
12+
class ParamAttribute
13+
{
14+
/**
15+
* @param \App\Helpers\MetaFormats\RequestParamType $type The request parameter type (Post or Query).
16+
* @param string $name The name of the request parameter.
17+
* @param string $description The description of the request parameter.
18+
* @param bool $required Whether the request parameter is required.
19+
* @param array $validators An array of validators applied to the request parameter.
20+
*/
21+
public function __construct(
22+
RequestParamType $type,
23+
string $name,
24+
string $description = "",
25+
bool $required = true,
26+
array $validators = [],
27+
) {
28+
}
29+
}

app/helpers/MetaFormats/FormatDefinitions/GroupFormat.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace App\Helpers\MetaFormats\FormatDefinitions;
44

5-
use App\Helpers\MetaFormats\FormatAttribute;
5+
use App\Helpers\MetaFormats\Attributes\FormatAttribute;
66
use App\Helpers\MetaFormats\MetaFormat;
77

88
#[FormatAttribute("group")]

app/helpers/MetaFormats/FormatDefinitions/UserFormat.php

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

33
namespace App\Helpers\MetaFormats\FormatDefinitions;
44

5-
use App\Helpers\MetaFormats\FormatAttribute;
5+
use App\Helpers\MetaFormats\Attributes\FormatAttribute;
66
use App\Helpers\MetaFormats\MetaFormat;
7-
use App\Helpers\MetaFormats\FormatParameterAttribute;
7+
use App\Helpers\MetaFormats\Attributes\FormatParameterAttribute;
88
use App\Helpers\MetaFormats\RequestParamType;
99

1010
#[FormatAttribute("userRegistration")]

app/helpers/MetaFormats/MetaFormatHelper.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,23 @@ public static function extractRequestAttributeData(
100100
return new RequestParamData($type, $description, $required);
101101
}
102102

103+
/**
104+
* Debug method used to extract all attribute data of a reflection object.
105+
* @param \ReflectionClass|\ReflectionProperty|\ReflectionMethod $reflectionObject The reflection object.
106+
* @return array Returns an array, where each element represents an attribute in top-down order of definition
107+
* in the code. Each element is an array of constructor arguments of the attribute.
108+
*/
109+
public static function debugGetAttributes(
110+
ReflectionClass|ReflectionProperty|ReflectionMethod $reflectionObject
111+
): array {
112+
$requestAttributes = $reflectionObject->getAttributes();
113+
$data = [];
114+
foreach ($requestAttributes as $attr) {
115+
$data[] = $attr->getArguments();
116+
}
117+
return $data;
118+
}
119+
103120
/**
104121
* Parses the format attributes of class fields and returns their metadata.
105122
* @param string $className The name of the class.

0 commit comments

Comments
 (0)