Skip to content

Post Processor #16

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 8 commits into from
Jul 14, 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
101 changes: 101 additions & 0 deletions docs/source/generator/postProcessor.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
Post Processor
==============

Post processors provide an easy way to extend your generated code. A post processor can be added to your `ModelGenerator` object:

.. code-block:: php

$generator = new ModelGenerator();
$generator->addPostProcessor(new PopulatePostProcessor());

$files = $generator->generateModelDirectory(__DIR__ . '/result')
->generateModels(new RecursiveDirectoryProvider(__DIR__ . '/schema'), __DIR__ . '/result');

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, ...).

Builtin Post Processors
-----------------------

PopulatePostProcessor
^^^^^^^^^^^^^^^^^^^^^

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.

.. code-block:: json

{
"$id": "example",
"type": "object",
"properties": {
"value": {
"type": "string"
}
}
}

Generated interface with the **PopulatePostProcessor**:

.. code-block:: php

public function getRawModelDataInput(): array;
public function populate(array $modelData): self;

public function setExample(float $example): self;
public function getExample(): float;

Now let's have a look at the behaviour of the generated model:

.. code-block:: php

// initialize the model with a valid value
$example = new Example(['value' => 'Hello World']);
$example->getRawModelDataInput(); // returns ['value' => 'Hello World']

// add an additional property to the model.
// if additional property constraints are defined in your JSON-Schema
// each additional property will be validated against the defined constraints.
$example->populate(['additionalValue' => 12]);
$example->getRawModelDataInput(); // returns ['value' => 'Hello World', 'additionalValue' => 12]

// update an existing property with a valid value
$example->populate(['value' => 'Good night!']);
$example->getRawModelDataInput(); // returns ['value' => 'Good night!', 'additionalValue' => 12]

// update an existing property with an invalid value which will throw an exception
try {
$example->populate(['value' => false]);
} catch (Exception $e) {
// perform error handling
}
// if the update of the model fails no values will be updated
$example->getRawModelDataInput(); // returns ['value' => 'Good night!', 'additionalValue' => 12]

.. warning::

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

Custom Post Processors
----------------------

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.

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:

.. code-block:: php

namespace MyApp\Model\Generator\PostProcessor;

use MyApp\Model\ActiveRecordTrait;
use PHPModelGenerator\SchemaProcessor\PostProcessor\PostProcessorInterface;

class ActiveRecordPostProcessor implements PostProcessorInterface
{
public function process(Schema $schema, GeneratorConfiguration $generatorConfiguration): void
{
$schema->addTrait(ActiveRecordTrait::class);
}
}

.. hint::

For examples how to implement a custom post processor have a look at the built in post processors located at **src/SchemaProcessor/PostProcessor/**
5 changes: 5 additions & 0 deletions docs/source/gettingStarted.rst
Original file line number Diff line number Diff line change
Expand Up @@ -280,3 +280,8 @@ Custom filter
addFilter(FilterInterface $customFilter);

Add a custom filter to the generator. For more details see `Filter <nonStandardExtensions/filter.html>`__.

Post Processors
---------------

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>`__.
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ Generates PHP model classes from JSON-Schema files including validation and prov
.. include:: toc-types.rst
.. include:: toc-complexTypes.rst
.. include:: toc-combinedSchemas.rst
.. include:: toc-generator.rst
.. include:: toc-nonStandardExtensions.rst
5 changes: 5 additions & 0 deletions docs/source/toc-generator.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.. toctree::
:caption: Extending the generator
:maxdepth: 1

generator/postProcessor
37 changes: 22 additions & 15 deletions src/Model/GeneratorConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
use PHPModelGenerator\Utils\ClassNameGenerator;
use PHPModelGenerator\Utils\ClassNameGeneratorInterface;
use PHPModelGenerator\Exception\ErrorRegistryException;
use PHPModelGenerator\Exception\ValidationException;

/**
* Class GeneratorConfiguration
Expand Down Expand Up @@ -67,22 +66,13 @@ public function __construct()
*/
public function addFilter(FilterInterface $filter): self
{
if (!(count($filter->getFilter()) === 2) ||
!is_string($filter->getFilter()[0]) ||
!is_string($filter->getFilter()[1]) ||
!is_callable($filter->getFilter())
) {
throw new InvalidFilterException("Invalid filter callback for filter {$filter->getToken()}");
}
$this->validateFilterCallback($filter->getFilter(), "Invalid filter callback for filter {$filter->getToken()}");

if ($filter instanceof TransformingFilterInterface) {
if (!(count($filter->getSerializer()) === 2) ||
!is_string($filter->getSerializer()[0]) ||
!is_string($filter->getSerializer()[1]) ||
!is_callable($filter->getSerializer())
) {
throw new InvalidFilterException("Invalid serializer callback for filter {$filter->getToken()}");
}
$this->validateFilterCallback(
$filter->getSerializer(),
"Invalid serializer callback for filter {$filter->getToken()}"
);
}

foreach ($filter->getAcceptedTypes() as $acceptedType) {
Expand All @@ -98,6 +88,23 @@ public function addFilter(FilterInterface $filter): self
return $this;
}

/**
* @param array $callback
* @param string $message
*
* @throws InvalidFilterException
*/
private function validateFilterCallback(array $callback, string $message): void
{
if (!(count($callback) === 2) ||
!is_string($callback[0]) ||
!is_string($callback[1]) ||
!is_callable($callback)
) {
throw new InvalidFilterException($message);
}
}

/**
* Get a filter by the given token
*
Expand Down
15 changes: 15 additions & 0 deletions src/Model/MethodInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types = 1);

namespace PHPModelGenerator\Model;

interface MethodInterface
{
/**
* Returns the code of the method including the function signature
*
* @return string
*/
public function getCode(): string;
}
19 changes: 16 additions & 3 deletions src/Model/Property/Property.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

use PHPModelGenerator\Exception\SchemaException;
use PHPModelGenerator\Model\Schema;
use PHPModelGenerator\Model\SchemaDefinition\JsonSchema;
use PHPModelGenerator\Model\SchemaDefinition\JsonSchemaTrait;
use PHPModelGenerator\Model\Validator;
use PHPModelGenerator\Model\Validator\PropertyValidatorInterface;
use PHPModelGenerator\PropertyProcessor\Decorator\Property\PropertyDecoratorInterface;
Expand All @@ -18,6 +20,8 @@
*/
class Property implements PropertyInterface
{
use JsonSchemaTrait;

/** @var string */
protected $name = '';
/** @var string */
Expand Down Expand Up @@ -49,16 +53,19 @@ class Property implements PropertyInterface
*
* @param string $name
* @param string $type
* @param JsonSchema $jsonSchema
* @param string $description
*
* @throws SchemaException
*/
public function __construct(string $name, string $type, string $description = '')
public function __construct(string $name, string $type, JsonSchema $jsonSchema, string $description = '')
{
$this->attribute = $this->processAttributeName($name);
$this->name = $name;
$this->type = $type;
$this->jsonSchema = $jsonSchema;
$this->description = $description;

$this->attribute = $this->processAttributeName($name);
}

/**
Expand Down Expand Up @@ -254,7 +261,13 @@ function ($element) {
$attributeName = lcfirst(join('', $elements));

if (empty($attributeName)) {
throw new SchemaException("Property name '$name' results in an empty attribute name");
throw new SchemaException(
sprintf(
"Property name '%s' results in an empty attribute name in file %s",
$name,
$this->jsonSchema->getFile()
)
);
}

return $attributeName;
Expand Down
8 changes: 8 additions & 0 deletions src/Model/Property/PropertyInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace PHPModelGenerator\Model\Property;

use PHPModelGenerator\Model\Schema;
use PHPModelGenerator\Model\SchemaDefinition\JsonSchema;
use PHPModelGenerator\Model\Validator;
use PHPModelGenerator\Model\Validator\PropertyValidatorInterface;
use PHPModelGenerator\PropertyProcessor\Decorator\Property\PropertyDecoratorInterface;
Expand Down Expand Up @@ -179,4 +180,11 @@ public function setNestedSchema(Schema $schema);
* @return null|Schema
*/
public function getNestedSchema(): ?Schema;

/**
* Get the JSON schema used to set up the property
*
* @return JsonSchema
*/
public function getJsonSchema(): JsonSchema;
}
9 changes: 9 additions & 0 deletions src/Model/Property/PropertyProxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace PHPModelGenerator\Model\Property;

use PHPModelGenerator\Model\SchemaDefinition\JsonSchema;
use PHPModelGenerator\Model\SchemaDefinition\ResolvedDefinitionsCollection;
use PHPModelGenerator\Model\Schema;
use PHPModelGenerator\Model\Validator\PropertyValidatorInterface;
Expand Down Expand Up @@ -219,4 +220,12 @@ public function getNestedSchema(): ?Schema
{
return $this->getProperty()->getNestedSchema();
}

/**
* @inheritdoc
*/
public function getJsonSchema(): JsonSchema
{
return $this->getProperty()->getJsonSchema();
}
}
69 changes: 0 additions & 69 deletions src/Model/Property/Serializer/TransformingFilterSerializer.php

This file was deleted.

Loading