Skip to content

Commit 632e4d7

Browse files
authored
Additional property post processor (#17)
Added additional property post processor to handle additional properties
1 parent 6c82abe commit 632e4d7

33 files changed

+902
-52
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
],
1313
"require": {
1414
"symplify/easy-coding-standard": "^7.2.3",
15-
"wol-soft/php-json-schema-model-generator-production": "^0.12.2",
15+
"wol-soft/php-json-schema-model-generator-production": "^0.13.0",
1616
"wol-soft/php-micro-template": "^1.3.2",
1717

1818
"php": ">=7.2",

docs/source/complexTypes/object.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,10 @@ Additional Properties
184184

185185
Using the keyword `additionalProperties` the object can be limited to not contain any additional properties by providing `false`. If a schema is provided all additional properties must be valid against the provided schema. Simple checks like 'must provide a string' are possible as well as checks like 'must contain an object with a specific structure'.
186186

187+
.. hint::
188+
189+
If you define constraints via `additionalProperties` you may want to use the `AdditionalPropertiesAccessorPostProcessor <../generator/postProcessor.html#additionalpropertiesaccessorpostprocessor>`__ to access and modify your additional properties.
190+
187191
.. code-block:: json
188192
189193
{
@@ -243,6 +247,10 @@ The thrown exception will be a *PHPModelGenerator\\Exception\\Object\\InvalidAdd
243247
// get the value provided to the property
244248
public function getProvidedValue()
245249
250+
.. warning::
251+
252+
The validation of additional properties is independently from the `implicit null <../gettingStarted.html#implicit-null>`__ setting. If you require your additional properties to accept null define a `multi type <multiType.html>`__ with explicit null.
253+
246254
Recursive Objects
247255
-----------------
248256

docs/source/generator/postProcessor.rst

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ Builtin Post Processors
1919
PopulatePostProcessor
2020
^^^^^^^^^^^^^^^^^^^^^
2121

22+
.. code-block:: php
23+
24+
$generator = new ModelGenerator();
25+
$generator->addPostProcessor(new PopulatePostProcessor());
26+
2227
The **PopulatePostProcessor** adds a populate method to your generated model. The populate method accepts an array which might contain any subset of the model's properties. All properties present in the provided array will be validated according to the validation rules from the JSON-Schema. If all values are valid the properties will be updated otherwise an exception will be thrown (if error collection is enabled an exception containing all violations, otherwise on the first occurring error, compare `collecting errors <../gettingStarted.html#collect-errors-vs-early-return>`__). Also basic model constraints like `minProperties`, `maxProperties` or `propertyNames` will be validated as the provided array may add additional properties to the model. If the model is updated also the values which can be fetched via `getRawModelDataInput` will be updated.
2328

2429
.. code-block:: json
@@ -27,7 +32,7 @@ The **PopulatePostProcessor** adds a populate method to your generated model. Th
2732
"$id": "example",
2833
"type": "object",
2934
"properties": {
30-
"value": {
35+
"example": {
3136
"type": "string"
3237
}
3338
}
@@ -74,6 +79,65 @@ Now let's have a look at the behaviour of the generated model:
7479

7580
If the **PopulatePostProcessor** is added to your model generator the populate method will be added to the model independently of the `immutable setting <../gettingStarted.html#immutable-classes>`__.
7681

82+
AdditionalPropertiesAccessorPostProcessor
83+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
84+
85+
.. code-block:: php
86+
87+
$generator = new ModelGenerator();
88+
$generator->addPostProcessor(new AdditionalPropertiesAccessorPostProcessor(true));
89+
90+
The **AdditionalPropertiesAccessorPostProcessor** adds methods to your model to work with `additional properties <../complexTypes/object.html#additional-properties>`__ on your objects. By default the post processor only adds methods to objects from a schema which defines constraints for additional properties. If the first constructor parameter *$addForModelsWithoutAdditionalPropertiesDefinition* is set to true the methods will also be added to objects generated from a schema which doesn't define additional properties constraints. If the *additionalProperties* keyword in a schema is set to false the methods will never be added.
91+
92+
Added methods
93+
~~~~~~~~~~~~~
94+
95+
.. code-block:: json
96+
97+
{
98+
"$id": "example",
99+
"type": "object",
100+
"properties": {
101+
"example": {
102+
"type": "string"
103+
}
104+
},
105+
"additionalProperties": {
106+
"type": "string"
107+
}
108+
}
109+
110+
Generated interface with the **AdditionalPropertiesAccessorPostProcessor**:
111+
112+
.. code-block:: php
113+
114+
public function getRawModelDataInput(): array;
115+
116+
public function setExample(float $example): self;
117+
public function getExample(): float;
118+
119+
public function getAdditionalProperties(): array;
120+
public function getAdditionalProperty(string $property): ?string;
121+
public function setAdditionalProperty(string $property, string $value): self;
122+
public function removeAdditionalProperty(string $property): bool;
123+
124+
.. note::
125+
126+
The methods **setAdditionalProperty** and **removeAdditionalProperty** are only added if the `immutable setting <../gettingStarted.html#immutable-classes>`__ is set to false.
127+
128+
**getAdditionalProperties**: This method returns all additional properties which are currently part of the model as key-value pairs where the key is the property name and the value the current value stored in the model. All other properties which are part of the object (in this case the property *example*) will not be included. In opposite to the *getRawModelDataInput* the values provided via this method are the processed values. This means if the schema provides an object-schema for additional properties an array of object instances will be returned. If the additional properties schema contains `filter <../nonStandardExtensions/filter.html>`__ the filtered (and in case of transforming filter transformed) values will be returned.
129+
130+
**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.
131+
132+
**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.
133+
134+
**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.
135+
136+
Serialization
137+
~~~~~~~~~~~~~
138+
139+
By default additional properties are not included in serialized models. If the **AdditionalPropertiesAccessorPostProcessor** is applied and `serialization <../gettingStarted.html#serialization-methods>`__ is enabled the additional properties will be merged into the serialization result. If the additional properties are processed via a transforming filter each value will be serialized via the serialisation method of the transforming filter.
140+
77141
Custom Post Processors
78142
----------------------
79143

docs/source/gettingStarted.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,10 @@ The generated class will implement the interface **PHPModelGenerator\\Interfaces
247247

248248
Additionally the class will implement the PHP builtin interface **\JsonSerializable** which allows the direct usage of the generated classes in a custom json_encode.
249249

250+
.. warning::
251+
252+
If you provide `additional properties <complexTypes/object.html#additional-properties>`__ you may want to use the `AdditionalPropertiesAccessorPostProcessor <generator/postProcessor.html#additionalpropertiesaccessorpostprocessor>`__ as the additional properties by default aren't included into the serialization result.
253+
250254
Output generation process
251255
^^^^^^^^^^^^^^^^^^^^^^^^^
252256

docs/source/nonStandardExtensions/filter.rst

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,6 @@ Let's have a look how the generated model behaves:
188188

189189
.. code-block:: php
190190
191-
// valid, the name will be NULL as the name is not required
192-
$family = new Person([]);
193-
194191
// A valid example
195192
$family = new Family(['members' => [null, null]]]);
196193
$family->getMembers(); // returns an empty array

src/Model/Property/Property.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,9 @@ public function getTypeHint(bool $outputType = false): string
122122
$input = [$this->type, $this->outputType];
123123
}
124124

125-
$input = join('|', array_map(function (string $input) {
125+
$input = join('|', array_map(function (string $input) use ($outputType): string {
126126
foreach ($this->typeHintDecorators as $decorator) {
127-
$input = $decorator->decorate($input);
127+
$input = $decorator->decorate($input, $outputType);
128128
}
129129

130130
return $input;

src/Model/Schema.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ public function getBaseValidators(): array
156156
*
157157
* @return $this
158158
*/
159-
public function addBaseValidator(PropertyValidatorInterface $baseValidator)
159+
public function addBaseValidator(PropertyValidatorInterface $baseValidator): self
160160
{
161161
$this->baseValidators[] = $baseValidator;
162162

src/Model/Validator/AdditionalItemsValidator.php

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,4 @@ class AdditionalItemsValidator extends AdditionalPropertiesValidator
2323
protected const ADDITIONAL_PROPERTIES_KEY = 'additionalItems';
2424

2525
protected const EXCEPTION_CLASS = InvalidAdditionalTupleItemsException::class;
26-
27-
/**
28-
* AdditionalItemsValidator constructor.
29-
*
30-
* @param SchemaProcessor $schemaProcessor
31-
* @param Schema $schema
32-
* @param JsonSchema $propertiesStructure
33-
* @param string $propertyName
34-
*
35-
* @throws SchemaException
36-
*/
37-
public function __construct(
38-
SchemaProcessor $schemaProcessor,
39-
Schema $schema,
40-
JsonSchema $propertiesStructure,
41-
string $propertyName
42-
) {
43-
parent::__construct($schemaProcessor, $schema, $propertiesStructure, $propertyName);
44-
}
4526
}

src/Model/Validator/AdditionalPropertiesValidator.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ class AdditionalPropertiesValidator extends PropertyTemplateValidator
3131

3232
/** @var PropertyInterface */
3333
private $validationProperty;
34+
/** @var bool */
35+
private $collectAdditionalProperties = false;
3436

3537
/**
3638
* AdditionalPropertiesValidator constructor.
@@ -69,6 +71,8 @@ public function __construct(
6971
),
7072
'generatorConfiguration' => $schemaProcessor->getGeneratorConfiguration(),
7173
'viewHelper' => new RenderHelper($schemaProcessor->getGeneratorConfiguration()),
74+
// by default don't collect additional property data
75+
'collectAdditionalProperties' => &$this->collectAdditionalProperties,
7276
],
7377
static::EXCEPTION_CLASS,
7478
[$propertyName ?? $schema->getClassName(), '&$invalidProperties']
@@ -85,6 +89,22 @@ public function getCheck(): string
8589
return parent::getCheck();
8690
}
8791

92+
/**
93+
* @param bool $collectAdditionalProperties
94+
*/
95+
public function setCollectAdditionalProperties(bool $collectAdditionalProperties): void
96+
{
97+
$this->collectAdditionalProperties = $collectAdditionalProperties;
98+
}
99+
100+
/**
101+
* @return PropertyInterface
102+
*/
103+
public function getValidationProperty(): PropertyInterface
104+
{
105+
return $this->validationProperty;
106+
}
107+
88108
/**
89109
* Initialize all variables which are required to execute a property names validator
90110
*

src/PropertyProcessor/Decorator/TypeHint/ArrayTypeHintDecorator.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ public function __construct(PropertyInterface $nestedProperty)
2929
/**
3030
* @inheritdoc
3131
*/
32-
public function decorate(string $input): string
32+
public function decorate(string $input, bool $outputType = false): string
3333
{
3434
return implode('|', array_map(function (string $typeHint): string {
3535
return "{$typeHint}[]";
36-
}, explode('|', $this->nestedProperty->getTypeHint())));
36+
}, explode('|', $this->nestedProperty->getTypeHint($outputType))));
3737
}
3838
}

0 commit comments

Comments
 (0)