Skip to content

Commit b60feea

Browse files
authored
Merge pull request #21 from wol-soft/CompositionValidationOnMutableObjects
Validation for composition validators in setter methods
2 parents a99d794 + 4886e70 commit b60feea

File tree

56 files changed

+1584
-269
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1584
-269
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ php:
1010
- 7.2
1111
- 7.3
1212
- 7.4
13+
- nightly
1314

1415
install:
1516
# Install coveralls.phar

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ As an optional parameter you can set up a *GeneratorConfiguration* object to con
6565
```php
6666
$generator = new Generator(
6767
(new GeneratorConfiguration())
68-
->setNamespacePrefix('\MyApp\Model')
68+
->setNamespacePrefix('MyApp\Model')
6969
->setImmutable(false)
7070
);
7171

@@ -82,11 +82,11 @@ The *GeneratorConfiguration* object offers the following methods to configure th
8282

8383
Method | Configuration | Default
8484
--- | --- | ---
85-
``` setNamespacePrefix(string $prefix) ``` <br><br>Example:<br> ``` setNamespacePrefix('\MyApp\Model') ``` | Configures a namespace prefix for all generated classes. The namespaces will be extended with the directory structure of the source directory. | Empty string so no namespace prefix will be used
85+
``` setNamespacePrefix(string $prefix) ``` <br><br>Example:<br> ``` setNamespacePrefix('MyApp\Model') ``` | Configures a namespace prefix for all generated classes. The namespaces will be extended with the directory structure of the source directory. | Empty string so no namespace prefix will be used
8686
``` setImmutable(bool $immutable) ``` <br><br>Example:<br> ``` setImmutable(false) ``` | If set to true the generated model classes will be delivered without setter methods for the object properties. | true
8787
``` setImplicitNull(bool $allowImplicitNull) ``` <br><br>Example:<br> ``` setImplicitNull(true) ``` | By setting the implicit null option to true all of your object properties which aren't required will implicitly accept null. | false
8888
``` setCollectErrors(bool $collectErrors) ``` <br><br>Example:<br> ``` setCollectErrors(false) ``` | By default the complete input is validated and in case of failing validations all error messages will be thrown in a single exception. If set to false the first failing validation will throw an exception. | true
89-
``` setPrettyPrint(bool $prettyPrint) ``` <br><br>Example:<br> ``` setPrettyPrint(true) ``` | If set to false, the generated model classes won't follow coding guidelines (but the generation is faster). If enabled the package [Symplify/EasyCodingStandard](https://github.com/Symplify/EasyCodingStandard) will be used to clean up the generated code. By default pretty printing is disabled. | false
89+
``` setPrettyPrint(bool $prettyPrint) ``` <br><br>Example:<br> ``` setPrettyPrint(true) ``` | If set to false, the generated model classes won't follow coding guidelines (but the generation is faster). If enabled the package [Symplify/EasyCodingStandard](https://github.com/Symplify/EasyCodingStandard) will be used to clean up the generated code (the package must be installed manually: `composer require --dev symplify/easy-coding-standard`). By default pretty printing is disabled. | false
9090
``` setSerialization(bool $serialization) ``` <br><br>Example:<br> ``` setSerialization(true) ``` | If set to true the serialization methods `toArray` and `toJSON` will be added to the public interface of the generated classes. | false
9191
``` setOutputEnabled(bool $outputEnabled) ``` <br><br>Example:<br> ``` setOutputEnabled(false) ``` | Enable or disable output of the generation process to STDOUT | true
9292
``` setErrorRegistryClass(string $exceptionClass) ``` <br><br>Example:<br> ``` setErrorRegistryClass(CustomException::class) ``` | Define a custom exception implementing the ErrorRegistryExceptionInterface to be used. The exception will be thrown if a validation fails and error collection is **enabled** | ErrorRegistryException::class

composer.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "wol-soft/php-json-schema-model-generator",
33
"homepage": "https://github.com/wol-soft/php-json-schema-model-generator",
4-
"description": "Creates immutable PHP model classes from JSON-Schema files",
4+
"description": "Creates (immutable) PHP model classes from JSON-Schema files",
55
"type": "library",
66
"license": "MIT",
77
"authors": [
@@ -11,16 +11,18 @@
1111
}
1212
],
1313
"require": {
14-
"symplify/easy-coding-standard": "^7.2.3",
15-
"wol-soft/php-json-schema-model-generator-production": "0.14.0",
14+
"wol-soft/php-json-schema-model-generator-production": "dev-SkipSerializationOfInternalProperties",
1615
"wol-soft/php-micro-template": "^1.3.2",
1716

1817
"php": ">=7.2",
1918
"ext-json": "*",
2019
"ext-mbstring": "*"
2120
},
2221
"require-dev": {
23-
"phpunit/phpunit": "^8.5"
22+
"phpunit/phpunit": "^8.5 || ^9.4"
23+
},
24+
"suggest": {
25+
"symplify/easy-coding-standard": "Allows pretty printing of the generated code"
2426
},
2527
"autoload": {
2628
"psr-4": {

docs/source/conf.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@
2020
# -- Project information -----------------------------------------------------
2121

2222
project = u'php-json-schema-model-generator'
23-
copyright = u'2019, Enno Woortmann'
23+
copyright = u'2020, Enno Woortmann'
2424
author = u'Enno Woortmann'
2525

2626
# The short X.Y version
27-
version = u''
27+
version = u'0.19'
2828
# The full version, including alpha/beta/rc tags
29-
release = u'0.6.0'
29+
release = u'0.19.0'
3030

3131

3232
# -- General configuration ---------------------------------------------------

docs/source/examples.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
Examples
2+
========
3+
4+
Ebay OpenAPIv3 spec
5+
-------------------
6+
7+
The Ebay OpenAPIv3 spec for the sell-inventory API is an around 6000 lines API definition. Using a script like the example below you can create ~120 PHP classes to handle requests, responses and all nested objects from the API spec:
8+
9+
.. code-block:: php
10+
11+
$generator = new ModelGenerator((new GeneratorConfiguration())
12+
->setNamespacePrefix('Ebay')
13+
->setImmutable(false)
14+
);
15+
16+
$file = __DIR__ . '/api-definition.json';
17+
$resultDir = __DIR__ . '/result';
18+
19+
file_put_contents(
20+
$file,
21+
file_get_contents('https://developer.ebay.com/api-docs/master/sell/inventory/openapi/3/sell_inventory_v1_oas3.json')
22+
);
23+
24+
$start = microtime(true);
25+
$generator
26+
->generateModelDirectory($resultDir)
27+
->generateModels(new OpenAPIv3Provider($file), $resultDir);
28+
29+
Measured runtime of the script (Pretty printing is disabled) is around 3 seconds at a memory peak consumption between 5 and 6 MB.

docs/source/generator/postProcessor.rst

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,12 @@ Generated interface with the **PopulatePostProcessor**:
4343
.. code-block:: php
4444
4545
public function getRawModelDataInput(): array;
46-
public function populate(array $modelData): self;
4746
4847
public function setExample(float $example): self;
4948
public function getExample(): float;
5049
50+
public function populate(array $modelData): self;
51+
5152
Now let's have a look at the behaviour of the generated model:
5253

5354
.. code-block:: php
@@ -79,7 +80,7 @@ Now let's have a look at the behaviour of the generated model:
7980

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

82-
The **PopulatePostProcessor** will also resolve all hooks which are applied to setters. Added code will be executed for all properties changed by a populate call.
83+
The **PopulatePostProcessor** will also resolve all hooks which are applied to setters. Added code will be executed for all properties changed by a populate call. Schema hooks which implement the **SetterAfterValidationHookInterface** will only be executed if all provided properties pass the validation.
8384

8485
AdditionalPropertiesAccessorPostProcessor
8586
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -170,10 +171,16 @@ What can you do inside your custom post processor?
170171

171172
* Add additional traits and interfaces to your models
172173
* Add additional methods and properties to your models
173-
* Hook via SchemaHooks into the generated source code and add your snippets at defined places inside the model:
174+
* Hook via **SchemaHooks** into the generated source code and add your snippets at defined places inside the model:
174175

175176
* Implement the **ConstructorBeforeValidationHookInterface** to add code to the beginning of your constructor
176177
* Implement the **ConstructorAfterValidationHookInterface** to add code to the end of your constructor
177178
* Implement the **GetterHookInterface** to add code to your getter methods
178179
* Implement the **SetterBeforeValidationHookInterface** to add code to the beginning of your setter methods
179180
* Implement the **SetterAfterValidationHookInterface** to add code to the end of your setter methods
181+
182+
.. warning::
183+
184+
If a setter for a property is called with the same value which is already stored internally (consequently no update of the property is required), the setters will return directly and as a result of that the setter hooks will not be executed.
185+
186+
This behaviour also applies also to properties changed via the *populate* method added by the `PopulatePostProcessor <#populatepostprocessor>`__ and the *setAdditionalProperty* method added by the `AdditionalPropertiesAccessorPostProcessor <#additionalpropertiesaccessorpostprocessor>`__

docs/source/gettingStarted.rst

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ As an optional parameter you can set up a *GeneratorConfiguration* object to con
4040
4141
$generator = new Generator(
4242
(new GeneratorConfiguration())
43-
->setNamespacePrefix('\MyApp\Model')
43+
->setNamespacePrefix('MyApp\Model')
4444
->setImmutable(false)
4545
);
4646
@@ -138,7 +138,7 @@ Further information about the generated namespaces can be found at `Namespaces <
138138
.. code-block:: php
139139
140140
(new GeneratorConfiguration())
141-
->setNamespacePrefix('\MyApp\Model');
141+
->setNamespacePrefix('MyApp\Model');
142142
143143
Immutable classes
144144
^^^^^^^^^^^^^^^^^
@@ -225,6 +225,10 @@ If set to false, the generated model classes won't follow coding guidelines (but
225225
(new GeneratorConfiguration())
226226
->setPrettyPrint(true);
227227
228+
.. warning::
229+
230+
The ECS package must be installed manually: `composer require --dev symplify/easy-coding-standard`
231+
228232
Serialization methods
229233
^^^^^^^^^^^^^^^^^^^^^
230234

docs/source/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Generates PHP model classes from JSON-Schema files including validation and prov
77
:maxdepth: 2
88

99
gettingStarted
10+
examples
1011

1112
.. include:: toc-generic.rst
1213
.. include:: toc-types.rst

phpunit.xml

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
3-
<phpunit backupGlobals="false"
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
backupGlobals="false"
44
backupStaticAttributes="false"
55
colors="true"
66
convertErrorsToExceptions="true"
77
convertWarningsToExceptions="true"
88
convertNoticesToExceptions="true"
99
processIsolation="false"
1010
stopOnFailure="false"
11-
bootstrap="tests/bootstrap.php">
12-
13-
<testsuite name="PHPModelGenerator">
14-
<directory>tests</directory>
15-
<exclude>tests/manual</exclude>
16-
</testsuite>
17-
18-
<filter>
19-
<whitelist>
20-
<directory>src</directory>
21-
</whitelist>
22-
</filter>
23-
</phpunit>
11+
bootstrap="tests/bootstrap.php"
12+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
13+
>
14+
<coverage>
15+
<include>
16+
<directory>src</directory>
17+
</include>
18+
</coverage>
19+
<testsuite name="PHPModelGenerator">
20+
<directory>tests</directory>
21+
<exclude>tests/manual</exclude>
22+
</testsuite>
23+
</phpunit>

src/Model/Property/Property.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ class Property implements PropertyInterface
3434
protected $isPropertyRequired = true;
3535
/** @var bool */
3636
protected $isPropertyReadOnly = false;
37+
/** @var bool */
38+
protected $isPropertyInternal = false;
3739
/** @var string */
3840
protected $description = '';
3941
/** @var mixed */
@@ -81,7 +83,7 @@ public function getName(): string
8183
*/
8284
public function getAttribute(): string
8385
{
84-
return $this->attribute;
86+
return ($this->isInternal() ? '_' : '') . $this->attribute;
8587
}
8688

8789
/**
@@ -343,4 +345,21 @@ public function getNestedSchema(): ?Schema
343345
{
344346
return $this->schema;
345347
}
348+
349+
/**
350+
* @inheritdoc
351+
*/
352+
public function setInternal(bool $isPropertyInternal): PropertyInterface
353+
{
354+
$this->isPropertyInternal = $isPropertyInternal;
355+
return $this;
356+
}
357+
358+
/**
359+
* @inheritdoc
360+
*/
361+
public function isInternal(): bool
362+
{
363+
return $this->isPropertyInternal;
364+
}
346365
}

src/Model/Property/PropertyInterface.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,13 @@ public function setRequired(bool $isPropertyRequired): PropertyInterface;
144144
*/
145145
public function setReadOnly(bool $isPropertyReadOnly): PropertyInterface;
146146

147+
/**
148+
* @param bool $isPropertyInternal
149+
*
150+
* @return PropertyInterface
151+
*/
152+
public function setInternal(bool $isPropertyInternal): PropertyInterface;
153+
147154
/**
148155
* @param mixed $defaultValue
149156
*
@@ -166,6 +173,11 @@ public function isRequired(): bool;
166173
*/
167174
public function isReadOnly(): bool;
168175

176+
/**
177+
* @return bool
178+
*/
179+
public function isInternal(): bool;
180+
169181
/**
170182
* Set a nested schema
171183
*

src/Model/Property/PropertyProxy.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,4 +228,20 @@ public function getJsonSchema(): JsonSchema
228228
{
229229
return $this->getProperty()->getJsonSchema();
230230
}
231+
232+
/**
233+
* @inheritdoc
234+
*/
235+
public function setInternal(bool $isPropertyInternal): PropertyInterface
236+
{
237+
return $this->getProperty()->setInternal($isPropertyInternal);
238+
}
239+
240+
/**
241+
* @inheritdoc
242+
*/
243+
public function isInternal(): bool
244+
{
245+
return $this->getProperty()->isInternal();
246+
}
231247
}

src/Model/RenderJob.php

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use PHPModelGenerator\Exception\FileSystemException;
1010
use PHPModelGenerator\Exception\RenderException;
1111
use PHPModelGenerator\Exception\ValidationException;
12+
use PHPModelGenerator\Model\Validator\AbstractComposedPropertyValidator;
1213
use PHPModelGenerator\SchemaProcessor\Hook\SchemaHookResolver;
1314
use PHPModelGenerator\SchemaProcessor\PostProcessor\PostProcessorInterface;
1415
use PHPModelGenerator\Utils\RenderHelper;
@@ -124,32 +125,40 @@ protected function renderClass(GeneratorConfiguration $generatorConfiguration):
124125
$render = new Render(__DIR__ . '/../Templates/');
125126
$namespace = trim(join('\\', [$generatorConfiguration->getNamespacePrefix(), $this->classPath]), '\\');
126127

127-
$use = array_merge(
128-
$this->schema->getUsedClasses(),
129-
$generatorConfiguration->collectErrors()
130-
? [$generatorConfiguration->getErrorRegistryClass()]
131-
: [ValidationException::class]
128+
$use = array_unique(
129+
array_merge(
130+
$this->schema->getUsedClasses(),
131+
$generatorConfiguration->collectErrors()
132+
? [$generatorConfiguration->getErrorRegistryClass()]
133+
: [ValidationException::class]
134+
)
132135
);
133136

134137
// filter out non-compound uses and uses which link to the current namespace
135138
$use = array_filter($use, function ($classPath) use ($namespace) {
136-
return strstr(str_replace($namespace, '', $classPath), '\\') ||
139+
return strstr(trim(str_replace("$namespace", '', $classPath), '\\'), '\\') ||
137140
(!strstr($classPath, '\\') && !empty($namespace));
138141
});
139142

140143
try {
141144
$class = $render->renderTemplate(
142145
'Model.phptpl',
143146
[
144-
'namespace' => $namespace,
145-
'use' => 'use ' . join(";\nuse ", array_unique($use)) . ';',
146-
'class' => $this->className,
147-
'schema' => $this->schema,
148-
'schemaHookResolver' => new SchemaHookResolver($this->schema),
149-
'generatorConfiguration' => $generatorConfiguration,
150-
'viewHelper' => new RenderHelper($generatorConfiguration),
147+
'namespace' => $namespace,
148+
'use' => $use,
149+
'class' => $this->className,
150+
'schema' => $this->schema,
151+
'schemaHookResolver' => new SchemaHookResolver($this->schema),
152+
'generatorConfiguration' => $generatorConfiguration,
153+
'viewHelper' => new RenderHelper($generatorConfiguration),
151154
// one hack a day keeps the problems away. Make true literal available for the templating. Easy fix
152-
'true' => true,
155+
'true' => true,
156+
'baseValidatorsWithoutCompositions' => array_filter(
157+
$this->schema->getBaseValidators(),
158+
function ($validator) {
159+
return !is_a($validator, AbstractComposedPropertyValidator::class);
160+
}
161+
),
153162
]
154163
);
155164
} catch (PHPMicroTemplateException $exception) {

0 commit comments

Comments
 (0)