Skip to content

Commit d9252a3

Browse files
committed
Enum post processor implementation
1 parent 2fbe8bc commit d9252a3

18 files changed

+215
-130
lines changed

src/Model/Property/Property.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,15 @@ public function getType(bool $outputType = false): ?PropertyType
8989
/**
9090
* @inheritdoc
9191
*/
92-
public function setType(PropertyType $type = null, PropertyType $outputType = null): PropertyInterface
93-
{
92+
public function setType(
93+
PropertyType $type = null,
94+
PropertyType $outputType = null,
95+
$reset = false
96+
): PropertyInterface {
97+
if ($reset) {
98+
$this->typeHintDecorators = [];
99+
}
100+
94101
$this->type = $type;
95102
$this->outputType = $outputType;
96103

src/Model/Property/PropertyInterface.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,15 @@ public function getType(bool $outputType = false): ?PropertyType;
4343
* @param PropertyType|null $type
4444
* @param PropertyType|null $outputType By default the output type will be equal to the base type but due to applied
4545
* filters the output type may change
46+
* @param bool $reset set to true for a full type reset (including type hint decorators like array, ...)
4647
*
4748
* @return PropertyInterface
4849
*/
49-
public function setType(PropertyType $type = null, PropertyType $outputType = null): PropertyInterface;
50+
public function setType(
51+
PropertyType $type = null,
52+
PropertyType $outputType = null,
53+
bool $reset = false
54+
): PropertyInterface;
5055

5156
/**
5257
* @param bool $outputType If set to true the output type hint will be returned (may differ from the base type)

src/Model/Property/PropertyProxy.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,11 @@ public function getType(bool $outputType = false): ?PropertyType
6969
/**
7070
* @inheritdoc
7171
*/
72-
public function setType(PropertyType $type = null, PropertyType $outputType = null): PropertyInterface
73-
{
72+
public function setType(
73+
PropertyType $type = null,
74+
PropertyType $outputType = null,
75+
bool $reset = false
76+
): PropertyInterface {
7477
return $this->getProperty()->setType($type, $outputType);
7578
}
7679

src/SchemaProcessor/PostProcessor/EnumPostProcessor.php

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@
1616
use PHPModelGenerator\Model\Validator\EnumValidator;
1717
use PHPModelGenerator\Model\Validator\FilterValidator;
1818
use PHPModelGenerator\ModelGenerator;
19+
use PHPModelGenerator\PropertyProcessor\Decorator\TypeHint\ClearTypeHintDecorator;
1920
use PHPModelGenerator\PropertyProcessor\Filter\FilterProcessor;
2021

22+
/**
23+
* Generates a PHP enum for enums from JSON schemas which are automatically mapped for properties holding the enum
24+
*/
2125
class EnumPostProcessor extends PostProcessor
2226
{
2327
private $generatedEnums = [];
@@ -31,8 +35,6 @@ class EnumPostProcessor extends PostProcessor
3135
/** @var bool */
3236
private $skipNonMappedEnums;
3337

34-
private $hasAddedFilter = false;
35-
3638
/**
3739
* @param string $targetDirectory The directory where to put the generated PHP enums
3840
* @param string $namespace The namespace for the generated enums
@@ -60,10 +62,7 @@ public function __construct(
6062

6163
public function process(Schema $schema, GeneratorConfiguration $generatorConfiguration): void
6264
{
63-
if (!$this->hasAddedFilter) {
64-
$generatorConfiguration->addFilter(new EnumFilter());
65-
$this->hasAddedFilter = true;
66-
}
65+
$generatorConfiguration->addFilter(new EnumFilter());
6766

6867
foreach ($schema->getProperties() as $property) {
6968
$json = $property->getJsonSchema()->getJson();
@@ -94,13 +93,13 @@ public function process(Schema $schema, GeneratorConfiguration $generatorConfigu
9493

9594
(new FilterProcessor())->process(
9695
$property,
97-
['filter' => 'php_model_generator_enum', 'fqcn' => $fqcn],
96+
['filter' => (new EnumFilter())->getToken(), 'fqcn' => $fqcn],
9897
$generatorConfiguration,
9998
$schema
10099
);
101100

102101
$schema->addUsedClass($fqcn);
103-
$property->setType($inputType, new PropertyType($name, !$property->isRequired()));
102+
$property->setType($inputType, new PropertyType($name, !$property->isRequired()), true);
104103

105104
if ($property->getDefaultValue() && in_array($property->getJsonSchema()->getJson()['default'], $values)) {
106105
$property->setDefaultValue("$name::{$property->getJsonSchema()->getJson()['default']}", true);
@@ -135,7 +134,6 @@ private function checkForExistingTransformingFilter(PropertyInterface $property)
135134

136135
public function postProcess(): void
137136
{
138-
$this->hasAddedFilter = false;
139137
$this->generatedEnums = [];
140138

141139
parent::postProcess();
@@ -155,6 +153,8 @@ private function validateEnum(PropertyInterface $property): bool
155153

156154
$types = $this->getArrayTypes($json['enum']);
157155

156+
// the enum must contain either only string or int values to be represented by a backed enum or provide a value
157+
// map to resolve the values
158158
if ($types !== ['string'] && !isset($json['map'])) {
159159
if ($this->skipNonMappedEnums) {
160160
return false;
@@ -196,7 +196,7 @@ private function renderEnum(
196196
$cases = [];
197197

198198
foreach ($values as $value) {
199-
$cases[$map ? array_search($value, $map) : $value] = var_export($value, true);
199+
$cases[ucfirst($map ? array_search($value, $map) : $value)] = var_export($value, true);
200200
}
201201

202202
file_put_contents(
@@ -206,8 +206,12 @@ private function renderEnum(
206206
[
207207
'namespace' => $this->namespace,
208208
'name' => $name,
209-
'backedType' => $this->getArrayTypes($values) === ['int'] ? 'int' : 'string',
210209
'cases' => $cases,
210+
'backedType' => match ($this->getArrayTypes($values)) {
211+
['string'] => 'string',
212+
['int'] => 'int',
213+
default => null,
214+
},
211215
]
212216
)
213217
);

src/SchemaProcessor/PostProcessor/Templates/Enum.phptpl

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,31 @@ declare(strict_types=1);
44

55
namespace {{ namespace }};
66

7-
enum {{ name }}: {{ backedType }} {
7+
enum {{ name }}{% if backedType %}: {{ backedType }}{% endif %} {
88
{% foreach cases as case, value %}
9-
case {{ case }} = {{ value }};
9+
case {{ case }}{% if backedType %} = {{ value }}{% endif %};
1010
{% endforeach %}
11+
12+
{% if not backedType %}
13+
public static function from(mixed $value): self
14+
{
15+
switch ($value) {
16+
{% foreach cases as case, value %}
17+
case {{ value }}: return self::{{ case }};
18+
{% endforeach %}
19+
default: throw new ValueError(
20+
sprintf('Invalid enum value %s for enum %s', var_export($value, true), self::class)
21+
);
22+
}
23+
}
24+
25+
public function to(): mixed
26+
{
27+
switch ($this) {
28+
{% foreach cases as case, value %}
29+
case self::{{ case }}: return {{ value }};
30+
{% endforeach %}
31+
}
32+
}
33+
{% endif %}
1134
}

src/Templates/Validator/Filter.phptpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// make sure exceptions from the filter are caught and added to the error handling
66
try {
77
$value = \{{ filterClass }}::{{ filterMethod }}($value, {{ filterOptions }});
8-
} catch (\Exception $filterException) {
8+
} catch (\Throwable $filterException) {
99
{% if isTransformingFilter %}
1010
$transformationFailed = true;
1111
{% endif %}

tests/AbstractPHPModelGeneratorTest.php

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -78,19 +78,23 @@ public function tearDown(): void
7878

7979
if ($this->hasFailed()) {
8080
$failedResultDir = FAILED_CLASSES_PATH . preg_replace( '/[^a-z0-9]+/i', '-', $this->getName());
81+
$dir = sys_get_temp_dir() . '/PHPModelGeneratorTest';
82+
83+
foreach (
84+
new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS))
85+
as
86+
$item
87+
) {
88+
$file = (string) $item;
89+
$nestedDir = dirname(str_replace($dir, '', $file));
90+
91+
if (!is_dir($failedResultDir . $nestedDir)) {
92+
@mkdir($failedResultDir . $nestedDir, 0755, true);
93+
}
8194

82-
@mkdir($failedResultDir, 0777, true);
83-
foreach ($this->names as $name) {
84-
copy(
85-
sys_get_temp_dir() . '/PHPModelGeneratorTest/' . $name . '.json',
86-
$failedResultDir . DIRECTORY_SEPARATOR . $name . '.json'
87-
);
88-
}
89-
90-
foreach ($this->generatedFiles as $file) {
9195
copy(
9296
$file,
93-
$failedResultDir . DIRECTORY_SEPARATOR . basename($file)
97+
$failedResultDir . $nestedDir . DIRECTORY_SEPARATOR . basename($file),
9498
);
9599
}
96100
}
@@ -216,8 +220,13 @@ protected function generateClass(
216220
->setOutputEnabled(false);
217221

218222
$baseDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'PHPModelGeneratorTest';
219-
foreach ($this->names as $name) {
220-
unlink($baseDir . DIRECTORY_SEPARATOR . 'Models' . DIRECTORY_SEPARATOR . $name . '.php');
223+
224+
foreach (
225+
new RecursiveIteratorIterator(new RecursiveDirectoryIterator($baseDir, FilesystemIterator::SKIP_DOTS))
226+
as
227+
$item
228+
) {
229+
unlink((string) $item);
221230
}
222231

223232
$className = $this->getClassName();
@@ -468,7 +477,7 @@ protected function getPropertyTypeAnnotation($object, string $property): string
468477
*
469478
* @return string
470479
*/
471-
protected function getMethodReturnTypeAnnotation($object, string $method): string
480+
protected function getReturnTypeAnnotation($object, string $method): string
472481
{
473482
$matches = [];
474483
preg_match(
@@ -489,7 +498,7 @@ protected function getMethodReturnTypeAnnotation($object, string $method): strin
489498
*
490499
* @return string
491500
*/
492-
protected function getMethodParameterTypeAnnotation($object, string $method, int $parameter = 0): string
501+
protected function getParameterTypeAnnotation($object, string $method, int $parameter = 0): string
493502
{
494503
$matches = [];
495504
preg_match_all(

tests/Basic/DefaultValueTest.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,13 @@ public function testTypedPropertyTypeHintsWithImplicitNullEnabledAcceptNull(): v
5555

5656
$this->assertSame('int|null', $this->getPropertyTypeAnnotation($object, 'property'));
5757

58-
$this->assertSame('int|null', $this->getMethodReturnTypeAnnotation($object, 'getProperty'));
58+
$this->assertSame('int|null', $this->getReturnTypeAnnotation($object, 'getProperty'));
5959
$returnType = $this->getReturnType($object, 'getProperty');
6060
$this->assertSame('int', $returnType->getName());
6161
// as implicit null is enabled the default value may be overwritten by a null value
6262
$this->assertTrue($returnType->allowsNull());
6363

64-
$this->assertSame('int|null', $this->getMethodParameterTypeAnnotation($object, 'setProperty'));
64+
$this->assertSame('int|null', $this->getParameterTypeAnnotation($object, 'setProperty'));
6565
$parameterType = $this->getParameterType($object, 'setProperty');
6666
$this->assertSame('int', $parameterType->getName());
6767
// as implicit null is enabled the default value may be overwritten by a null value
@@ -82,12 +82,12 @@ public function testTypedPropertyTypeHintsWithImplicitNullDisabledDeclinesNull()
8282

8383
$this->assertSame('int', $this->getPropertyTypeAnnotation($object, 'property'));
8484

85-
$this->assertSame('int', $this->getMethodReturnTypeAnnotation($object, 'getProperty'));
85+
$this->assertSame('int', $this->getReturnTypeAnnotation($object, 'getProperty'));
8686
$returnType = $this->getReturnType($object, 'getProperty');
8787
$this->assertSame('int', $returnType->getName());
8888
$this->assertFalse($returnType->allowsNull());
8989

90-
$this->assertSame('int', $this->getMethodParameterTypeAnnotation($object, 'setProperty'));
90+
$this->assertSame('int', $this->getParameterTypeAnnotation($object, 'setProperty'));
9191
$parameterType = $this->getParameterType($object, 'setProperty');
9292
$this->assertSame('int', $parameterType->getName());
9393
$this->assertFalse($parameterType->allowsNull());
@@ -223,10 +223,10 @@ public function testUntypedPropertyTypeAnnotationsAreMixed(bool $implicitNull):
223223

224224
$this->assertSame('mixed', $this->getPropertyTypeAnnotation($object, 'property'));
225225

226-
$this->assertSame('mixed', $this->getMethodReturnTypeAnnotation($object, 'getProperty'));
226+
$this->assertSame('mixed', $this->getReturnTypeAnnotation($object, 'getProperty'));
227227
$this->assertNull($this->getReturnType($object, 'getProperty'));
228228

229-
$this->assertSame('mixed', $this->getMethodParameterTypeAnnotation($object, 'setProperty'));
229+
$this->assertSame('mixed', $this->getParameterTypeAnnotation($object, 'setProperty'));
230230
$this->assertNull($this->getParameterType($object, 'setProperty'));
231231
}
232232
}

tests/ComposedValue/ComposedAllOfTest.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -103,23 +103,23 @@ public function testCompositionTypes(bool $implicitNull): void
103103

104104
$this->assertSame(
105105
$implicitNull ? 'int|null' : 'int',
106-
$this->getMethodParameterTypeAnnotation($className, 'setAge')
106+
$this->getParameterTypeAnnotation($className, 'setAge')
107107
);
108108
$setAgeParamType = $this->getParameterType($className, 'setAge');
109109
$this->assertSame('int', $setAgeParamType->getName());
110110
$this->assertSame($implicitNull, $setAgeParamType->allowsNull());
111111

112-
$this->assertSame('string', $this->getMethodParameterTypeAnnotation($className, 'setName'));
112+
$this->assertSame('string', $this->getParameterTypeAnnotation($className, 'setName'));
113113
$setNameParamType = $this->getParameterType($className, 'setName');
114114
$this->assertSame('string', $setNameParamType->getName());
115115
$this->assertFalse($setNameParamType->allowsNull());
116116

117-
$this->assertSame('int|null', $this->getMethodReturnTypeAnnotation($className, 'getAge'));
117+
$this->assertSame('int|null', $this->getReturnTypeAnnotation($className, 'getAge'));
118118
$getAgeReturnType = $this->getReturnType($className, 'getAge');
119119
$this->assertSame('int', $getAgeReturnType->getName());
120120
$this->assertTrue($getAgeReturnType->allowsNull());
121121

122-
$this->assertSame('string', $this->getMethodReturnTypeAnnotation($className, 'getName'));
122+
$this->assertSame('string', $this->getReturnTypeAnnotation($className, 'getName'));
123123
$getNameReturnType = $this->getReturnType($className, 'getName');
124124
$this->assertSame('string', $getNameReturnType->getName());
125125
$this->assertFalse($getNameReturnType->allowsNull());
@@ -145,7 +145,7 @@ public function testAllOfTypePropertyHasTypeAnnotation(): void
145145
$regexp = '/ComposedAllOfTest[\w]*_Merged_[\w]*/';
146146

147147
$this->assertRegExp($regexp, $this->getPropertyTypeAnnotation($object, 'property'));
148-
$this->assertRegExp($regexp, $this->getMethodReturnTypeAnnotation($object, 'getProperty'));
148+
$this->assertRegExp($regexp, $this->getReturnTypeAnnotation($object, 'getProperty'));
149149

150150
// base class, merged property class and two classes for validating the composition components
151151
$this->assertCount(4, $this->getGeneratedFiles());
@@ -624,11 +624,11 @@ public function testIdenticalMergedSchemaIsRedirected(): void
624624

625625
$this->assertRegExp(
626626
'/ComposedAllOfTest_\w+_Merged_CEO\w+\|null$/',
627-
$this->getMethodParameterTypeAnnotation($className, 'setCeo')
627+
$this->getParameterTypeAnnotation($className, 'setCeo')
628628
);
629629
$this->assertSame(
630-
$this->getMethodParameterTypeAnnotation($className, 'setCeo'),
631-
$this->getMethodParameterTypeAnnotation($className, 'setCfo')
630+
$this->getParameterTypeAnnotation($className, 'setCeo'),
631+
$this->getParameterTypeAnnotation($className, 'setCfo')
632632
);
633633
}
634634
}

tests/ComposedValue/ComposedAnyOfTest.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,22 +73,22 @@ public function testCompositionTypes(bool $implicitNull): void
7373
$this->assertSame('int|null', $this->getPropertyTypeAnnotation($className, 'age'));
7474
$this->assertSame('string|null', $this->getPropertyTypeAnnotation($className, 'name'));
7575

76-
$this->assertSame('int|null', $this->getMethodParameterTypeAnnotation($className, 'setAge'));
76+
$this->assertSame('int|null', $this->getParameterTypeAnnotation($className, 'setAge'));
7777
$setAgeParamType = $this->getParameterType($className, 'setAge');
7878
$this->assertSame('int', $setAgeParamType->getName());
7979
$this->assertTrue($setAgeParamType->allowsNull());
8080

81-
$this->assertSame('string|null', $this->getMethodParameterTypeAnnotation($className, 'setName'));
81+
$this->assertSame('string|null', $this->getParameterTypeAnnotation($className, 'setName'));
8282
$setNameParamType = $this->getParameterType($className, 'setName');
8383
$this->assertSame('string', $setNameParamType->getName());
8484
$this->assertTrue($setNameParamType->allowsNull());
8585

86-
$this->assertSame('int|null', $this->getMethodReturnTypeAnnotation($className, 'getAge'));
86+
$this->assertSame('int|null', $this->getReturnTypeAnnotation($className, 'getAge'));
8787
$getAgeReturnType = $this->getReturnType($className, 'getAge');
8888
$this->assertSame('int', $getAgeReturnType->getName());
8989
$this->assertTrue($getAgeReturnType->allowsNull());
9090

91-
$this->assertSame('string|null', $this->getMethodReturnTypeAnnotation($className, 'getName'));
91+
$this->assertSame('string|null', $this->getReturnTypeAnnotation($className, 'getName'));
9292
$getNameReturnType = $this->getReturnType($className, 'getName');
9393
$this->assertSame('string', $getNameReturnType->getName());
9494
$this->assertTrue($getNameReturnType->allowsNull());
@@ -176,7 +176,7 @@ public function testAnyOfTypePropertyHasTypeAnnotation(
176176
$className = $this->generateClassFromFile($schema);
177177

178178
$this->assertRegExp($annotationPattern, $this->getPropertyTypeAnnotation($className, 'property'));
179-
$this->assertRegExp($annotationPattern, $this->getMethodReturnTypeAnnotation($className, 'getProperty'));
179+
$this->assertRegExp($annotationPattern, $this->getReturnTypeAnnotation($className, 'getProperty'));
180180

181181
$this->assertCount($generatedClasses, $this->getGeneratedFiles());
182182
}

0 commit comments

Comments
 (0)