Skip to content

Commit a13f4ad

Browse files
committed
Add filter
Add readonly documentation
1 parent cecd4e3 commit a13f4ad

20 files changed

+724
-3
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
],
1313
"require": {
1414
"symplify/easy-coding-standard": "^6.0.4",
15-
"wol-soft/php-json-schema-model-generator-production": "^0.4.0",
15+
"wol-soft/php-json-schema-model-generator-production": "^0.5.0",
1616
"wol-soft/php-micro-template": "^1.3.1",
1717

1818
"php": ">=7.2",

docs/source/generic/readonly.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
Readonly values
2+
===============
3+
4+
By default all classes are immutable. If the GeneratorConfiguration option for immutability is disabled setters for all properties are generated. If single properties should be readonly the keyword `readonly` can be used.
5+
6+
.. code-block:: json
7+
8+
{
9+
"$id": "person",
10+
"type": "object",
11+
"properties": {
12+
"name": {
13+
"type": "string",
14+
"readonly": true
15+
},
16+
"age": {
17+
"type": "integer"
18+
}
19+
}
20+
}
21+
22+
Generated interface (with immutability disabled):
23+
24+
.. code-block:: php
25+
26+
public function getName(): ?string;
27+
28+
public function setAge(?int $example): self;
29+
public function getAge(): ?int;

docs/source/generic/required.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Behaviour with different inputs:
3030
3131
// property example not provided
3232
$example = new Example([]);
33-
$example->getExample(); // returns "Not provided"
33+
$example->getExample(); // returns "Not provided". If no default value has been defined in the schema NULL would be returned
3434
3535
// property example explicitly set to null (allowed as the property isn't required)
3636
$example = new Example(['example' => null]);
@@ -55,7 +55,7 @@ By setting the property to a required value the property must be always provided
5555
"required": ["example"]
5656
}
5757
58-
Generated interface (typehints not nullable any longer):
58+
Generated interface (type hints not nullable any longer):
5959

6060
.. code-block:: php
6161

docs/source/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ 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-nonStandardExtensions.rst
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
Filter
2+
======
3+
4+
Filter can be used to preprocess values. Filters are applied after the required and type validation. If a filter is applied to a property which has a type which is not supported by the filter an exception will be thrown.
5+
Filters can be either supplied as a string or as a list of filters:
6+
7+
.. code-block:: json
8+
9+
{
10+
"$id": "person",
11+
"type": "object",
12+
"properties": {
13+
"firstname": {
14+
"type": "string",
15+
"filter": "trim"
16+
},
17+
"lastname": {
18+
"type": "string",
19+
"filter": [
20+
"trim"
21+
]
22+
}
23+
}
24+
}
25+
26+
Builtin filter
27+
--------------
28+
29+
trim
30+
^^^^
31+
32+
The trim filter is only valid for string properties.
33+
34+
.. code-block:: json
35+
36+
{
37+
"$id": "person",
38+
"type": "object",
39+
"properties": {
40+
"name": {
41+
"type": "string",
42+
"filter": "trim",
43+
"minLength": 2,
44+
}
45+
}
46+
}
47+
48+
Let's have a look how the generated model behaves:
49+
50+
.. code-block:: php
51+
52+
// valid, the name will be NULL as the name is not required
53+
$person = new Person();
54+
55+
// Throws an exception as the name provides an invalid value after being trimmed.
56+
// Exception: 'Value for name must not be shorter than 2'
57+
$person = new Person(['name' => ' A ']);
58+
59+
// A valid example
60+
$person = new Person(['name' => ' Albert ']);
61+
$person->getName(); // returns 'Albert'
62+
// the raw model data input is not affected by the filter
63+
$person->getRawModelDataInput(); // returns ['name' => ' Albert ']
64+
65+
// If setters are generated the setters also perform validations.
66+
// Exception: 'Value for name must not be shorter than 2'
67+
$person->setName(' D ');
68+
69+
If the filter trim is used for a property which doesn't require a string value and a non string value is provided an exception will be thrown:
70+
71+
* Filter trim is not compatible with property type __TYPE__ for property __PROPERTY_NAME__
72+
73+
Custom filter
74+
-------------
75+
76+
You can implement custom filter and use them in your schema files. You must add your custom filter to the generator configuration.
77+
78+
.. code-block:: php
79+
80+
$generator = new Generator(
81+
(new GeneratorConfiguration())
82+
->addFilter(new UppercaseFilter())
83+
);
84+
85+
Your filter must implement the interface **PHPModelGenerator\\PropertyProcessor\\Filter\\FilterInterface**. Make sure the given callable array returned by **getFilter** is accessible as well during the generation process as during code execution using the generated model.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.. toctree::
2+
:caption: Non standard extensions
3+
:maxdepth: 1
4+
5+
nonStandardExtensions/filter
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace PHPModelGenerator\Exception;
6+
7+
/**
8+
* Class InvalidFilterException
9+
*
10+
* @package PHPModelGenerator\Exception
11+
*/
12+
class InvalidFilterException extends PHPModelGeneratorException
13+
{
14+
}

src/Model/GeneratorConfiguration.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
namespace PHPModelGenerator\Model;
66

7+
use PHPModelGenerator\Exception\InvalidFilterException;
8+
use PHPModelGenerator\PropertyProcessor\Filter\FilterInterface;
9+
use PHPModelGenerator\PropertyProcessor\Filter\TrimFilter;
710
use PHPModelGenerator\Utils\ClassNameGenerator;
811
use PHPModelGenerator\Utils\ClassNameGeneratorInterface;
912
use PHPModelGenerator\Exception\ErrorRegistryException;
@@ -34,13 +37,54 @@ class GeneratorConfiguration
3437
protected $serialization = false;
3538
/** @var ClassNameGeneratorInterface */
3639
protected $classNameGenerator;
40+
/** @var FilterInterface[] */
41+
protected $filter;
3742

3843
/**
3944
* GeneratorConfiguration constructor.
4045
*/
4146
public function __construct()
4247
{
4348
$this->classNameGenerator = new ClassNameGenerator();
49+
50+
$this->addFilter(new TrimFilter());
51+
}
52+
53+
/**
54+
* Add an additional filter
55+
*
56+
* @param FilterInterface $filter
57+
*
58+
* @return $this
59+
*
60+
* @throws InvalidFilterException
61+
*/
62+
public function addFilter(FilterInterface $filter): self
63+
{
64+
// TODO: check accepted types
65+
if (!(count($filter->getFilter()) === 2) ||
66+
!is_string($filter->getFilter()[0]) ||
67+
!is_string($filter->getFilter()[1]) ||
68+
!is_callable($filter->getFilter())
69+
) {
70+
throw new InvalidFilterException("Invalid filter callback for filter {$filter->getToken()}");
71+
}
72+
73+
$this->filter[$filter->getToken()] = $filter;
74+
75+
return $this;
76+
}
77+
78+
/**
79+
* Get a filter by the given token
80+
*
81+
* @param string $token
82+
*
83+
* @return FilterInterface|null
84+
*/
85+
public function getFilter(string $token): ?FilterInterface
86+
{
87+
return $this->filter[$token] ?? null;
4488
}
4589

4690
/**
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace PHPModelGenerator\Model\Validator;
6+
7+
use PHPModelGenerator\Exception\SchemaException;
8+
use PHPModelGenerator\Model\Property\PropertyInterface;
9+
use PHPModelGenerator\PropertyProcessor\Filter\FilterInterface;
10+
11+
class FilterValidator extends PropertyValidator
12+
{
13+
/**
14+
* FilterValidator constructor.
15+
*
16+
* @param FilterInterface $filter
17+
* @param PropertyInterface $property
18+
*
19+
* @throws SchemaException
20+
*/
21+
public function __construct(FilterInterface $filter, PropertyInterface $property)
22+
{
23+
if (!empty($filter->getAcceptedTypes()) &&
24+
$property->getType() &&
25+
!in_array($property->getType(), $filter->getAcceptedTypes())
26+
) {
27+
throw new SchemaException(
28+
sprintf(
29+
'Filter %s is not compatible with property type %s for property %s',
30+
$filter->getToken(),
31+
$property->getType(),
32+
$property->getName()
33+
)
34+
);
35+
}
36+
37+
parent::__construct(
38+
// check if the given value has a type matched by the filter
39+
(!empty($filter->getAcceptedTypes())
40+
? '($value !== null && (!is_' . implode('($value) && !is_', $filter->getAcceptedTypes()) . '($value)))'
41+
: ''
42+
) . sprintf(
43+
' || (($value = call_user_func([%s::class, "%s"], $value)) && false)',
44+
$filter->getFilter()[0],
45+
$filter->getFilter()[1]
46+
),
47+
sprintf(
48+
'Filter %s is not compatible with property type " . gettype($value) . " for property %s',
49+
$filter->getToken(),
50+
$property->getName()
51+
)
52+
);
53+
}
54+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace PHPModelGenerator\PropertyProcessor\Filter;
6+
7+
interface FilterInterface
8+
{
9+
/**
10+
* Return a list of accepted data types for the filter (eg. ['string', 'int']). If the filter is applied to a
11+
* value which doesn't match an accepted type an exception will be thrown. If an empty array is returned the filter
12+
* will accept all types.
13+
*
14+
* @return array
15+
*/
16+
public function getAcceptedTypes(): array;
17+
18+
/**
19+
* Return the token for the filter
20+
*
21+
* @return string
22+
*/
23+
public function getToken(): string;
24+
25+
/**
26+
* Return the filter to apply. Make sure the returned array is a callable which is also callable after the
27+
* render process
28+
*
29+
* @return array
30+
*/
31+
public function getFilter(): array;
32+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace PHPModelGenerator\PropertyProcessor\Filter;
6+
7+
use PHPModelGenerator\Exception\SchemaException;
8+
use PHPModelGenerator\Model\GeneratorConfiguration;
9+
use PHPModelGenerator\Model\Property\PropertyInterface;
10+
use PHPModelGenerator\Model\Validator\FilterValidator;
11+
12+
/**
13+
* Class FilterProcessor
14+
*
15+
* @package PHPModelGenerator\PropertyProcessor\Filter
16+
*/
17+
class FilterProcessor
18+
{
19+
/**
20+
* @param PropertyInterface $property
21+
* @param $filterList
22+
* @param GeneratorConfiguration $generatorConfiguration
23+
*
24+
* @throws SchemaException
25+
*/
26+
public function process(
27+
PropertyInterface $property,
28+
$filterList,
29+
GeneratorConfiguration $generatorConfiguration
30+
): void {
31+
if (is_string($filterList)) {
32+
$filterList = [$filterList];
33+
}
34+
35+
foreach ($filterList as $filterToken) {
36+
if (!($filter = $generatorConfiguration->getFilter($filterToken))) {
37+
throw new SchemaException("Unsupported filter $filterToken");
38+
}
39+
40+
$property->addValidator(new FilterValidator($filter, $property), 3);
41+
}
42+
}
43+
}

0 commit comments

Comments
 (0)