diff --git a/composer.json b/composer.json index 1a46cfd..e6367c1 100644 --- a/composer.json +++ b/composer.json @@ -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", diff --git a/docs/source/complexTypes/object.rst b/docs/source/complexTypes/object.rst index e54efb4..147305b 100644 --- a/docs/source/complexTypes/object.rst +++ b/docs/source/complexTypes/object.rst @@ -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: @@ -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 ---------- diff --git a/docs/source/gettingStarted.rst b/docs/source/gettingStarted.rst index 77241a1..72c97ca 100644 --- a/docs/source/gettingStarted.rst +++ b/docs/source/gettingStarted.rst @@ -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 ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/Model/Property/Property.php b/src/Model/Property/Property.php index 515c858..2cac36e 100644 --- a/src/Model/Property/Property.php +++ b/src/Model/Property/Property.php @@ -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; diff --git a/src/Model/Property/PropertyInterface.php b/src/Model/Property/PropertyInterface.php index 3f0351b..b39a8f3 100644 --- a/src/Model/Property/PropertyInterface.php +++ b/src/Model/Property/PropertyInterface.php @@ -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 diff --git a/src/Model/Property/PropertyProxy.php b/src/Model/Property/PropertyProxy.php index 1fd405c..1c04465 100644 --- a/src/Model/Property/PropertyProxy.php +++ b/src/Model/Property/PropertyProxy.php @@ -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); } /** diff --git a/src/PropertyProcessor/Decorator/Property/IntToFloatCastDecorator.php b/src/PropertyProcessor/Decorator/Property/IntToFloatCastDecorator.php index 6be03a7..f77dff8 100644 --- a/src/PropertyProcessor/Decorator/Property/IntToFloatCastDecorator.php +++ b/src/PropertyProcessor/Decorator/Property/IntToFloatCastDecorator.php @@ -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"; } diff --git a/src/PropertyProcessor/Decorator/Property/ObjectInstantiationDecorator.php b/src/PropertyProcessor/Decorator/Property/ObjectInstantiationDecorator.php index 0a53449..c8b49d2 100644 --- a/src/PropertyProcessor/Decorator/Property/ObjectInstantiationDecorator.php +++ b/src/PropertyProcessor/Decorator/Property/ObjectInstantiationDecorator.php @@ -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 @@ -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'] + ), ] ); } diff --git a/src/PropertyProcessor/Decorator/Property/PropertyDecoratorInterface.php b/src/PropertyProcessor/Decorator/Property/PropertyDecoratorInterface.php index 0e060a3..72c176d 100644 --- a/src/PropertyProcessor/Decorator/Property/PropertyDecoratorInterface.php +++ b/src/PropertyProcessor/Decorator/Property/PropertyDecoratorInterface.php @@ -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; } diff --git a/src/PropertyProcessor/Decorator/Property/PropertyTransferDecorator.php b/src/PropertyProcessor/Decorator/Property/PropertyTransferDecorator.php index 62549a7..17d48c3 100644 --- a/src/PropertyProcessor/Decorator/Property/PropertyTransferDecorator.php +++ b/src/PropertyProcessor/Decorator/Property/PropertyTransferDecorator.php @@ -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); } } diff --git a/src/Templates/Decorator/ObjectInstantiationDecorator.phptpl b/src/Templates/Decorator/ObjectInstantiationDecorator.phptpl new file mode 100644 index 0000000..85cd04d --- /dev/null +++ b/src/Templates/Decorator/ObjectInstantiationDecorator.phptpl @@ -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 }}) diff --git a/src/Templates/Decorator/ObjectInstantiationDecoratorDirectException.phptpl b/src/Templates/Decorator/ObjectInstantiationDecoratorDirectException.phptpl deleted file mode 100644 index 2a861e9..0000000 --- a/src/Templates/Decorator/ObjectInstantiationDecoratorDirectException.phptpl +++ /dev/null @@ -1 +0,0 @@ -(is_array($param = {{ input }}) ? new {{ className }}($param) : $param) \ No newline at end of file diff --git a/src/Templates/Decorator/ObjectInstantiationDecoratorErrorRegistry.phptpl b/src/Templates/Decorator/ObjectInstantiationDecoratorErrorRegistry.phptpl deleted file mode 100644 index 1dd7902..0000000 --- a/src/Templates/Decorator/ObjectInstantiationDecoratorErrorRegistry.phptpl +++ /dev/null @@ -1,11 +0,0 @@ -(function ($value) { - try { - return is_array($value) ? new {{ className }}($value) : $value; - } catch (\Exception $instantiationException) { - foreach($instantiationException->getErrors() as $nestedValidationError) { - $this->errorRegistry->addError($nestedValidationError); - } - - return $instantiationException; - } -})({{ input }}) diff --git a/src/Templates/Model.phptpl b/src/Templates/Model.phptpl index 3f733f8..0ea4300 100644 --- a/src/Templates/Model.phptpl +++ b/src/Templates/Model.phptpl @@ -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); } diff --git a/src/Utils/RenderHelper.php b/src/Utils/RenderHelper.php index 8c53e26..3428e83 100644 --- a/src/Utils/RenderHelper.php +++ b/src/Utils/RenderHelper.php @@ -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) . '; }'; } /** diff --git a/tests/Basic/SchemaDependencyTest.php b/tests/Basic/SchemaDependencyTest.php index 78d14af..3849418 100644 --- a/tests/Basic/SchemaDependencyTest.php +++ b/tests/Basic/SchemaDependencyTest.php @@ -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' => [ @@ -368,7 +369,8 @@ public function invalidSchemaDependencyNestedObjectDataProvider(): array ], <<