Skip to content

Commit a9bda3c

Browse files
authored
Merge pull request #71 from wol-soft/70-filter-in-composition
Fix issue #70
2 parents e1968a0 + 757eb1d commit a9bda3c

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)