Skip to content

Commit

Permalink
Additional property post processor (#17)
Browse files Browse the repository at this point in the history
Added additional property post processor to handle additional properties
  • Loading branch information
wol-soft committed Jul 20, 2020
1 parent 6c82abe commit 632e4d7
Show file tree
Hide file tree
Showing 33 changed files with 902 additions and 52 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
],
"require": {
"symplify/easy-coding-standard": "^7.2.3",
"wol-soft/php-json-schema-model-generator-production": "^0.12.2",
"wol-soft/php-json-schema-model-generator-production": "^0.13.0",
"wol-soft/php-micro-template": "^1.3.2",

"php": ">=7.2",
Expand Down
8 changes: 8 additions & 0 deletions docs/source/complexTypes/object.rst
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ Additional Properties

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'.

.. hint::

If you define constraints via `additionalProperties` you may want to use the `AdditionalPropertiesAccessorPostProcessor <../generator/postProcessor.html#additionalpropertiesaccessorpostprocessor>`__ to access and modify your additional properties.

.. code-block:: json
{
Expand Down Expand Up @@ -243,6 +247,10 @@ The thrown exception will be a *PHPModelGenerator\\Exception\\Object\\InvalidAdd
// get the value provided to the property
public function getProvidedValue()
.. warning::

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.

Recursive Objects
-----------------

Expand Down
66 changes: 65 additions & 1 deletion docs/source/generator/postProcessor.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ Builtin Post Processors
PopulatePostProcessor
^^^^^^^^^^^^^^^^^^^^^

.. code-block:: php
$generator = new ModelGenerator();
$generator->addPostProcessor(new PopulatePostProcessor());
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.

.. code-block:: json
Expand All @@ -27,7 +32,7 @@ The **PopulatePostProcessor** adds a populate method to your generated model. Th
"$id": "example",
"type": "object",
"properties": {
"value": {
"example": {
"type": "string"
}
}
Expand Down Expand Up @@ -74,6 +79,65 @@ Now let's have a look at the behaviour of the generated model:

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>`__.

AdditionalPropertiesAccessorPostProcessor
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code-block:: php
$generator = new ModelGenerator();
$generator->addPostProcessor(new AdditionalPropertiesAccessorPostProcessor(true));
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.

Added methods
~~~~~~~~~~~~~

.. code-block:: json
{
"$id": "example",
"type": "object",
"properties": {
"example": {
"type": "string"
}
},
"additionalProperties": {
"type": "string"
}
}
Generated interface with the **AdditionalPropertiesAccessorPostProcessor**:

.. code-block:: php
public function getRawModelDataInput(): array;
public function setExample(float $example): self;
public function getExample(): float;
public function getAdditionalProperties(): array;
public function getAdditionalProperty(string $property): ?string;
public function setAdditionalProperty(string $property, string $value): self;
public function removeAdditionalProperty(string $property): bool;
.. note::

The methods **setAdditionalProperty** and **removeAdditionalProperty** are only added if the `immutable setting <../gettingStarted.html#immutable-classes>`__ is set to false.

**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.

**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.

**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.

**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.

Serialization
~~~~~~~~~~~~~

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.

Custom Post Processors
----------------------

Expand Down
4 changes: 4 additions & 0 deletions docs/source/gettingStarted.rst
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,10 @@ The generated class will implement the interface **PHPModelGenerator\\Interfaces

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

.. warning::

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.

Output generation process
^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
3 changes: 0 additions & 3 deletions docs/source/nonStandardExtensions/filter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,6 @@ Let's have a look how the generated model behaves:

.. code-block:: php
// valid, the name will be NULL as the name is not required
$family = new Person([]);
// A valid example
$family = new Family(['members' => [null, null]]]);
$family->getMembers(); // returns an empty array
Expand Down
4 changes: 2 additions & 2 deletions src/Model/Property/Property.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,9 @@ public function getTypeHint(bool $outputType = false): string
$input = [$this->type, $this->outputType];
}

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

return $input;
Expand Down
2 changes: 1 addition & 1 deletion src/Model/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ public function getBaseValidators(): array
*
* @return $this
*/
public function addBaseValidator(PropertyValidatorInterface $baseValidator)
public function addBaseValidator(PropertyValidatorInterface $baseValidator): self
{
$this->baseValidators[] = $baseValidator;

Expand Down
19 changes: 0 additions & 19 deletions src/Model/Validator/AdditionalItemsValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,4 @@ class AdditionalItemsValidator extends AdditionalPropertiesValidator
protected const ADDITIONAL_PROPERTIES_KEY = 'additionalItems';

protected const EXCEPTION_CLASS = InvalidAdditionalTupleItemsException::class;

/**
* AdditionalItemsValidator constructor.
*
* @param SchemaProcessor $schemaProcessor
* @param Schema $schema
* @param JsonSchema $propertiesStructure
* @param string $propertyName
*
* @throws SchemaException
*/
public function __construct(
SchemaProcessor $schemaProcessor,
Schema $schema,
JsonSchema $propertiesStructure,
string $propertyName
) {
parent::__construct($schemaProcessor, $schema, $propertiesStructure, $propertyName);
}
}
20 changes: 20 additions & 0 deletions src/Model/Validator/AdditionalPropertiesValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class AdditionalPropertiesValidator extends PropertyTemplateValidator

/** @var PropertyInterface */
private $validationProperty;
/** @var bool */
private $collectAdditionalProperties = false;

/**
* AdditionalPropertiesValidator constructor.
Expand Down Expand Up @@ -69,6 +71,8 @@ public function __construct(
),
'generatorConfiguration' => $schemaProcessor->getGeneratorConfiguration(),
'viewHelper' => new RenderHelper($schemaProcessor->getGeneratorConfiguration()),
// by default don't collect additional property data
'collectAdditionalProperties' => &$this->collectAdditionalProperties,
],
static::EXCEPTION_CLASS,
[$propertyName ?? $schema->getClassName(), '&$invalidProperties']
Expand All @@ -85,6 +89,22 @@ public function getCheck(): string
return parent::getCheck();
}

/**
* @param bool $collectAdditionalProperties
*/
public function setCollectAdditionalProperties(bool $collectAdditionalProperties): void
{
$this->collectAdditionalProperties = $collectAdditionalProperties;
}

/**
* @return PropertyInterface
*/
public function getValidationProperty(): PropertyInterface
{
return $this->validationProperty;
}

/**
* Initialize all variables which are required to execute a property names validator
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ public function __construct(PropertyInterface $nestedProperty)
/**
* @inheritdoc
*/
public function decorate(string $input): string
public function decorate(string $input, bool $outputType = false): string
{
return implode('|', array_map(function (string $typeHint): string {
return "{$typeHint}[]";
}, explode('|', $this->nestedProperty->getTypeHint())));
}, explode('|', $this->nestedProperty->getTypeHint($outputType))));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ public function __construct(PropertyInterface $nestedProperty)
/**
* @inheritdoc
*/
public function decorate(string $input): string
public function decorate(string $input, bool $outputType = false): string
{
return (new TypeHintDecorator(explode('|', $this->nestedProperty->getTypeHint())))->decorate($input);
return (new TypeHintDecorator(explode('|', $this->nestedProperty->getTypeHint($outputType))))
->decorate($input, $outputType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public function __construct(array $types)
/**
* @inheritdoc
*/
public function decorate(string $input): string
public function decorate(string $input, bool $outputType = false): string
{
return implode('|', array_unique(array_filter(array_merge(explode('|', $input), $this->types))));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ interface TypeHintDecoratorInterface
* Decorate a given string
*
* @param string $input The input getting decorated
* @param bool $outputType
*
* @return string
*/
public function decorate(string $input): string;
public function decorate(string $input, bool $outputType = false): string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ public function __construct(PropertyInterface $property)
/**
* @inheritdoc
*/
public function decorate(string $input): string
public function decorate(string $input, bool $outputType = false): string
{
return $this->property->getTypeHint();
return $this->property->getTypeHint($outputType);
}
}
10 changes: 6 additions & 4 deletions src/PropertyProcessor/Property/MultiTypeProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,12 @@ public function process(string $propertyName, JsonSchema $propertySchema): Prope

$property->addTypeHintDecorator(
new TypeHintDecorator(
array_map(function (PropertyInterface $subProperty): string {
return $subProperty->getTypeHint();
},
$subProperties)
array_map(
function (PropertyInterface $subProperty): string {
return $subProperty->getTypeHint();
},
$subProperties
)
)
);

Expand Down
5 changes: 4 additions & 1 deletion src/PropertyProcessor/Property/NullProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use PHPModelGenerator\Model\Property\PropertyInterface;
use PHPModelGenerator\Model\SchemaDefinition\JsonSchema;
use PHPModelGenerator\PropertyProcessor\Decorator\TypeHint\TypeHintDecorator;

/**
* Class NullProcessor
Expand All @@ -26,6 +27,8 @@ class NullProcessor extends AbstractTypedValueProcessor
*/
public function process(string $propertyName, JsonSchema $propertySchema): PropertyInterface
{
return (parent::process($propertyName, $propertySchema))->setType('');
return (parent::process($propertyName, $propertySchema))
->setType('')
->addTypeHintDecorator(new TypeHintDecorator(['null']));
}
}

0 comments on commit 632e4d7

Please sign in to comment.