Skip to content

Commit 1553c9c

Browse files
committed
* Implement pattern property accessor test cases including interactions with other post processors and setter methods of mutable objects
* Fix accessing additional properties of an object without an additional properties definition.
1 parent 3b863e4 commit 1553c9c

File tree

10 files changed

+515
-24
lines changed

10 files changed

+515
-24
lines changed

docs/source/generator/postProcessor.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ Generated interface with the **AdditionalPropertiesAccessorPostProcessor**:
136136

137137
**getAdditionalProperty**: Returns the current value of a single additional property. If the requested property doesn't exist null will be returned. Returns as well as *getAdditionalProperties* the processed values.
138138

139-
**setAdditionalProperty**: Adds or updates an additional property. Performs all necessary validations like property names or min and max properties validations will be performed. If the additional properties are processed via a transforming filter an already transformed value will be accepted. If a property which is regularly defined in the schema a *RegularPropertyAsAdditionalPropertyException* will be thrown. If the change is valid and performed also the output of *getRawModelDataInput* will be updated.
139+
**setAdditionalProperty**: Adds or updates an additional property. Performs all necessary validations like property names or min and max properties validations. If the additional properties are processed via a transforming filter an already transformed value will be accepted. If a property which is regularly defined in the schema a *RegularPropertyAsAdditionalPropertyException* will be thrown. If the change is valid and performed also the output of *getRawModelDataInput* will be updated.
140140

141141
**removeAdditionalProperty**: Removes an existing additional property from the model. Returns true if the additional property has been removed, false otherwise (if no additional property with the requested key exists). May throw a *MinPropertiesException* if the change would result in an invalid model state. If the change is valid and performed also the output of *getRawModelDataInput* will be updated.
142142

src/SchemaProcessor/PostProcessor/AdditionalPropertiesAccessorPostProcessor.php

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

55
namespace PHPModelGenerator\SchemaProcessor\PostProcessor;
66

7+
use Exception;
78
use PHPModelGenerator\Exception\Object\MinPropertiesException;
89
use PHPModelGenerator\Exception\Object\RegularPropertyAsAdditionalPropertyException;
910
use PHPModelGenerator\Exception\SchemaException;
@@ -17,6 +18,7 @@
1718
use PHPModelGenerator\Model\SerializedValue;
1819
use PHPModelGenerator\Model\Validator\AdditionalPropertiesValidator;
1920
use PHPModelGenerator\Model\Validator\FilterValidator;
21+
use PHPModelGenerator\Model\Validator\PropertyTemplateValidator;
2022
use PHPModelGenerator\Model\Validator\PropertyValidator;
2123
use PHPModelGenerator\PropertyProcessor\Decorator\TypeHint\ArrayTypeHintDecorator;
2224
use PHPModelGenerator\PropertyProcessor\Decorator\TypeHint\TypeHintDecorator;
@@ -44,11 +46,14 @@ public function __construct(bool $addForModelsWithoutAdditionalPropertiesDefinit
4446
{
4547
$this->addForModelsWithoutAdditionalPropertiesDefinition = $addForModelsWithoutAdditionalPropertiesDefinition;
4648
}
49+
4750
/**
4851
* Add methods to handle additional properties to the provided schema
4952
*
5053
* @param Schema $schema
5154
* @param GeneratorConfiguration $generatorConfiguration
55+
*
56+
* @throws SchemaException
5257
*/
5358
public function process(Schema $schema, GeneratorConfiguration $generatorConfiguration): void
5459
{
@@ -80,13 +85,19 @@ public function process(Schema $schema, GeneratorConfiguration $generatorConfigu
8085
$this->addSetAdditionalPropertyMethod($schema, $generatorConfiguration, $validationProperty);
8186
$this->addRemoveAdditionalPropertyMethod($schema, $generatorConfiguration);
8287
}
88+
89+
if (!isset($json['additionalProperties']) || $json['additionalProperties'] === true) {
90+
$this->addUpdateAdditionalProperties($schema);
91+
}
8392
}
8493

8594
/**
8695
* Adds an array property to the schema which holds all additional properties
8796
*
8897
* @param Schema $schema
8998
* @param PropertyInterface|null $validationProperty
99+
*
100+
* @throws SchemaException
90101
*/
91102
private function addAdditionalPropertiesCollectionProperty(
92103
Schema $schema,
@@ -269,4 +280,48 @@ private function addGetAdditionalPropertyMethod(
269280
)
270281
);
271282
}
283+
284+
/**
285+
* Usually the AdditionalPropertiesValidator validates all additional properties against the constraints and updates
286+
* the internal storage of the additional properties. If no additional property constraints are defined for the
287+
* schema the provided additional properties must be updated separately as no AdditionalPropertiesValidator is added
288+
* to the generated class.
289+
*
290+
* @param Schema $schema
291+
*/
292+
private function addUpdateAdditionalProperties(Schema $schema): void
293+
{
294+
$schema->addBaseValidator(
295+
new class ($schema) extends PropertyTemplateValidator {
296+
public function __construct(Schema $schema)
297+
{
298+
parent::__construct(
299+
new Property($schema->getClassName(), null, $schema->getJsonSchema()),
300+
join(
301+
DIRECTORY_SEPARATOR,
302+
[
303+
'..',
304+
'SchemaProcessor',
305+
'PostProcessor',
306+
'Templates',
307+
'AdditionalProperties',
308+
'UpdateAdditionalProperties.phptpl',
309+
]
310+
),
311+
[
312+
'additionalProperties' => preg_replace(
313+
'(\d+\s=>)',
314+
'',
315+
var_export(
316+
array_keys($schema->getJsonSchema()->getJson()['properties'] ?? []),
317+
true
318+
)
319+
),
320+
],
321+
Exception::class
322+
);
323+
}
324+
}
325+
);
326+
}
272327
}

src/SchemaProcessor/PostProcessor/Templates/AdditionalProperties/GetAdditionalProperty.phptpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*
44
* @param string $property The key of the additional property
55
*
6-
* {% if validationProperty %}{% if viewHelper.getTypeHintAnnotation(validationProperty, true) %}@return {{ viewHelper.getTypeHintAnnotation(validationProperty, true) }}{% endif %}{% endif %}
6+
* {% if validationProperty %}{% if viewHelper.getTypeHintAnnotation(validationProperty, true) %}@return {{ viewHelper.getTypeHintAnnotation(validationProperty, true) }}{% endif %}{% else %}@return mixed{% endif %}
77
*/
88
public function getAdditionalProperty(string $property){% if validationProperty %}{% if validationProperty.getType(true) %}: {{ viewHelper.getType(validationProperty, true) }}{% endif %}{% endif %}
99
{
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
(function () use ($value) {
2+
foreach (array_diff(array_keys($value), {{ additionalProperties }}) as $propertyKey) {
3+
$this->additionalProperties[$propertyKey] = $value[$propertyKey];
4+
}
5+
6+
return false;
7+
})()

tests/AbstractPHPModelGeneratorTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,23 @@ public function setUp(): void
4848
@mkdir(sys_get_temp_dir() . '/PHPModelGeneratorTest/Models');
4949
}
5050

51+
/**
52+
* Polyfill for assertRegEx to avoid warnings during test execution
53+
*
54+
* TODO: remove and switch all calls to assertMatchesRegularExpression when dropping support for PHPUnit < 9.1
55+
* TODO: (dropping support for PHP < 7.4)
56+
*
57+
* @param string $pattern
58+
* @param string $string
59+
* @param string $message
60+
*/
61+
public static function assertRegExp(string $pattern, string $string, string $message = ''): void
62+
{
63+
is_callable([parent::class, 'assertMatchesRegularExpression'])
64+
? parent::assertMatchesRegularExpression($pattern, $string, $message)
65+
: parent::assertRegExp($pattern, $string, $message);
66+
}
67+
5168
/**
5269
* Check if the test has failed. In this case move all JSON files and generated classes in a directory for debugging
5370
*

tests/PostProcessor/AdditionalPropertiesAccessorPostProcessorTest.php

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public function testAdditionalPropertiesAccessorsDependOnConfigurationForAdditio
111111
$this->assertSame('array', $returnType->getName());
112112
$this->assertFalse($returnType->allowsNull());
113113

114-
$this->assertEmpty($this->getMethodReturnTypeAnnotation($object, 'getAdditionalProperty'));
114+
$this->assertSame('mixed', $this->getMethodReturnTypeAnnotation($object, 'getAdditionalProperty'));
115115
$this->assertNull($this->getReturnType($object, 'getAdditionalProperty'));
116116
}
117117
}
@@ -124,13 +124,24 @@ public function testAdditionalPropertiesSettersForMutableObjectsWithoutAdditiona
124124
(new GeneratorConfiguration())->setImmutable(false)
125125
);
126126

127-
$object = new $className();
128-
129-
$this->assertTrue(is_callable([$object, 'setAdditionalProperty']));
130-
$this->assertTrue(is_callable([$object, 'removeAdditionalProperty']));
127+
$object = new $className(['property1' => 100]);
131128

132129
$this->assertSame('mixed', $this->getMethodParameterTypeAnnotation($object, 'setAdditionalProperty', 1));
133130
$this->assertNull($this->getParameterType($object, 'setAdditionalProperty', 1));
131+
$this->assertSame('mixed', $this->getMethodReturnTypeAnnotation($object, 'getAdditionalProperty'));
132+
133+
$this->assertSame(100, $object->getAdditionalProperty('property1'));
134+
$this->assertEqualsCanonicalizing(['property1' => 100], $object->getAdditionalProperties());
135+
136+
$object->setAdditionalProperty('property2', 200);
137+
$this->assertEqualsCanonicalizing(['property1' => 100, 'property2' => 200], $object->getAdditionalProperties());
138+
139+
$object->setAdditionalProperty('property1', 10);
140+
$this->assertEqualsCanonicalizing(['property1' => 10, 'property2' => 200], $object->getAdditionalProperties());
141+
142+
$object->removeAdditionalProperty('property1');
143+
$this->assertEqualsCanonicalizing(['property2' => 200], $object->getAdditionalProperties());
144+
$this->assertNull($object->getAdditionalProperty('property1'));
134145
}
135146

136147
/**
@@ -156,7 +167,10 @@ public function testAdditionalPropertiesAccessorsAreGeneratedForAdditionalProper
156167
$this->assertFalse(is_callable([$object, 'setAdditionalProperty']));
157168
$this->assertFalse(is_callable([$object, 'removeAdditionalProperty']));
158169

159-
$this->assertSame(['property1' => 'Hello', 'property2' => 'World'], $object->getAdditionalProperties());
170+
$this->assertEqualsCanonicalizing(
171+
['property1' => 'Hello', 'property2' => 'World'],
172+
$object->getAdditionalProperties()
173+
);
160174
$this->assertSame('Hello', $object->getAdditionalProperty('property1'));
161175
$this->assertSame('World', $object->getAdditionalProperty('property2'));
162176
$this->assertNull($object->getAdditionalProperty('property3'));
@@ -187,11 +201,11 @@ public function testAdditionalPropertiesModifiersAreGeneratedForMutableObjects()
187201

188202
// test adding a new additional property
189203
$object->setAdditionalProperty('property3', ' Good night ');
190-
$this->assertSame(
204+
$this->assertEqualsCanonicalizing(
191205
['property1' => 'Hello', 'property2' => 'World', 'property3' => 'Good night'],
192206
$object->getAdditionalProperties()
193207
);
194-
$this->assertSame(
208+
$this->assertEqualsCanonicalizing(
195209
['property1' => ' Hello ', 'property2' => 'World', 'property3' => ' Good night '],
196210
$object->getRawModelDataInput()
197211
);
@@ -200,22 +214,22 @@ public function testAdditionalPropertiesModifiersAreGeneratedForMutableObjects()
200214
// test removing an additional property
201215
$this->assertTrue($object->removeAdditionalProperty('property2'));
202216
$this->assertFalse($object->removeAdditionalProperty('property2'));
203-
$this->assertSame(
217+
$this->assertEqualsCanonicalizing(
204218
['property1' => 'Hello', 'property3' => 'Good night'],
205219
$object->getAdditionalProperties()
206220
);
207-
$this->assertSame(
221+
$this->assertEqualsCanonicalizing(
208222
['property1' => ' Hello ', 'property3' => ' Good night '],
209223
$object->getRawModelDataInput()
210224
);
211225

212226
// test update an existing additional property
213227
$object->setAdditionalProperty('property3', ' !Good night! ');
214-
$this->assertSame(
228+
$this->assertEqualsCanonicalizing(
215229
['property1' => 'Hello', 'property3' => '!Good night!'],
216230
$object->getAdditionalProperties()
217231
);
218-
$this->assertSame(
232+
$this->assertEqualsCanonicalizing(
219233
['property1' => ' Hello ', 'property3' => ' !Good night! '],
220234
$object->getRawModelDataInput()
221235
);
@@ -363,7 +377,10 @@ public function testAdditionalPropertiesAreSerialized(bool $implicitNull): void
363377
$this->assertInstanceOf(DateTime::class, $object->getAdditionalProperty('end'));
364378
$this->assertInstanceOf(DateTime::class, $object->getAdditionalProperties()['end']);
365379

366-
$this->assertSame(['name' => 'Late autumn', 'start' => '20201010', 'end' => '20201212'], $object->toArray());
380+
$this->assertEqualsCanonicalizing(
381+
['name' => 'Late autumn', 'start' => '20201010', 'end' => '20201212'],
382+
$object->toArray()
383+
);
367384

368385
// test adding a transformed value
369386
$object->setAdditionalProperty('now', new DateTime());

0 commit comments

Comments
 (0)