Skip to content

Commit dc53974

Browse files
committed
Implementation draft for advanced filter logic
* Add support for filters which modify the internal type of a property (eg. transforming a string into an object). The modified type will be reflected into the generated Model (eg. the getter for a former string property may return a object now if the property was transformed by a filter) * Add support for filter options which may be defined in the JSON-Schema and passed through into the filter * Added additional builtin filter dateTime (transforms a string into a DateTime object) Cleaned up some namespace quirks
1 parent 7bebdc2 commit dc53974

File tree

17 files changed

+309
-57
lines changed

17 files changed

+309
-57
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": "^7.2.3",
15-
"wol-soft/php-json-schema-model-generator-production": "0.7.0",
15+
"wol-soft/php-json-schema-model-generator-production": "dev-AdvancedFilter",
1616
"wol-soft/php-micro-template": "^1.3.1",
1717

1818
"php": ">=7.2",

docs/source/nonStandardExtensions/filter.rst

Lines changed: 120 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,31 @@ Filters can be either supplied as a string or as a list of filters (multiple fil
2323
}
2424
}
2525
26+
If a list is used filters may include additional option parameters. In this case a single filter must be provided as an object with the key **filter** defining the filter:
27+
28+
.. code-block:: json
29+
30+
{
31+
"type": "object",
32+
"properties": {
33+
"created": {
34+
"type": "string",
35+
"filter": [
36+
{
37+
"filter": "dateTime",
38+
"denyEmptyValue": true
39+
}
40+
]
41+
}
42+
}
43+
}
44+
45+
.. warning::
46+
47+
Filters may change the type of the property. For example the builtin filter **dateTime** creates a DateTime object. Consequently further validations like pattern checks for the string property won't be performed.
48+
49+
The return type of the last applied filter will be used to define the type of the property inside the generated model (in the example given above the method **getCreated** will return a DateTime object).
50+
2651
Builtin filter
2752
--------------
2853

@@ -62,14 +87,69 @@ Let's have a look how the generated model behaves:
6287
// the raw model data input is not affected by the filter
6388
$person->getRawModelDataInput(); // returns ['name' => ' Albert ']
6489
65-
// If setters are generated the setters also perform validations.
90+
// If setters are generated the setters also execute the filter and perform validations.
6691
// Exception: 'Value for name must not be shorter than 2'
6792
$person->setName(' D ');
6893
6994
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:
7095

7196
* Filter trim is not compatible with property type __TYPE__ for property __PROPERTY_NAME__
7297

98+
dateTime
99+
^^^^^^^^
100+
101+
The dateTime filter is only valid for string properties.
102+
103+
.. code-block:: json
104+
105+
{
106+
"$id": "car",
107+
"type": "object",
108+
"properties": {
109+
"productionDate": {
110+
"type": "string",
111+
"filter": "dateTime"
112+
}
113+
}
114+
}
115+
116+
.. warning::
117+
118+
The dateTime filter modifies the type of your property
119+
120+
Generated interface:
121+
122+
.. code-block:: php
123+
124+
public function setProductionDate(?string $productionDate): self;
125+
public function getProductionDate(): ?DateTime;
126+
127+
Let's have a look how the generated model behaves:
128+
129+
.. code-block:: php
130+
131+
// valid, the name will be NULL as the productionDate is not required
132+
$person = new Car([]);
133+
134+
// Throws an exception as the provided value is not valid for the DateTime constructor
135+
$person = new Car(['productionDate' => 'Hello']);
136+
137+
// A valid example
138+
$person = new Car(['productionDate' => '2020-10-10']);
139+
$person->productionDate(); // returns a DateTime object
140+
// the raw model data input is not affected by the filter
141+
$person->getRawModelDataInput(); // returns ['productionDate' => '2020-10-10']
142+
143+
Additional options
144+
~~~~~~~~~~~~~~~~~~
145+
146+
================ ============= ===========
147+
Option Default value Description
148+
================ ============= ===========
149+
convertNullToNow false If null is provided a DateTime object with the current time will be created (works only if the property isn't required as null would be denied otherwise before the filter is executed)
150+
denyEmptyValue false An empty string value will be denied (by default an empty string value will result in a DateTime object with the current time)
151+
================ ============= ===========
152+
73153
Custom filter
74154
-------------
75155

@@ -83,7 +163,7 @@ You can implement custom filter and use them in your schema files. You must add
83163
);
84164
85165
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.
86-
The callable filter method must be a static method. Internally it will be called via *call_user_func*. A custom filter may look like:
166+
The callable filter method must be a static method. Internally it will be called via *call_user_func_array*. A custom filter may look like:
87167

88168
.. code-block:: php
89169
@@ -138,3 +218,41 @@ If the custom filter is added to the generator configuration you can now use the
138218
139219
$person = new Person(['name' => ' Albert ']);
140220
$person->getName(); // returns 'ALBERT'
221+
222+
Accessing additional filter options
223+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
224+
225+
Filters may handle additional configuration options like the builtin dateTime-filter. The options will be passed as an array as the second argument of your filter function. Let's assume you want to add additional options to your uppercase-filter you'd add the options parameter to your static filter implementation:
226+
227+
.. code-block:: php
228+
229+
public static function uppercase(?string $value, array $options): ?string
230+
{
231+
// do something with a custom option
232+
if ($options['onlyVocals'] ?? false) {
233+
// uppercase only the vocals of the provided value
234+
}
235+
236+
// ... default implementation
237+
}
238+
239+
The option will be available if your JSON-Schema uses the object-notation for the filter:
240+
241+
.. code-block:: json
242+
243+
{
244+
"$id": "person",
245+
"type": "object",
246+
"properties": {
247+
"name": {
248+
"type": "string",
249+
"filter": [
250+
{
251+
"filter": "uppercase",
252+
"onlyVocals": true
253+
},
254+
"trim"
255+
]
256+
}
257+
}
258+
}

src/Model/GeneratorConfiguration.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace PHPModelGenerator\Model;
66

77
use PHPModelGenerator\Exception\InvalidFilterException;
8+
use PHPModelGenerator\PropertyProcessor\Filter\DateTimeFilter;
89
use PHPModelGenerator\PropertyProcessor\Filter\FilterInterface;
910
use PHPModelGenerator\PropertyProcessor\Filter\TrimFilter;
1011
use PHPModelGenerator\Utils\ClassNameGenerator;
@@ -47,7 +48,9 @@ public function __construct()
4748
{
4849
$this->classNameGenerator = new ClassNameGenerator();
4950

50-
$this->addFilter(new TrimFilter());
51+
$this
52+
->addFilter(new DateTimeFilter())
53+
->addFilter(new TrimFilter());
5154
}
5255

5356
/**

src/Model/Property/Property.php

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ class Property implements PropertyInterface
2424
protected $attribute = '';
2525
/** @var string */
2626
protected $type = 'null';
27+
/** @var string|null */
28+
protected $outputType = null;
2729
/** @var bool */
2830
protected $isPropertyRequired = true;
2931
/** @var bool */
@@ -78,27 +80,28 @@ public function getAttribute(): string
7880
/**
7981
* @inheritdoc
8082
*/
81-
public function getType(): string
83+
public function getType(bool $outputType = false): string
8284
{
83-
return $this->type;
85+
return $outputType && $this->outputType !== null ? $this->outputType : $this->type;
8486
}
8587

8688
/**
8789
* @inheritdoc
8890
*/
89-
public function setType(string $type): PropertyInterface
91+
public function setType(string $type, ?string $outputType = null): PropertyInterface
9092
{
9193
$this->type = $type;
94+
$this->outputType = $outputType;
9295

9396
return $this;
9497
}
9598

9699
/**
97100
* @inheritdoc
98101
*/
99-
public function getTypeHint(): string
102+
public function getTypeHint(bool $outputType = false): string
100103
{
101-
$input = $this->type;
104+
$input = $outputType && $this->outputType !== null ? $this->outputType : $this->type;
102105

103106
foreach ($this->typeHintDecorators as $decorator) {
104107
$input = $decorator->decorate($input);

src/Model/Property/PropertyInterface.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,27 @@ public function getName(): string;
2828
public function getAttribute(): string;
2929

3030
/**
31+
* @param bool $outputType If set to true the output type will be returned (may differ from the base type)
32+
*
3133
* @return string
3234
*/
33-
public function getType(): string;
35+
public function getType(bool $outputType = false): string;
3436

3537
/**
3638
* @param string $type
39+
* @param string|null $outputType By default the output type will be equal to the base type but due to applied
40+
* filters the output type may change
3741
*
3842
* @return PropertyInterface
3943
*/
40-
public function setType(string $type): PropertyInterface;
44+
public function setType(string $type, ?string $outputType = null): PropertyInterface;
4145

4246
/**
47+
* @param bool $outputType If set to true the output type hint will be returned (may differ from the base type)
48+
*
4349
* @return string
4450
*/
45-
public function getTypeHint(): string;
51+
public function getTypeHint(bool $outputType = false): string;
4652

4753
/**
4854
* @param TypeHintDecoratorInterface $typeHintDecorator

src/Model/Property/PropertyProxy.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,25 +63,25 @@ public function getAttribute(): string
6363
/**
6464
* @inheritdoc
6565
*/
66-
public function getType(): string
66+
public function getType(bool $outputType = false): string
6767
{
68-
return $this->getProperty()->getType();
68+
return $this->getProperty()->getType($outputType);
6969
}
7070

7171
/**
7272
* @inheritdoc
7373
*/
74-
public function setType(string $type): PropertyInterface
74+
public function setType(string $type, ?string $outputType = null): PropertyInterface
7575
{
76-
return $this->getProperty()->setType($type);
76+
return $this->getProperty()->setType($type, $outputType);
7777
}
7878

7979
/**
8080
* @inheritdoc
8181
*/
82-
public function getTypeHint(): string
82+
public function getTypeHint(bool $outputType = false): string
8383
{
84-
return $this->getProperty()->getTypeHint();
84+
return $this->getProperty()->getTypeHint($outputType);
8585
}
8686

8787
/**

src/Model/RenderJob.php

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public function render(string $destination, GeneratorConfiguration $generatorCon
7272
}
7373

7474
if ($generatorConfiguration->isOutputEnabled()) {
75-
echo "Rendered class {$generatorConfiguration->getNamespacePrefix()}$this->classPath\\$this->className\n";
75+
echo "Rendered class {$generatorConfiguration->getNamespacePrefix()}\\$this->classPath\\$this->className\n";
7676
}
7777
}
7878

@@ -107,24 +107,20 @@ protected function generateModelDirectory(string $destination, string $classPath
107107
*/
108108
protected function renderClass(GeneratorConfiguration $generatorConfiguration): string
109109
{
110-
$getFullNamespace = function (string $classPath) use ($generatorConfiguration): string {
111-
return trim($generatorConfiguration->getNamespacePrefix() . $classPath, '\\');
112-
};
113-
114-
$render = new Render(__DIR__ . "/../Templates/");
115-
$namespace = $getFullNamespace($this->classPath);
110+
$render = new Render(__DIR__ . '/../Templates/');
111+
$namespace = trim(join('\\', [$generatorConfiguration->getNamespacePrefix(), $this->classPath]), '\\');
116112

117113
$use = array_merge(
118-
array_map($getFullNamespace, $this->schema->getUsedClasses()),
114+
$this->schema->getUsedClasses(),
119115
$generatorConfiguration->collectErrors()
120116
? [$generatorConfiguration->getErrorRegistryClass()]
121117
: [$generatorConfiguration->getExceptionClass()]
122118
);
123119

124-
// TODO: filter out uses in the same namespace
125-
// filter out non-compound namespaces
126-
$use = array_filter($use, function ($classPath) {
127-
return strstr($classPath, '\\');
120+
// filter out non-compound uses and uses which link to the current namespace
121+
$use = array_filter($use, function ($classPath) use ($namespace) {
122+
return strstr(str_replace($namespace, '', $classPath), '\\') ||
123+
(!strstr($classPath, '\\') && !empty($namespace));
128124
});
129125

130126
try {
@@ -139,6 +135,8 @@ protected function renderClass(GeneratorConfiguration $generatorConfiguration):
139135
'generatorConfiguration' => $generatorConfiguration,
140136
'viewHelper' => new RenderHelper($generatorConfiguration),
141137
'initialClass' => $this->initialClass,
138+
// one hack a day keeps the problems away. Make true literal available for the templating. Easy fix
139+
'true' => true,
142140
]
143141
);
144142
} catch (PHPMicroTemplateException $exception) {

src/Model/Schema.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ public function getSchemaDictionary(): SchemaDefinitionDictionary
159159
*/
160160
public function addUsedClass(string $path): self
161161
{
162-
$this->usedClasses[] = $path;
162+
$this->usedClasses[] = trim($path, '\\');
163163

164164
return $this;
165165
}

0 commit comments

Comments
 (0)