Skip to content

Commit c98bad2

Browse files
committed
each class field can now be checked individually, added cache for reflective structures
1 parent 106c6ea commit c98bad2

File tree

6 files changed

+203
-70
lines changed

6 files changed

+203
-70
lines changed

app/commands/MetaTester.php

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

33
namespace App\Console;
44

5+
use App\Helpers\MetaFormats\FormatDefinitions\GroupFormat;
56
use Symfony\Component\Console\Command\Command;
67
use Symfony\Component\Console\Input\InputArgument;
78
use Symfony\Component\Console\Input\InputInterface;
@@ -28,11 +29,14 @@ protected function execute(InputInterface $input, OutputInterface $output)
2829

2930
public function test(string $arg)
3031
{
31-
$view = new TestView();
32-
$view->endpoint([
33-
"id" => "0",
34-
"organizational" => false,
35-
], "0001");
36-
// $view->get_user_info(0);
32+
// $view = new TestView();
33+
// $view->endpoint([
34+
// "id" => "0",
35+
// "organizational" => false,
36+
// ], "0001");
37+
// // $view->get_user_info(0);
38+
39+
$format = new GroupFormat();
40+
var_dump($format->checkIfAssignable("primaryAdminsIds", [ "10000000-2000-4000-8000-160000000000", "10000000-2000-4000-8000-160000000000" ]));
3741
}
3842
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php
2+
3+
namespace App\Helpers\MetaFormats;
4+
5+
use App\Exceptions\InternalServerException;
6+
7+
class FieldFormatDefinition
8+
{
9+
public ?string $format;
10+
// A string name of the field type yielded by 'ReflectionProperty::getType()'.
11+
public ?string $type;
12+
13+
///TODO: double check this
14+
private static array $gettypeToReflectiveMap = [
15+
"boolean" => "bool",
16+
"integer" => "int",
17+
"double" => "double",
18+
"string" => "string",
19+
"array" => "array",
20+
"object" => "object",
21+
"resource" => "resource",
22+
"NULL" => "null",
23+
];
24+
25+
/**
26+
* Constructs a field format definition.
27+
* Either the @format or @type parameter need to have a non-null value (or both).
28+
* @param ?string $format The format of the field.
29+
* @param ?string $type The PHP type of the field yielded by a 'ReflectionProperty::getType()' call.
30+
* @throws \App\Exceptions\InternalServerException Thrown when both @format and @type were null.
31+
*/
32+
public function __construct(?string $format, ?string $type)
33+
{
34+
// if both are null, there is no way to validate an assigned value
35+
if ($format === null && $type === null) {
36+
throw new InternalServerException("Both the format and type of a field definition were undefined.");
37+
}
38+
39+
$this->format = $format;
40+
$this->type = $type;
41+
}
42+
43+
/**
44+
* Checks whether a value meets this definition.
45+
* @param mixed $value The value to be checked.
46+
* @throws \App\Exceptions\InternalServerException Thrown when the format does not have a validator.
47+
* @return bool Returns whether the value passed the test.
48+
*/
49+
public function conformsToDefinition(mixed $value)
50+
{
51+
// use format validators if possible
52+
if ($this->format !== null) {
53+
// enables parsing more complicated formats (string[]?, string?[], string?[][]?, ...)
54+
$parsedFormat = new FormatParser($this->format);
55+
return self::recursiveFormatChecker($value, $parsedFormat);
56+
}
57+
58+
// convert the gettype return value to the reflective return value
59+
$valueType = gettype($value);
60+
if (!array_key_exists($valueType, self::$gettypeToReflectiveMap)) {
61+
throw new InternalServerException("Unknown gettype value: $valueType");
62+
}
63+
return $valueType === $this->type;
64+
}
65+
66+
/**
67+
* Checks whether the value fits a format recursively.
68+
* The format can contain array modifiers and thus all array elements need to be checked recursively.
69+
* @param mixed $value The value to be checked
70+
* @param \App\Helpers\MetaFormats\FormatParser $parsedFormat A parsed format used for recursive traversal.
71+
* @throws \App\Exceptions\InternalServerException Thrown when a format does not have a validator.
72+
* @return bool Returns whether the value conforms to the format.
73+
*/
74+
private static function recursiveFormatChecker(mixed $value, FormatParser $parsedFormat): bool
75+
{
76+
// check nullability
77+
if ($value === null) {
78+
return $parsedFormat->nullable;
79+
}
80+
81+
// handle arrays
82+
if ($parsedFormat->isArray) {
83+
if (!is_array($value)) {
84+
return false;
85+
}
86+
87+
// if any element fails, the whole format fails
88+
foreach ($value as $element) {
89+
if (!self::recursiveFormatChecker($element, $parsedFormat->nested)) {
90+
return false;
91+
}
92+
}
93+
return true;
94+
}
95+
96+
// check whether the validator exists
97+
$validators = FormatCache::getValidators();
98+
if (!array_key_exists($parsedFormat->format, $validators)) {
99+
throw new InternalServerException("The format {$parsedFormat->format} does not have a validator.");
100+
}
101+
102+
return $validators[$parsedFormat->format]($value);
103+
}
104+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
namespace App\Helpers\MetaFormats;
4+
5+
use App\Exceptions\InternalServerException;
6+
7+
class FormatCache
8+
{
9+
private static ?array $formatToClassMap = null;
10+
private static ?array $classToFormatMap = null;
11+
private static ?array $formatToFieldFormatsMap = null;
12+
private static ?array $validators = null;
13+
14+
public static function getFormatToClassMap(): array
15+
{
16+
if (self::$formatToClassMap == null) {
17+
self::$formatToClassMap = MetaFormatHelper::createFormatToClassMap();
18+
}
19+
return self::$formatToClassMap;
20+
}
21+
22+
public static function getClassToFormatMap(): array
23+
{
24+
if (self::$classToFormatMap == null) {
25+
self::$classToFormatMap = [];
26+
$formatToClassMap = self::getFormatToClassMap();
27+
foreach ($formatToClassMap as $format => $class) {
28+
self::$classToFormatMap[$class] = $format;
29+
}
30+
}
31+
return self::$classToFormatMap;
32+
}
33+
34+
public static function getFormatToFieldDefinitionsMap(): array
35+
{
36+
if (self::$formatToFieldFormatsMap == null) {
37+
self::$formatToFieldFormatsMap = [];
38+
$formatToClassMap = self::getFormatToClassMap();
39+
foreach ($formatToClassMap as $format => $class) {
40+
self::$formatToFieldFormatsMap[$format] = MetaFormatHelper::createNameToFieldDefinitionsMap($class);
41+
}
42+
}
43+
return self::$formatToFieldFormatsMap;
44+
}
45+
46+
public static function getValidators(): array
47+
{
48+
if (self::$validators == null) {
49+
self::$validators = MetaFormatHelper::getValidators();
50+
}
51+
return self::$validators;
52+
}
53+
54+
public static function getFieldDefinitions(string $className)
55+
{
56+
$classToFormatMap = self::getClassToFormatMap();
57+
if (!array_key_exists($className, $classToFormatMap)) {
58+
throw new InternalServerException("The class $className does not have a format definition.");
59+
}
60+
61+
$format = $classToFormatMap[$className];
62+
$formatToFieldFormatsMap = self::getFormatToFieldDefinitionsMap();
63+
if (!array_key_exists($format, $formatToFieldFormatsMap)) {
64+
throw new InternalServerException("The format $format does not have a field format definition.");
65+
}
66+
67+
return $formatToFieldFormatsMap[$format];
68+
}
69+
}

app/helpers/MetaFormats/MetaFormat.php

Lines changed: 15 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,24 @@
22

33
namespace App\Helpers\MetaFormats;
44

5+
use App\Exceptions\InternalServerException;
56
use App\Helpers\Swagger\AnnotationHelper;
67

8+
use function Symfony\Component\String\b;
9+
710
class MetaFormat
811
{
9-
// validates primitive formats of intrinsic PHP types
10-
///TODO: make this static somehow (or cached)
11-
private $validators;
12-
13-
public function __construct()
12+
public function checkIfAssignable(string $fieldName, mixed $value): bool
1413
{
15-
$this->validators = MetaFormatHelper::getValidators();
14+
$fieldFormats = FormatCache::getFieldDefinitions(get_class($this));
15+
if (!array_key_exists($fieldName, $fieldFormats)) {
16+
throw new InternalServerException("The field name $fieldName is not present in the format definition.");
17+
}
18+
// get the definition for the specific field
19+
$formatDefinition = $fieldFormats[$fieldName];
20+
return $formatDefinition->conformsToDefinition($value);
1621
}
1722

18-
1923
/**
2024
* Validates the given format.
2125
* @return bool Returns whether the format and all nested formats are valid.
@@ -27,62 +31,17 @@ public function validate()
2731
return false;
2832
}
2933

30-
// check properties
31-
$selfFormat = MetaFormatHelper::getClassFormats(get_class($this));
32-
foreach ($selfFormat as $propertyName => $propertyFormat) {
33-
///TODO: check if this is true
34-
/// if the property is checked by type only, there is no need to check it as an invalid assignment
35-
/// would rise an error
36-
$value = $this->$propertyName;
37-
$format = $propertyFormat["format"];
38-
if ($format === null) {
39-
continue;
40-
}
41-
42-
// enables parsing more complicated formats (string[]?, string?[], string?[][]?, ...)
43-
$parsedFormat = new FormatParser($format);
44-
if (!$this->recursiveFormatChecker($value, $parsedFormat)) {
34+
// go through all fields and check whether they were assigned properly
35+
$fieldFormats = FormatCache::getFieldDefinitions(get_class($this));
36+
foreach ($fieldFormats as $fieldName => $fieldFormat) {
37+
if (!$this->checkIfAssignable($fieldName, $this->$fieldName)) {
4538
return false;
4639
}
4740
}
4841

4942
return true;
5043
}
5144

52-
private function recursiveFormatChecker($value, FormatParser $parsedFormat)
53-
{
54-
// enables parsing more complicated formats (string[]?, string?[], string?[][]?, ...)
55-
56-
// check nullability
57-
if ($value === null) {
58-
return $parsedFormat->nullable;
59-
}
60-
61-
// handle arrays
62-
if ($parsedFormat->isArray) {
63-
if (!is_array($value)) {
64-
return false;
65-
}
66-
67-
// if any element fails, the whole format fails
68-
foreach ($value as $element) {
69-
if (!$this->recursiveFormatChecker($element, $parsedFormat->nested)) {
70-
return false;
71-
}
72-
}
73-
return true;
74-
}
75-
76-
///TODO: raise an error
77-
// check whether the validator exists
78-
if (!array_key_exists($parsedFormat->format, $this->validators)) {
79-
echo "Error: missing validator for format: " . $parsedFormat->format . "\n";
80-
return false;
81-
}
82-
83-
return $this->validators[$parsedFormat->format]($value);
84-
}
85-
8645
/**
8746
* Validates this format. Automatically called by the validate method on all fields.
8847
* Primitive formats should always override this, composite formats might want to override

app/helpers/MetaFormats/MetaFormatHelper.php

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ private static function extractFormatFromAttribute(ReflectionClass|ReflectionPro
8080
* @param string $className The name of the class.
8181
* @return array{format: string|null, type: string|null} with the field name as the key.
8282
*/
83-
public static function getClassFormats(string $className)
83+
public static function createNameToFieldDefinitionsMap(string $className)
8484
{
8585
$class = new ReflectionClass($className);
8686
$fields = get_class_vars($className);
@@ -92,10 +92,7 @@ public static function getClassFormats(string $className)
9292
// get null if there is no type
9393
$fieldType = $field->getType()?->getName();
9494

95-
$formats[$fieldName] = [
96-
"type" => $fieldType,
97-
"format" => $format,
98-
];
95+
$formats[$fieldName] = new FieldFormatDefinition($format, $fieldType);
9996
}
10097

10198
return $formats;
@@ -104,7 +101,7 @@ public static function getClassFormats(string $className)
104101
/**
105102
* Creates a mapping from formats to class names, where the class defines the format.
106103
*/
107-
public static function getFormatDefinitions()
104+
public static function createFormatToClassMap()
108105
{
109106
// scan directory of format definitions
110107
$formatFiles = scandir(self::$formatDefinitionFolder);

app/helpers/MetaFormats/PrimitiveFormatValidators.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class PrimitiveFormatValidators
77
/**
88
* @format uuid
99
*/
10-
public function validateUuid($uuid)
10+
public function validateUuid($uuid): bool
1111
{
1212
if (!self::checkType($uuid, PhpTypes::String)) {
1313
return false;
@@ -16,7 +16,7 @@ public function validateUuid($uuid)
1616
return preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/', $uuid) === 1;
1717
}
1818

19-
private static function checkType($value, PhpTypes $type)
19+
private static function checkType($value, PhpTypes $type): bool
2020
{
2121
return gettype($value) === $type->value;
2222
}

0 commit comments

Comments
 (0)