Skip to content

Commit 757eb1d

Browse files
committed
Fix issue #70
Merged properties of a composition are filled up with the provided values without further validation execution after the single composition branches are validated separately. Consequently,changes applied to the values from filters are missing. The new added method _getModifiedValues sets up a diff with all changed values from the nested composition branch. All modified values are applied to the merged property afterwards so changes from filters are kept.
1 parent e1968a0 commit 757eb1d

File tree

5 files changed

+179
-1
lines changed

5 files changed

+179
-1
lines changed

src/PropertyProcessor/ComposedValue/AbstractComposedValueProcessor.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace PHPModelGenerator\PropertyProcessor\ComposedValue;
66

77
use PHPModelGenerator\Exception\SchemaException;
8+
use PHPModelGenerator\Model\MethodInterface;
89
use PHPModelGenerator\Model\Property\CompositionPropertyDecorator;
910
use PHPModelGenerator\Model\Property\Property;
1011
use PHPModelGenerator\Model\Property\PropertyInterface;
@@ -117,6 +118,10 @@ function () use (&$resolvedCompositions, $property, $compositionProperties, $pro
117118
),
118119
100
119120
);
121+
122+
if (!$this->schema->hasMethod('_getModifiedValues')) {
123+
$this->addGetModifiedValuesMethodToSchema($compositionProperties);
124+
}
120125
}
121126

122127
/**
@@ -339,6 +344,64 @@ function () use ($property, $mergedPropertySchema): void {
339344
}
340345
}
341346

347+
/**
348+
* Add a method to the schema to gather values from a nested object which are modified. This is required to adopt
349+
* filter changes to the values which are passed into a merged property
350+
*
351+
* @param CompositionPropertyDecorator[] $compositionProperties
352+
*/
353+
private function addGetModifiedValuesMethodToSchema(array $compositionProperties): void
354+
{
355+
$this->schema->addMethod('_getModifiedValues', new class ($compositionProperties) implements MethodInterface {
356+
/** @var CompositionPropertyDecorator[] $compositionProperties */
357+
private $compositionProperties;
358+
359+
public function __construct(array $compositionProperties)
360+
{
361+
$this->compositionProperties = $compositionProperties;
362+
}
363+
364+
public function getCode(): string
365+
{
366+
$defaultValueMap = [];
367+
$propertyAccessors = [];
368+
foreach ($this->compositionProperties as $compositionProperty) {
369+
if (!$compositionProperty->getNestedSchema()) {
370+
continue;
371+
}
372+
373+
foreach ($compositionProperty->getNestedSchema()->getProperties() as $property) {
374+
$propertyAccessors[$property->getName()] = 'get' . ucfirst($property->getAttribute());
375+
376+
if ($property->getDefaultValue() !== null) {
377+
$defaultValueMap[] = $property->getName();
378+
}
379+
}
380+
}
381+
382+
return sprintf('
383+
private function _getModifiedValues(array $originalModelData, object $nestedCompositionObject): array {
384+
$modifiedValues = [];
385+
$defaultValueMap = %s;
386+
387+
foreach (%s as $key => $accessor) {
388+
if ((isset($originalModelData[$key]) || in_array($key, $defaultValueMap))
389+
&& method_exists($nestedCompositionObject, $accessor)
390+
&& ($modifiedValue = $nestedCompositionObject->$accessor()) !== ($originalModelData[$key] ?? !$modifiedValue)
391+
) {
392+
$modifiedValues[$key] = $modifiedValue;
393+
}
394+
}
395+
396+
return $modifiedValues;
397+
}',
398+
var_export($defaultValueMap, true),
399+
var_export($propertyAccessors, true)
400+
);
401+
}
402+
});
403+
}
404+
342405
/**
343406
* @param int $composedElements The amount of elements which are composed together
344407
*

src/Templates/Model.phptpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ declare(strict_types = 1);
2020
{% if schema.getDescription() %} * {{ schema.getDescription() }}
2121
*{% endif %}
2222
* This is an auto-implemented class implemented by the php-json-schema-model-generator.
23-
* If you need to implement something in this class use inheritance. Else you will loose your changes if the classes
23+
* If you need to implement something in this class use inheritance. Else you will lose your changes if the classes
2424
* are re-generated.
2525
*/
2626
class {{ class }} {% if schema.getInterfaces() %}implements {{ viewHelper.joinClassNames(schema.getInterfaces()) }}{% endif %}

src/Templates/Validator/ComposedItem.phptpl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
$validatorComponentIndex = 0;
1313
$originalModelData = $value;
1414
$proposedValue = null;
15+
$modifiedValues = [];
1516

1617
{% if not generatorConfiguration.isImmutable() %}
1718
$originalPropertyValidationState = $this->_propertyValidationState ?? [];
@@ -90,6 +91,9 @@
9091
$proposedValue = $proposedValue ?? $value;
9192
{% endif %}
9293

94+
if (is_object($value)) {
95+
$modifiedValues = array_merge($modifiedValues, $this->_getModifiedValues($originalModelData, $value));
96+
}
9397
{% if not generatorConfiguration.isImmutable() %}
9498
{% if not generatorConfiguration.collectErrors() %}
9599
if (isset($validatorIndex)) {
@@ -118,6 +122,10 @@
118122

119123
{% if mergedProperty %}
120124
if (is_object($proposedValue)) {
125+
if ($modifiedValues) {
126+
$value = array_merge($value, $modifiedValues);
127+
}
128+
121129
{{ viewHelper.resolvePropertyDecorator(mergedProperty) }}
122130
} else {
123131
$value = $proposedValue;

tests/Issues/Issue/Issue70Test.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PHPModelGenerator\Tests\Issues\Issue;
6+
7+
use PHPModelGenerator\Filter\TransformingFilterInterface;
8+
use PHPModelGenerator\Model\GeneratorConfiguration;
9+
use PHPModelGenerator\Tests\Issues\AbstractIssueTest;
10+
11+
class Issue70Test extends AbstractIssueTest
12+
{
13+
/**
14+
* @dataProvider validInputDataProvider
15+
*/
16+
public function testValidInput(string $filter, array $input, $expectedOutput): void
17+
{
18+
$className = $this->generateClassFromFileTemplate(
19+
'filterInCompositionInArray.json',
20+
[$filter],
21+
(new GeneratorConfiguration())->addFilter($this->getFilter())
22+
);
23+
24+
$object = new $className($input);
25+
26+
$this->assertCount(1, $object->getItems());
27+
$this->assertSame('Hello', $object->getItems()[0]->getTitle());
28+
$this->assertSame($expectedOutput, $object->getItems()[0]->getProperty());
29+
}
30+
31+
public function validInputDataProvider(): array
32+
{
33+
return [
34+
'basic filter - default value' => ['trim', ['items' => [['title' => 'Hello']]], 'now'],
35+
'basic filter - custom value - not modified' => ['trim', ['items' => [['title' => 'Hello', 'property' => 'later']]], 'later'],
36+
'basic filter - custom value - modified' => ['trim', ['items' => [['title' => 'Hello', 'property' => ' later ']]], 'later'],
37+
'basic filter - null' => ['trim', ['items' => [['title' => 'Hello', 'property' => null]]], null],
38+
'transforming filter - default value' => ['countChars', ['items' => [['title' => 'Hello']]], 3],
39+
'transforming filter - transformed value' => ['countChars', ['items' => [['title' => 'Hello', 'property' => 5]]], 5],
40+
'transforming filter - custom value' => ['countChars', ['items' => [['title' => 'Hello', 'property' => 'Hello World']]], 11],
41+
'transforming filter - null' => ['countChars', ['items' => [['title' => 'Hello', 'property' => null]]], null],
42+
];
43+
}
44+
45+
public function getFilter(): TransformingFilterInterface
46+
{
47+
return new class () implements TransformingFilterInterface {
48+
public function getAcceptedTypes(): array
49+
{
50+
return ['string', 'null'];
51+
}
52+
53+
public function getToken(): string
54+
{
55+
return 'countChars';
56+
}
57+
58+
public function getFilter(): array
59+
{
60+
return [Issue70Test::class, 'filter'];
61+
}
62+
63+
public function getSerializer(): array
64+
{
65+
return [Issue70Test::class, 'filter'];
66+
}
67+
};
68+
}
69+
70+
public static function filter(?string $input): ?int
71+
{
72+
return $input === null ? null : strlen($input);
73+
}
74+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"type": "object",
3+
"properties": {
4+
"items": {
5+
"type": "array",
6+
"items":{
7+
"allOf": [
8+
{
9+
"type": "object",
10+
"properties": {
11+
"title": {
12+
"type": "string"
13+
}
14+
},
15+
"required": [
16+
"title"
17+
]
18+
},
19+
{
20+
"type": "object",
21+
"properties": {
22+
"property": {
23+
"type": "string",
24+
"filter": "%s",
25+
"default": "now"
26+
}
27+
}
28+
}
29+
]
30+
}
31+
}
32+
}
33+
}

0 commit comments

Comments
 (0)