Skip to content

Commit 469b733

Browse files
committed
Add PopulatePostProcessor
Move base validators to a separate function inside the generated models Add post processor documentation
1 parent 2ede6ba commit 469b733

File tree

14 files changed

+304
-32
lines changed

14 files changed

+304
-32
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
Post Processor
2+
==============
3+
4+
Post processors provide an easy way to extend your generated code. A post processor can be added to your `ModelGenerator` object:
5+
6+
.. code-block:: php
7+
8+
$generator = new ModelGenerator();
9+
$generator->addPostProcessor(new PopulatePostProcessor());
10+
11+
$files = $generator->generateModelDirectory(__DIR__ . '/result')
12+
->generateModels(new RecursiveDirectoryProvider(__DIR__ . '/schema'), __DIR__ . '/result');
13+
14+
All added post processors will be executed after a schema was processed and before a model is rendered. Consequently a post processor can be used to change the generated class or to extend the class. Also additional tasks which don't change the rendered code may be executed (eg. create a documentation file for the class, create SQL create statements for tables representing the class, ...).
15+
16+
Builtin Post Processors
17+
-----------------------
18+
19+
PopulatePostProcessor
20+
^^^^^^^^^^^^^^^^^^^^^
21+
22+
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.
23+
24+
.. code-block:: json
25+
26+
{
27+
"$id": "example",
28+
"type": "object",
29+
"properties": {
30+
"value": {
31+
"type": "string"
32+
}
33+
}
34+
}
35+
36+
Generated interface with the **PopulatePostProcessor**:
37+
38+
.. code-block:: php
39+
40+
public function getRawModelDataInput(): array;
41+
public function populate(array $modelData): self;
42+
43+
public function setExample(float $example): self;
44+
public function getExample(): float;
45+
46+
Now let's have a look at the behaviour of the generated model:
47+
48+
.. code-block:: php
49+
50+
// initialize the model with a valid value
51+
$example = new Example(['value' => 'Hello World']);
52+
$example->getRawModelDataInput(); // returns ['value' => 'Hello World']
53+
54+
// add an additional property to the model.
55+
// if additional property constraints are defined in your JSON-Schema
56+
// each additional property will be validated against the defined constraints.
57+
$example->populate(['additionalValue' => 12]);
58+
$example->getRawModelDataInput(); // returns ['value' => 'Hello World', 'additionalValue' => 12]
59+
60+
// update an existing property with a valid value
61+
$example->populate(['value' => 'Good night!']);
62+
$example->getRawModelDataInput(); // returns ['value' => 'Good night!', 'additionalValue' => 12]
63+
64+
// update an existing property with an invalid value which will throw an exception
65+
try {
66+
$example->populate(['value' => false]);
67+
} catch (Exception $e) {
68+
// perform error handling
69+
}
70+
// if the update of the model fails no values will be updated
71+
$example->getRawModelDataInput(); // returns ['value' => 'Good night!', 'additionalValue' => 12]
72+
73+
Custom Post Processors
74+
----------------------
75+
76+
You can implement custom post processors to accomplish your tasks. Each post processor must implement the **PHPModelGenerator\\SchemaProcessor\\PostProcessor\\PostProcessorInterface**. If you have implemented a post processor add the post processor to your `ModelGenerator` and the post processor will be executed for each class.
77+
78+
A custom post processor which adds a custom trait to the generated model (eg. a trait adding methods for an active record pattern implementation) may look like:
79+
80+
.. code-block:: php
81+
82+
namespace MyApp\Model\Generator\PostProcessor;
83+
84+
use MyApp\Model\ActiveRecordTrait;
85+
use PHPModelGenerator\SchemaProcessor\PostProcessor\PostProcessorInterface;
86+
87+
class ActiveRecordPostProcessor implements PostProcessorInterface
88+
{
89+
public function process(Schema $schema, GeneratorConfiguration $generatorConfiguration): void
90+
{
91+
$schema->addTrait(ActiveRecordTrait::class);
92+
}
93+
}

docs/source/gettingStarted.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,3 +280,8 @@ Custom filter
280280
addFilter(FilterInterface $customFilter);
281281
282282
Add a custom filter to the generator. For more details see `Filter <nonStandardExtensions/filter.html>`__.
283+
284+
Post Processors
285+
---------------
286+
287+
Additionally to the described generator configuration options you can add post processors to your model generator object to change or extend the generated code. For more details see `post processors <generator/postProcessor.html>`__.

docs/source/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ Generates PHP model classes from JSON-Schema files including validation and prov
1212
.. include:: toc-types.rst
1313
.. include:: toc-complexTypes.rst
1414
.. include:: toc-combinedSchemas.rst
15+
.. include:: toc-generator.rst
1516
.. include:: toc-nonStandardExtensions.rst

docs/source/toc-generator.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.. toctree::
2+
:caption: Extending the generator
3+
:maxdepth: 1
4+
5+
generator/postProcessor

src/Model/GeneratorConfiguration.php

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -66,22 +66,13 @@ public function __construct()
6666
*/
6767
public function addFilter(FilterInterface $filter): self
6868
{
69-
if (!(count($filter->getFilter()) === 2) ||
70-
!is_string($filter->getFilter()[0]) ||
71-
!is_string($filter->getFilter()[1]) ||
72-
!is_callable($filter->getFilter())
73-
) {
74-
throw new InvalidFilterException("Invalid filter callback for filter {$filter->getToken()}");
75-
}
69+
$this->validateFilterCallback($filter->getFilter(), "Invalid filter callback for filter {$filter->getToken()}");
7670

7771
if ($filter instanceof TransformingFilterInterface) {
78-
if (!(count($filter->getSerializer()) === 2) ||
79-
!is_string($filter->getSerializer()[0]) ||
80-
!is_string($filter->getSerializer()[1]) ||
81-
!is_callable($filter->getSerializer())
82-
) {
83-
throw new InvalidFilterException("Invalid serializer callback for filter {$filter->getToken()}");
84-
}
72+
$this->validateFilterCallback(
73+
$filter->getSerializer(),
74+
"Invalid serializer callback for filter {$filter->getToken()}"
75+
);
8576
}
8677

8778
foreach ($filter->getAcceptedTypes() as $acceptedType) {
@@ -97,6 +88,23 @@ public function addFilter(FilterInterface $filter): self
9788
return $this;
9889
}
9990

91+
/**
92+
* @param array $callback
93+
* @param string $message
94+
*
95+
* @throws InvalidFilterException
96+
*/
97+
private function validateFilterCallback(array $callback, string $message): void
98+
{
99+
if (!(count($callback) === 2) ||
100+
!is_string($callback[0]) ||
101+
!is_string($callback[1]) ||
102+
!is_callable($callback)
103+
) {
104+
throw new InvalidFilterException($message);
105+
}
106+
}
107+
100108
/**
101109
* Get a filter by the given token
102110
*

src/Model/RenderJob.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,12 @@ public function __construct(
5555

5656
/**
5757
* @param PostProcessorInterface[] $postProcessors
58+
* @param GeneratorConfiguration $generatorConfiguration
5859
*/
59-
public function postProcess(array $postProcessors)
60+
public function postProcess(array $postProcessors, GeneratorConfiguration $generatorConfiguration)
6061
{
6162
foreach ($postProcessors as $postProcessor) {
62-
$postProcessor->process($this->schema);
63+
$postProcessor->process($this->schema, $generatorConfiguration);
6364
}
6465
}
6566

src/PropertyProcessor/Property/BaseProcessor.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,10 @@ protected function addMaxPropertiesValidator(string $propertyName, array $proper
146146

147147
$this->schema->addBaseValidator(
148148
new PropertyValidator(
149-
sprintf('count($modelData) > %d', $propertyData['maxProperties']),
149+
sprintf(
150+
'count(array_merge(array_keys($this->rawModelDataInput), array_keys($modelData))) > %d',
151+
$propertyData['maxProperties']
152+
),
150153
MaxPropertiesException::class,
151154
[$propertyName, $propertyData['maxProperties']]
152155
)
@@ -167,7 +170,10 @@ protected function addMinPropertiesValidator(string $propertyName, array $proper
167170

168171
$this->schema->addBaseValidator(
169172
new PropertyValidator(
170-
sprintf('count($modelData) < %d', $propertyData['minProperties']),
173+
sprintf(
174+
'count(array_merge(array_keys($this->rawModelDataInput), array_keys($modelData))) < %d',
175+
$propertyData['minProperties']
176+
),
171177
MinPropertiesException::class,
172178
[$propertyName, $propertyData['minProperties']]
173179
)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace PHPModelGenerator\SchemaProcessor\PostProcessor;
6+
7+
use PHPModelGenerator\Model\GeneratorConfiguration;
8+
use PHPModelGenerator\Model\Schema;
9+
10+
/**
11+
* Class PopulatePostProcessor
12+
*
13+
* @package PHPModelGenerator\SchemaProcessor\PostProcessor
14+
*/
15+
class PopulatePostProcessor implements PostProcessorInterface
16+
{
17+
public function process(Schema $schema, GeneratorConfiguration $generatorConfiguration): void
18+
{
19+
$schema->addMethod('populate', new RenderedMethod($schema, $generatorConfiguration, 'Populate.phptpl'));
20+
}
21+
}

src/SchemaProcessor/PostProcessor/PostProcessorInterface.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace PHPModelGenerator\SchemaProcessor\PostProcessor;
66

7+
use PHPModelGenerator\Model\GeneratorConfiguration;
78
use PHPModelGenerator\Model\Schema;
89

910
interface PostProcessorInterface
@@ -12,6 +13,7 @@ interface PostProcessorInterface
1213
* Have fun doin' crazy stuff with the schema
1314
*
1415
* @param Schema $schema
16+
* @param GeneratorConfiguration $generatorConfiguration
1517
*/
16-
public function process(Schema $schema): void;
18+
public function process(Schema $schema, GeneratorConfiguration $generatorConfiguration): void;
1719
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace PHPModelGenerator\SchemaProcessor\PostProcessor;
6+
7+
use PHPMicroTemplate\Exception\FileSystemException;
8+
use PHPMicroTemplate\Exception\SyntaxErrorException;
9+
use PHPMicroTemplate\Exception\UndefinedSymbolException;
10+
use PHPMicroTemplate\Render;
11+
use PHPModelGenerator\Model\GeneratorConfiguration;
12+
use PHPModelGenerator\Model\MethodInterface;
13+
use PHPModelGenerator\Model\Schema;
14+
use PHPModelGenerator\Utils\RenderHelper;
15+
16+
/**
17+
* Class RenderedMethod
18+
*
19+
* @package PHPModelGenerator\SchemaProcessor\PostProcessor
20+
*/
21+
class RenderedMethod implements MethodInterface
22+
{
23+
/** @var Render */
24+
static private $renderer;
25+
26+
/** @var Schema */
27+
private $schema;
28+
/** @var GeneratorConfiguration */
29+
private $generatorConfiguration;
30+
/** @var string */
31+
private $template;
32+
/** @var array */
33+
private $templateValues;
34+
35+
public function __construct(
36+
Schema $schema,
37+
GeneratorConfiguration $generatorConfiguration,
38+
string $template,
39+
array $templateValues = []
40+
) {
41+
$this->schema = $schema;
42+
$this->generatorConfiguration = $generatorConfiguration;
43+
$this->template = $template;
44+
$this->templateValues = $templateValues;
45+
}
46+
47+
/**
48+
* @inheritDoc
49+
*
50+
* @throws FileSystemException
51+
* @throws SyntaxErrorException
52+
* @throws UndefinedSymbolException
53+
*/
54+
public function getCode(): string
55+
{
56+
return $this->getRenderer()->renderTemplate(
57+
$this->template,
58+
array_merge(
59+
[
60+
'schema' => $this->schema,
61+
'viewHelper' => new RenderHelper($this->generatorConfiguration),
62+
'generatorConfiguration' => $this->generatorConfiguration,
63+
],
64+
$this->templateValues
65+
)
66+
);
67+
}
68+
69+
/**
70+
* @return Render
71+
*/
72+
protected function getRenderer(): Render
73+
{
74+
if (!self::$renderer) {
75+
self::$renderer = new Render(__DIR__ . DIRECTORY_SEPARATOR . 'Templates' . DIRECTORY_SEPARATOR);
76+
}
77+
78+
return self::$renderer;
79+
}
80+
}

0 commit comments

Comments
 (0)