Skip to content

Commit 12d69d2

Browse files
author
Enno Woortmann
committed
Add initial enum post processor implementation to convert JSON Schema enums into PHP Enums usable with PHP >= 8.1
1 parent 0cadce7 commit 12d69d2

File tree

12 files changed

+214
-13
lines changed

12 files changed

+214
-13
lines changed

src/Model/GeneratorConfiguration.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace PHPModelGenerator\Model;
66

7+
use Exception;
78
use PHPModelGenerator\Exception\InvalidFilterException;
89
use PHPModelGenerator\Filter\FilterInterface;
910
use PHPModelGenerator\Filter\TransformingFilterInterface;
@@ -68,6 +69,7 @@ public function __construct()
6869
*
6970
* @return $this
7071
*
72+
* @throws Exception
7173
* @throws InvalidFilterException
7274
*/
7375
public function addFilter(FilterInterface ...$additionalFilter): self
@@ -93,6 +95,10 @@ public function addFilter(FilterInterface ...$additionalFilter): self
9395
}
9496
}
9597

98+
if (isset($this->filter[$filter->getToken()])) {
99+
throw new Exception("duplicate filter token {$filter->getToken()}");
100+
}
101+
96102
$this->filter[$filter->getToken()] = $filter;
97103
}
98104

src/Model/Property/Property.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -277,9 +277,9 @@ public function setReadOnly(bool $isPropertyReadOnly): PropertyInterface
277277
/**
278278
* @inheritdoc
279279
*/
280-
public function setDefaultValue($defaultValue): PropertyInterface
280+
public function setDefaultValue($defaultValue, bool $raw = false): PropertyInterface
281281
{
282-
$this->defaultValue = $defaultValue;
282+
$this->defaultValue = $defaultValue !== null && !$raw ? var_export($defaultValue, true) : $defaultValue;
283283

284284
return $this;
285285
}
@@ -289,7 +289,7 @@ public function setDefaultValue($defaultValue): PropertyInterface
289289
*/
290290
public function getDefaultValue(): ?string
291291
{
292-
return $this->defaultValue !== null ? var_export($this->defaultValue, true) : null;
292+
return $this->defaultValue;
293293
}
294294

295295
/**

src/Model/Property/PropertyInterface.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,12 @@ public function setInternal(bool $isPropertyInternal): PropertyInterface;
158158

159159
/**
160160
* @param mixed $defaultValue
161+
* @param bool $raw By default, the provided value will be added to the generated code via var_export. If the raw
162+
* option is enabled the value provided in $defaultValue will not be changed.
161163
*
162164
* @return PropertyInterface
163165
*/
164-
public function setDefaultValue($defaultValue): PropertyInterface;
166+
public function setDefaultValue($defaultValue, bool $raw = false): PropertyInterface;
165167

166168
/**
167169
* @return string|null

src/Model/Property/PropertyProxy.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,9 +198,9 @@ public function isReadOnly(): bool
198198
/**
199199
* @inheritdoc
200200
*/
201-
public function setDefaultValue($defaultValue): PropertyInterface
201+
public function setDefaultValue($defaultValue, bool $raw = false): PropertyInterface
202202
{
203-
return $this->getProperty()->setDefaultValue($defaultValue);
203+
return $this->getProperty()->setDefaultValue($defaultValue, $raw);
204204
}
205205

206206
/**

src/Model/Validator/EnumValidator.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ class EnumValidator extends PropertyValidator
2323
*/
2424
public function __construct(PropertyInterface $property, array $allowedValues)
2525
{
26-
2726
parent::__construct(
2827
$property,
2928
'!in_array($value, ' . RenderHelper::varExportArray($allowedValues) . ', true)',
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace PHPModelGenerator\SchemaProcessor\PostProcessor;
6+
7+
use BackedEnum;
8+
use PHPModelGenerator\Filter\TransformingFilterInterface;
9+
10+
class EnumFilter implements TransformingFilterInterface
11+
{
12+
public function getAcceptedTypes(): array
13+
{
14+
return ['string', 'null'];
15+
}
16+
17+
public function getToken(): string
18+
{
19+
return 'php_model_generator_enum';
20+
}
21+
22+
public function getFilter(): array
23+
{
24+
return [self::class, 'filter'];
25+
}
26+
27+
public function getSerializer(): array
28+
{
29+
return [self::class, 'serialize'];
30+
}
31+
32+
// TODO: move to production lib
33+
public static function filter($value, $options): ?BackedEnum
34+
{
35+
return $value === null ? null : $options['fqcn']::from($value);
36+
}
37+
38+
public static function serialize(?BackedEnum $value): null | int | string
39+
{
40+
return $value?->value;
41+
}
42+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace PHPModelGenerator\SchemaProcessor\PostProcessor;
6+
7+
use PHPMicroTemplate\Render;
8+
use PHPModelGenerator\Exception\SchemaException;
9+
use PHPModelGenerator\Filter\TransformingFilterInterface;
10+
use PHPModelGenerator\Model\GeneratorConfiguration;
11+
use PHPModelGenerator\Model\Property\PropertyInterface;
12+
use PHPModelGenerator\Model\Property\PropertyType;
13+
use PHPModelGenerator\Model\Schema;
14+
use PHPModelGenerator\Model\Validator;
15+
use PHPModelGenerator\Model\Validator\EnumValidator;
16+
use PHPModelGenerator\Model\Validator\FilterValidator;
17+
use PHPModelGenerator\ModelGenerator;
18+
use PHPModelGenerator\PropertyProcessor\Filter\FilterProcessor;
19+
20+
class EnumPostProcessor extends PostProcessor
21+
{
22+
/** @var string */
23+
private $namespace;
24+
/** @var Render */
25+
private $renderer;
26+
/** @var string */
27+
private $targetDirectory;
28+
29+
private $hasAddedFilter = false;
30+
31+
public function __construct(string $targetDirectory, string $namespace)
32+
{
33+
if (PHP_VERSION_ID < 80100) {
34+
throw new \Exception('Enumerations are only allowed since PHP 8.1');
35+
}
36+
37+
(new ModelGenerator())->generateModelDirectory($targetDirectory);
38+
39+
$this->renderer = new Render(__DIR__ . DIRECTORY_SEPARATOR . 'Templates' . DIRECTORY_SEPARATOR);
40+
$this->namespace = trim($namespace, '\\');
41+
$this->targetDirectory = $targetDirectory;
42+
}
43+
44+
public function process(Schema $schema, GeneratorConfiguration $generatorConfiguration): void
45+
{
46+
if (!$this->hasAddedFilter) {
47+
$generatorConfiguration->addFilter(new EnumFilter());
48+
$this->hasAddedFilter = true;
49+
}
50+
51+
foreach ($schema->getProperties() as $property) {
52+
if (!isset($property->getJsonSchema()->getJson()['enum'])) {
53+
continue;
54+
}
55+
56+
$this->checkForExistingTransformingFilter($property);
57+
58+
$values = $property->getJsonSchema()->getJson()['enum'];
59+
sort($values);
60+
61+
$name = $schema->getClassName() . ucfirst($property->getName());
62+
file_put_contents(
63+
$this->targetDirectory . DIRECTORY_SEPARATOR . $name . '.php',
64+
$this->renderer->renderTemplate(
65+
'Enum.phptpl',
66+
[
67+
'namespace' => $this->namespace,
68+
'name' => $name,
69+
'backedType' => 'string',
70+
'cases' => array_combine(
71+
$values,
72+
array_map(
73+
static function ($value): string {
74+
return var_export($value, true);
75+
},
76+
$values
77+
)
78+
),
79+
]
80+
)
81+
);
82+
83+
$fqcn = "$this->namespace\\$name";
84+
if ($generatorConfiguration->isOutputEnabled()) {
85+
echo "Rendered enum $fqcn\n";
86+
}
87+
88+
$inputType = $property->getType();
89+
90+
(new FilterProcessor())->process(
91+
$property,
92+
['filter' => 'php_model_generator_enum', 'fqcn' => $fqcn],
93+
$generatorConfiguration,
94+
$schema
95+
);
96+
97+
$schema->addUsedClass($fqcn);
98+
$property->setType($inputType, new PropertyType($name, !$property->isRequired()));
99+
100+
if ($property->getDefaultValue() && in_array($property->getJsonSchema()->getJson()['default'], $values)) {
101+
$property->setDefaultValue("$name::{$property->getJsonSchema()->getJson()['default']}", true);
102+
}
103+
104+
// remove the enum validator as the validation is performed by the PHP enum
105+
$property->filterValidators(static function (Validator $validator): bool {
106+
echo "filter validator: " . $validator->getValidator()::class . PHP_EOL;
107+
return !is_a($validator->getValidator(), EnumValidator::class);
108+
});
109+
}
110+
}
111+
112+
/**
113+
* @throws SchemaException
114+
*/
115+
private function checkForExistingTransformingFilter(PropertyInterface $property): void
116+
{
117+
foreach ($property->getValidators() as $validator) {
118+
$validator = $validator->getValidator();
119+
120+
if ($validator instanceof FilterValidator
121+
&& $validator->getFilter() instanceof TransformingFilterInterface
122+
) {
123+
throw new SchemaException("Can't apply enum filter to an already transformed value");
124+
}
125+
}
126+
}
127+
128+
public function postProcess(): void
129+
{
130+
$this->hasAddedFilter = false;
131+
132+
parent::postProcess();
133+
}
134+
}

src/SchemaProcessor/PostProcessor/PatternPropertiesAccessorPostProcessor.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public function process(Schema $schema, GeneratorConfiguration $generatorConfigu
3838

3939
foreach ($schema->getBaseValidators() as $validator) {
4040
if (is_a($validator, PatternPropertiesValidator::class)) {
41-
array_push($patternTypes, $validator->getValidationProperty()->getType(true));
41+
$patternTypes[] = $validator->getValidationProperty()->getType(true);
4242
}
4343
}
4444

@@ -97,7 +97,7 @@ static function (bool $carry, PropertyType $type): bool {
9797
);
9898

9999
if ($nullable) {
100-
array_push($baseTypes, 'null');
100+
$baseTypes[] = 'null';
101101
}
102102

103103
return join('[]|', $baseTypes) . '[]';

src/SchemaProcessor/PostProcessor/RenderedMethod.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,4 @@ protected function getRenderer(): Render
7878

7979
return self::$renderer;
8080
}
81-
}
81+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace {{ namespace }};
6+
7+
enum {{ name }}{% if backedType %}: {{ backedType }}{% endif %} {
8+
{% foreach cases as case, value %}
9+
case {{ case }}{% if backedType %} = {{ value }}{% endif %};
10+
{% endforeach %}
11+
}

0 commit comments

Comments
 (0)