Skip to content

Commit 6a997b6

Browse files
committed
Add test cases for redirection of identical enums
Fix schema redirection for objects with different orders
1 parent 30836bb commit 6a997b6

File tree

8 files changed

+205
-29
lines changed

8 files changed

+205
-29
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
}
1212
],
1313
"require": {
14+
"symfony/polyfill-php81": "^1.28",
1415
"wol-soft/php-json-schema-model-generator-production": "dev-enumPostProcessor",
1516
"wol-soft/php-micro-template": "^1.9.0",
1617

src/Model/SchemaDefinition/JsonSchema.php

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace PHPModelGenerator\Model\SchemaDefinition;
66

7+
use PHPModelGenerator\Utils\ArrayHash;
8+
79
/**
810
* Class JsonSchema
911
*
@@ -62,11 +64,7 @@ public function getJson(): array
6264
*/
6365
public function getSignature(): string
6466
{
65-
return md5(
66-
json_encode(
67-
array_intersect_key($this->json, array_fill_keys(self::SCHEMA_SIGNATURE_RELEVANT_FIELDS, null))
68-
)
69-
);
67+
return ArrayHash::hash($this->json, self::SCHEMA_SIGNATURE_RELEVANT_FIELDS);
7068
}
7169

7270
/**

src/SchemaProcessor/PostProcessor/EnumPostProcessor.php

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use PHPModelGenerator\Model\Validator\FilterValidator;
1818
use PHPModelGenerator\ModelGenerator;
1919
use PHPModelGenerator\PropertyProcessor\Filter\FilterProcessor;
20+
use PHPModelGenerator\Utils\ArrayHash;
2021

2122
/**
2223
* Generates a PHP enum for enums from JSON schemas which are automatically mapped for properties holding the enum
@@ -75,19 +76,22 @@ public function process(Schema $schema, GeneratorConfiguration $generatorConfigu
7576
$this->checkForExistingTransformingFilter($property);
7677

7778
$values = $json['enum'];
78-
sort($values);
79-
$hash = md5(print_r($values, true));
80-
81-
if (!isset($this->generatedEnums[$hash])) {
82-
$this->generatedEnums[$hash] = $this->renderEnum(
83-
$generatorConfiguration,
84-
$json['$id'] ?? $schema->getClassName() . ucfirst($property->getName()),
85-
$values,
86-
$json['enum-map'] ?? null
87-
);
79+
$enumSignature = ArrayHash::hash($json, ['enum', 'enum-map', '$id']);
80+
$enumName = $json['$id'] ?? $schema->getClassName() . ucfirst($property->getName());
81+
82+
if (!isset($this->generatedEnums[$enumSignature])) {
83+
$this->generatedEnums[$enumSignature] = [
84+
'name' => $enumName,
85+
'fqcn' => $this->renderEnum($generatorConfiguration, $enumName, $values, $json['enum-map'] ?? null),
86+
];
87+
} else {
88+
if ($generatorConfiguration->isOutputEnabled()) {
89+
echo "Duplicated signature $enumSignature for enum $enumName." .
90+
" Redirecting to {$this->generatedEnums[$enumSignature]['name']}\n";
91+
}
8892
}
8993

90-
$fqcn = $this->generatedEnums[$hash];
94+
$fqcn = $this->generatedEnums[$enumSignature]['fqcn'];
9195
$name = substr($fqcn, strrpos($fqcn, "\\") + 1);
9296

9397
$inputType = $property->getType();
@@ -168,18 +172,22 @@ private function validateEnum(PropertyInterface $property): bool
168172
$throw('Unmapped enum %s in file %s');
169173
}
170174

171-
if (isset($json['enum-map']) &&
172-
(
173-
!is_array($json['enum-map'])
174-
|| $this->getArrayTypes(array_keys($json['enum-map'])) !== ['string']
175-
|| count(array_uintersect(
176-
$json['enum-map'],
177-
$json['enum'],
178-
function ($a, $b): int { return $a === $b ? 0 : 1; }
179-
)) !== count($json['enum'])
180-
)
181-
) {
182-
$throw('invalid enum map %s in file %s');
175+
if (isset($json['enum-map'])) {
176+
asort($json['enum']);
177+
if (is_array($json['enum-map'])) {
178+
asort($json['enum-map']);
179+
}
180+
181+
if (!is_array($json['enum-map'])
182+
|| $this->getArrayTypes(array_keys($json['enum-map'])) !== ['string']
183+
|| count(array_uintersect(
184+
$json['enum-map'],
185+
$json['enum'],
186+
function ($a, $b): int { return $a === $b ? 0 : 1; }
187+
)) !== count($json['enum'])
188+
) {
189+
$throw('invalid enum map %s in file %s');
190+
}
183191
}
184192

185193
return true;
@@ -213,6 +221,11 @@ private function renderEnum(
213221
case ['integer']: $backedType = 'int'; break;
214222
}
215223

224+
// make sure different enums with an identical name don't overwrite each other
225+
while (in_array("$this->namespace\\$name", array_column($this->generatedEnums, 'fqcn'))) {
226+
$name .= '_1';
227+
}
228+
216229
file_put_contents(
217230
$this->targetDirectory . DIRECTORY_SEPARATOR . $name . '.php',
218231
$this->renderer->renderTemplate(

src/Utils/ArrayHash.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PHPModelGenerator\Utils;
6+
7+
class ArrayHash
8+
{
9+
public static function hash(array $array, array $relevantFields = []): string
10+
{
11+
return md5(json_encode(self::filter($array, $relevantFields)));
12+
}
13+
14+
private static function filter(array $array, array $relevantFields): array
15+
{
16+
if ($relevantFields) {
17+
foreach ($array as $key => $_) {
18+
if (!in_array($key, $relevantFields)) {
19+
unset($array[$key]);
20+
}
21+
}
22+
}
23+
24+
self::array_multiksort($array);
25+
26+
return $array;
27+
}
28+
29+
private static function array_multiksort(array &$array): void
30+
{
31+
foreach ($array as &$value) {
32+
if (is_array($value)) {
33+
self::array_multiksort($value);
34+
}
35+
}
36+
37+
array_is_list($array) ? sort($array) : ksort($array);
38+
}
39+
}

tests/PostProcessor/EnumPostProcessorTest.php

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
use ReflectionEnum;
1616
use UnitEnum;
1717

18-
// TODO: multiple enums, enum redirect
1918
class EnumPostProcessorTest extends AbstractPHPModelGeneratorTest
2019
{
2120
/**
@@ -363,6 +362,100 @@ public function testEnumPropertyWithTransformingFilterThrowsAnException(): void
363362
$this->generateClassFromFile('EnumPropertyWithTransformingFilter.json');
364363
}
365364

365+
/**
366+
* @dataProvider identicalEnumsDataProvider
367+
*/
368+
public function testIdenticalEnumsAreMappedToOneEnum(string $file, array $enums): void
369+
{
370+
$this->addPostProcessor();
371+
372+
$className = $this->generateClassFromFileTemplate(
373+
$file,
374+
$enums,
375+
(new GeneratorConfiguration())->setImmutable(false)->setCollectErrors(false),
376+
false
377+
);
378+
379+
$this->includeGeneratedEnums(1);
380+
381+
$object = new $className(['property1' => 'Hans', 'property2' => 'Dieter']);
382+
$this->assertSame('Hans', $object->getProperty1()->value);
383+
$this->assertSame('Dieter', $object->getProperty2()->value);
384+
385+
$this->assertSame(get_class($object->getProperty1()), get_class($object->getProperty2()));
386+
}
387+
388+
public function identicalEnumsDataProvider(): array
389+
{
390+
return [
391+
'simple enum' => [
392+
'MultipleEnumProperties.json',
393+
['["Hans", "Dieter"]', '["Dieter", "Hans"]'],
394+
],
395+
'mapped enum' => [
396+
'MultipleEnumPropertiesMapped.json',
397+
[
398+
'"names"', '["Hans", "Dieter"]', '{"a": "Hans", "b": "Dieter"}',
399+
'"names"', '["Dieter", "Hans"]', '{"b": "Dieter", "a": "Hans"}',
400+
],
401+
],
402+
];
403+
}
404+
405+
/**
406+
* @dataProvider differentEnumsDataProvider
407+
*/
408+
public function testDifferentEnumsAreNotMappedToOneEnum(string $file, array $enums): void
409+
{
410+
$this->addPostProcessor();
411+
412+
$className = $this->generateClassFromFileTemplate(
413+
$file,
414+
$enums,
415+
(new GeneratorConfiguration())->setImmutable(false)->setCollectErrors(false),
416+
false
417+
);
418+
419+
$this->includeGeneratedEnums(2);
420+
$object = new $className(['property1' => 'Hans', 'property2' => 'Dieter']);
421+
422+
$this->assertSame('Hans', $object->getProperty1()->value);
423+
$this->assertSame('Dieter', $object->getProperty2()->value);
424+
425+
$this->assertNotSame(get_class($object->getProperty1()), get_class($object->getProperty2()));
426+
}
427+
428+
public function differentEnumsDataProvider(): array
429+
{
430+
return [
431+
'different values' => [
432+
'MultipleEnumProperties.json',
433+
['["Hans", "Dieter"]', '["Dieter", "Anna"]'],
434+
],
435+
'different $id' => [
436+
'MultipleEnumPropertiesMapped.json',
437+
[
438+
'"names"', '["Hans", "Dieter"]', '{"a": "Hans", "b": "Dieter"}',
439+
'"attendees"', '["Hans", "Dieter"]', '{"a": "Hans", "b": "Dieter"}',
440+
],
441+
],
442+
'different values mapped enum' => [
443+
'MultipleEnumPropertiesMapped.json',
444+
[
445+
'"names"', '["Hans", "Anna"]', '{"a": "Hans", "b": "Anna"}',
446+
'"names"', '["Hans", "Dieter"]', '{"a": "Hans", "b": "Dieter"}',
447+
],
448+
],
449+
'different mapping' => [
450+
'MultipleEnumPropertiesMapped.json',
451+
[
452+
'"names"', '["Hans", "Dieter"]', '{"a": "Hans", "b": "Dieter"}',
453+
'"names"', '["Hans", "Dieter"]', '{"a": "Dieter", "b": "Hans"}',
454+
],
455+
],
456+
];
457+
}
458+
366459
private function addPostProcessor(): void
367460
{
368461
$this->modifyModelGenerator = static function (ModelGenerator $generator): void {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"type": "object",
3+
"properties": {
4+
"property1": {
5+
"enum": %s
6+
},
7+
"property2": {
8+
"enum": %s
9+
}
10+
}
11+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"type": "object",
3+
"properties": {
4+
"property1": {
5+
"$id": %s,
6+
"enum": %s,
7+
"enum-map": %s
8+
},
9+
"property2": {
10+
"$id": %s,
11+
"enum": %s,
12+
"enum-map": %s
13+
}
14+
}
15+
}

tests/Schema/IdenticalNestedSchemaTest/IdenticalSubSchema.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
"object1": {
55
"type": "object",
66
"properties": {
7+
"property2": {
8+
"type": "integer"
9+
},
710
"property1": {
811
"type": "string"
912
}
@@ -17,6 +20,9 @@
1720
"properties": {
1821
"property1": {
1922
"type": "string"
23+
},
24+
"property2": {
25+
"type": "integer"
2026
}
2127
},
2228
"required": [

0 commit comments

Comments
 (0)