Skip to content

Commit

Permalink
Optimize exceptions for nested objects (#20)
Browse files Browse the repository at this point in the history
Optimize exceptions for nested objects to be represented by a dedicated exception instead of being merged into top level messages
  • Loading branch information
wol-soft authored Jul 25, 2020
1 parent 9f1d46a commit ac2ae33
Show file tree
Hide file tree
Showing 16 changed files with 83 additions and 38 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.13.0",
"wol-soft/php-json-schema-model-generator-production": "0.14.0",
"wol-soft/php-micro-template": "^1.3.2",

"php": ">=7.2",
Expand Down
24 changes: 22 additions & 2 deletions docs/source/complexTypes/object.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,12 @@ Generated interface:
Possible exceptions:

* Invalid type for car. Requires object, got __TYPE__
.. code-block:: none
* Invalid type for car. Requires object, got __TYPE__
The nested object will be validated in the nested class Car which may throw additional exceptions if invalid data is provided.
* Invalid nested object for property car:
- Invalid type for model. Requires string, got __TYPE__
The thrown exception will be a *PHPModelGenerator\\Exception\\Generic\\InvalidTypeException* which provides the following methods to get further error details:

Expand All @@ -61,6 +64,23 @@ The thrown exception will be a *PHPModelGenerator\\Exception\\Generic\\InvalidTy
// get the value provided to the property
public function getProvidedValue()
The nested object will be validated in the nested class Car which may throw additional exceptions if invalid data is provided. If the internal validation of a nested object fails a *PHPModelGenerator\\Exception\\Generic\\NestedObjectException* will be thrown which provides the following methods to get further error details:

.. code-block:: php
// Returns the exception which was thrown in the nested object
public function getNestedException()
// get the name of the property which contains the nested object
public function getPropertyName(): string
// get the value provided to the property
public function getProvidedValue()
If `error collection <../gettingStarted.html#collect-errors-vs-early-return>`__ is enabled the nested exception returned by `getNestedException` will be an **ErrorRegistryException** containing all validation errors of the nested object. Otherwise it will contain the first validation error which occurred during the validation of the nested object.

.. hint::

If the class created for a nested object is instantiated manually you will either get a collection exception or a specific exception based on your error collection configuration if invalid data is provided.

Namespaces
----------

Expand Down
4 changes: 4 additions & 0 deletions docs/source/gettingStarted.rst
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ All collected exceptions from an ErrorRegistryException are accessible via the *
(new GeneratorConfiguration())
->setCollectErrors(false);
.. hint::

All builtin exceptions provide serialization methods (compare `serialization <#serialization-methods>`_). By default sensitive data (file and line) of the exception will not be serialized. The serialization methods provide another parameter `$stripSensitiveData`. When this parameter is set to false file and line information will be included.

Custom exception classes
^^^^^^^^^^^^^^^^^^^^^^^^

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 @@ -215,10 +215,10 @@ public function addDecorator(PropertyDecoratorInterface $decorator): PropertyInt
/**
* @inheritdoc
*/
public function resolveDecorator(string $input): string
public function resolveDecorator(string $input, bool $nestedProperty): string
{
foreach ($this->decorators as $decorator) {
$input = $decorator->decorate($input, $this);
$input = $decorator->decorate($input, $this, $nestedProperty);
}

return $input;
Expand Down
3 changes: 2 additions & 1 deletion src/Model/Property/PropertyInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,11 @@ public function addDecorator(PropertyDecoratorInterface $decorator): PropertyInt
* Resolve all decorators of the property
*
* @param string $input
* @param bool $nestedProperty
*
* @return string
*/
public function resolveDecorator(string $input): string;
public function resolveDecorator(string $input, bool $nestedProperty): string;

/**
* @return bool
Expand Down
4 changes: 2 additions & 2 deletions src/Model/Property/PropertyProxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,9 @@ public function addDecorator(PropertyDecoratorInterface $decorator): PropertyInt
/**
* @inheritdoc
*/
public function resolveDecorator(string $input): string
public function resolveDecorator(string $input, bool $nestedProperty): string
{
return $this->getProperty()->resolveDecorator($input);
return $this->getProperty()->resolveDecorator($input, $nestedProperty);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class IntToFloatCastDecorator implements PropertyDecoratorInterface
/**
* @inheritdoc
*/
public function decorate(string $input, PropertyInterface $property): string
public function decorate(string $input, PropertyInterface $property, bool $nestedProperty): string
{
return "is_int($input) ? (float) $input : $input";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
namespace PHPModelGenerator\PropertyProcessor\Decorator\Property;

use PHPMicroTemplate\Render;
use PHPModelGenerator\Exception\Object\NestedObjectException;
use PHPModelGenerator\Model\GeneratorConfiguration;
use PHPModelGenerator\Model\Property\PropertyInterface;
use PHPModelGenerator\Model\Validator\PropertyValidator;
use PHPModelGenerator\Utils\RenderHelper;

/**
* Class ObjectInstantiationDecorator
Expand Down Expand Up @@ -43,17 +46,21 @@ public function __construct(string $className, GeneratorConfiguration $generator
/**
* @inheritdoc
*/
public function decorate(string $input, PropertyInterface $property): string
public function decorate(string $input, PropertyInterface $property, bool $nestedProperty): string
{
$template = $this->generatorConfiguration->collectErrors()
? 'ObjectInstantiationDecoratorErrorRegistry.phptpl'
: 'ObjectInstantiationDecoratorDirectException.phptpl';

return static::$renderer->renderTemplate(
DIRECTORY_SEPARATOR . 'Decorator' . DIRECTORY_SEPARATOR . $template,
DIRECTORY_SEPARATOR . 'Decorator' . DIRECTORY_SEPARATOR . 'ObjectInstantiationDecorator.phptpl',
[
'input' => $input,
'className' => $this->className,
'nestedProperty' => $nestedProperty,
'viewHelper' => new RenderHelper($this->generatorConfiguration),
'generatorConfiguration' => $this->generatorConfiguration,
'nestedValidator' => new PropertyValidator(
'',
NestedObjectException::class,
[$property->getName(), '&$instantiationException']
),
]
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ interface PropertyDecoratorInterface
/**
* Decorate a given string
*
* @param string $input
* @param string $input
* @param PropertyInterface $property The property getting decorated
* @param bool $nestedProperty
*
* @return string
*/
public function decorate(string $input, PropertyInterface $property): string;
public function decorate(string $input, PropertyInterface $property, bool $nestedProperty): string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ public function __construct(PropertyInterface $property)
/**
* @inheritdoc
*/
public function decorate(string $input, PropertyInterface $property): string
public function decorate(string $input, PropertyInterface $property, bool $nestedProperty): string
{
return $this->property->resolveDecorator($input);
return $this->property->resolveDecorator($input, $nestedProperty);
}
}
21 changes: 21 additions & 0 deletions src/Templates/Decorator/ObjectInstantiationDecorator.phptpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
(function ($value) {
try {
return is_array($value) ? new {{ className }}($value) : $value;
} catch (\Exception $instantiationException) {
{% if nestedProperty %}
{{ viewHelper.validationError(nestedValidator) }}
{% else %}
{% if generatorConfiguration.collectErrors() %}
foreach($instantiationException->getErrors() as $nestedValidationError) {
$this->errorRegistry->addError($nestedValidationError);
}
{% else %}
throw $instantiationException;
{% endif %}
{% endif %}

{% if generatorConfiguration.collectErrors() %}
return $instantiationException;
{% endif %}
}
})({{ input }})

This file was deleted.

This file was deleted.

2 changes: 1 addition & 1 deletion src/Templates/Model.phptpl
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ class {{ class }} {% if schema.getInterfaces() %}implements {{ viewHelper.joinCl

$value = $modelData['{{ property.getName() }}'] ?? $this->{{ property.getAttribute() }};

{{ viewHelper.resolvePropertyDecorator(property) }}
{{ viewHelper.resolvePropertyDecorator(property, true) }}

$this->{{ property.getAttribute() }} = $this->validate{{ viewHelper.ucfirst(property.getAttribute()) }}($value, $modelData);
}
Expand Down
7 changes: 4 additions & 3 deletions src/Utils/RenderHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,18 +74,19 @@ public function joinClassNames(array $fqcns): string
* Resolve all associated decorators of a property
*
* @param PropertyInterface $property
* @param bool $nestedProperty
*
* @return string
*/
public function resolvePropertyDecorator(PropertyInterface $property): string
public function resolvePropertyDecorator(PropertyInterface $property, bool $nestedProperty = false): string
{
if (!$property->hasDecorators()) {
return '';
}

return $property->isRequired()
? '$value = ' . $property->resolveDecorator('$value') . ';'
: 'if ($value !== null) { $value = ' . $property->resolveDecorator('$value') . '; }';
? '$value = ' . $property->resolveDecorator('$value', $nestedProperty) . ';'
: 'if ($value !== null) { $value = ' . $property->resolveDecorator('$value', $nestedProperty) . '; }';
}

/**
Expand Down
8 changes: 5 additions & 3 deletions tests/Basic/SchemaDependencyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -353,8 +353,9 @@ public function invalidSchemaDependencyNestedObjectDataProvider(): array
Invalid schema which is dependant on credit_card:
- Missing required value for billing_address
- Invalid type for billing_address. Requires string, got NULL
- Missing required value for name
- Invalid type for name. Requires string, got NULL
- Invalid nested object for property owner:
- Missing required value for name
- Invalid type for name. Requires string, got NULL
ERROR
],
'invalid data type' => [
Expand All @@ -368,7 +369,8 @@ public function invalidSchemaDependencyNestedObjectDataProvider(): array
],
<<<ERROR
Invalid schema which is dependant on credit_card:
- Invalid type for name. Requires string, got boolean
- Invalid nested object for property owner:
- Invalid type for name. Requires string, got boolean
ERROR
],
];
Expand Down

0 comments on commit ac2ae33

Please sign in to comment.