Skip to content

Optimize exceptions for nested objects #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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