Skip to content

Commit 2d10675

Browse files
committed
Models implement the JsonSerializable interface
Added implicit/explixit null test cases
1 parent c461e32 commit 2d10675

File tree

14 files changed

+260
-33
lines changed

14 files changed

+260
-33
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ $ composer require --dev wol-soft/php-json-schema-model-generator
3939
$ composer require wol-soft/php-json-schema-model-generator-production
4040
```
4141

42-
To avoid adding all dependencies of the php-json-schema-model-generator to your production dependencies it's recommended to add the library as a dev-dependency and include the [wol-soft/php-json-schema-model-generator-production](https://github.com/wol-soft/php-json-schema-model-generator-production) library. The production library provides all classes to run the generated code. Generating the classes should either be a step done in the development environment or as a build step of your application (for example you could generate the models in a [composer post-update-cmd script](https://getcomposer.org/doc/articles/scripts.md#command-events), which is the recommended workflow).
42+
To avoid adding all dependencies of the php-json-schema-model-generator to your production dependencies it's recommended to add the library as a dev-dependency and include the [wol-soft/php-json-schema-model-generator-production](https://github.com/wol-soft/php-json-schema-model-generator-production) library. The production library provides all classes to run the generated code. Generating the classes should either be a step done in the development environment or as a build step of your application (for example you could generate the models in a [composer post-autoload-dump script](https://getcomposer.org/doc/articles/scripts.md#command-events), which is the recommended workflow).
4343

4444
## Basic usage ##
4545

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.10.0",
15+
"wol-soft/php-json-schema-model-generator-production": "^0.11.0",
1616
"wol-soft/php-micro-template": "^1.3.1",
1717

1818
"php": ">=7.2",

docs/source/gettingStarted.rst

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ The recommended way to install php-json-model-generator is through `Composer <ht
1111
composer require --dev wol-soft/php-json-schema-model-generator
1212
composer require wol-soft/php-json-schema-model-generator-production
1313
14-
To avoid adding all dependencies of the php-json-model-generator to your production dependencies it's recommended to add the library as a dev-dependency and include the php-json-model-generator-exception library. The exception library provides all classes to run the generated code. Generating the classes should either be a step done in the development environment or as a build step of your application (for example you could generate the models in a `composer post-update-cmd script<https://getcomposer.org/doc/articles/scripts.md#command-events>`__, which is the recommended workflow).
14+
To avoid adding all dependencies of the php-json-model-generator to your production dependencies it's recommended to add the library as a dev-dependency and include the php-json-model-generator-exception library. The exception library provides all classes to run the generated code. Generating the classes should either be a step done in the development environment or as a build step of your application (for example you could generate the models in a `composer post-autoload-dump script<https://getcomposer.org/doc/articles/scripts.md#command-events>`__, which is the recommended workflow).
1515

1616
Generating classes
1717
------------------
@@ -225,7 +225,7 @@ Serialization methods
225225
226226
setSerialization(bool $serialization);
227227
228-
If set to true the serialization methods `toArray` and `toJSON` will be added to the public interface of the generated classes. By default no serialization methods are added.
228+
If set to true the serialization methods `toArray`, `toJSON` and `jsonSerialize` will be added to the public interface of the generated classes. By default no serialization methods are added.
229229

230230
.. code-block:: php
231231
@@ -238,9 +238,12 @@ Generated interface:
238238
239239
public function toArray([int $depth = 512]): array;
240240
public function toJSON([int $options = 0 [, int $depth = 512]]): string;
241+
public function jsonSerialize(): array;
241242
242243
The generated class will implement the interface **PHPModelGenerator\\Interfaces\\SerializationInterface** implemented in the php-json-schema-model-generator-production repository. This interface can be used to write additional generic modules to handle the generated models. The $depth parameter defines the maximum amount of nested objects which are serialized. The $options parameter for the toJSON method provides access to the underlying option bitmask of `json_encode <https://www.php.net/manual/de/function.json-encode.php>`_.
243244

245+
Additionally the class will implement the PHP builtin interface **\JsonSerializable** which allows the direct usage of the generated classes in a custom json_encode.
246+
244247
Output generation process
245248
^^^^^^^^^^^^^^^^^^^^^^^^^
246249

docs/source/nonStandardExtensions/filter.rst

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,15 @@ If you write a custom transforming filter you must define the return type of you
9090

9191
The return type of the transforming filter will be used to define the type of the property inside the generated model (in the example one section above given above the method **getCreated** will return a DateTime object). Additionally the generated model also accepts the transformed type as input type. So **setCreated** will accept a string and a DateTime object. If an already transformed value is provided the filter which transforms the value will **not** be executed. Also all filters which are defined before the transformation will **not** be executed (eg. a trim filter before a dateTime filter will not be executed if a DateTime object is provided).
9292

93+
If you use a filter on a property which accepts multiple types (eg. explicit null ['string', 'null'] or ['string', 'integer']) the filter must accept each of the types defined on the property.
94+
9395
Builtin filter
9496
--------------
9597

9698
trim
9799
^^^^
98100

99-
The trim filter is only valid for string properties.
101+
The trim filter is only valid for string and null properties.
100102

101103
.. code-block:: json
102104
@@ -140,7 +142,7 @@ If the filter trim is used for a property which doesn't require a string value a
140142
notEmpty
141143
^^^^^^^^
142144

143-
The dateTime filter is only valid for array properties.
145+
The dateTime filter is only valid for array and null properties.
144146

145147
.. code-block:: json
146148
@@ -174,7 +176,7 @@ Let's have a look how the generated model behaves:
174176
dateTime
175177
^^^^^^^^
176178

177-
The dateTime filter is only valid for string properties.
179+
The dateTime filter is only valid for string and null properties.
178180

179181
.. code-block:: json
180182
@@ -292,7 +294,7 @@ The callable filter method must be a static method. Internally it will be called
292294
293295
.. hint::
294296

295-
If your filter accepts null values add 'null' to your *getAcceptedTypes* to make sure your filter is compatible with explicit null type
297+
If your filter accepts null values add 'null' to your *getAcceptedTypes* to make sure your filter is compatible with explicit null type.
296298

297299
.. hint::
298300

src/Templates/Model.phptpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ declare(strict_types = 1);
1717
{% if namespace %} * @package {{ namespace }} {% endif %}
1818
*/
1919
class {{ class }} implements \PHPModelGenerator\Interfaces\JSONModelInterface
20-
{% if generatorConfiguration.hasSerializationEnabled() %}, \PHPModelGenerator\Interfaces\SerializationInterface{% endif %}
20+
{% if generatorConfiguration.hasSerializationEnabled() %}, \PHPModelGenerator\Interfaces\SerializationInterface, \JsonSerializable{% endif %}
2121
{
2222
{% if generatorConfiguration.hasSerializationEnabled() %}use \PHPModelGenerator\Traits\SerializableTrait;{% endif %}
2323

tests/AbstractPHPModelGeneratorTest.php

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,11 @@ private function copyExternalJSON(): void
105105
/**
106106
* Generate a class from a given JSON schema file and return the FQCN
107107
*
108-
* @param string $file
108+
* @param string $file
109109
* @param GeneratorConfiguration|null $generatorConfiguration
110-
* @param bool $originalClassNames
111-
* @param string $schemaProviderClass
110+
* @param bool $originalClassNames
111+
* @param bool $implicitNull
112+
* @param string $schemaProviderClass
112113
*
113114
* @return string
114115
*
@@ -120,12 +121,14 @@ protected function generateClassFromFile(
120121
string $file,
121122
GeneratorConfiguration $generatorConfiguration = null,
122123
bool $originalClassNames = false,
124+
bool $implicitNull = true,
123125
string $schemaProviderClass = RecursiveDirectoryProvider::class
124126
): string {
125127
return $this->generateClass(
126128
file_get_contents(__DIR__ . '/Schema/' . $this->getStaticClassName() . '/' . $file),
127129
$generatorConfiguration,
128130
$originalClassNames,
131+
$implicitNull,
129132
$schemaProviderClass
130133
);
131134
}
@@ -170,14 +173,14 @@ function ($item) use ($escape) {
170173
/**
171174
* Generate a class from a given JSON schema string and return the FQCN
172175
*
173-
* @param string $jsonSchema
176+
* @param string $jsonSchema
174177
* @param GeneratorConfiguration|null $generatorConfiguration
175-
* @param bool $originalClassNames
176-
* @param string $schemaProviderClass
178+
* @param bool $originalClassNames
179+
* @param bool $implicitNull
180+
* @param string $schemaProviderClass
177181
*
178182
* @return string
179183
*
180-
* @throws Exception
181184
* @throws FileSystemException
182185
* @throws RenderException
183186
* @throws SchemaException
@@ -186,11 +189,12 @@ protected function generateClass(
186189
string $jsonSchema,
187190
GeneratorConfiguration $generatorConfiguration = null,
188191
bool $originalClassNames = false,
192+
bool $implicitNull = true,
189193
string $schemaProviderClass = RecursiveDirectoryProvider::class
190194
): string {
191195
$generatorConfiguration = ($generatorConfiguration ?? (new GeneratorConfiguration())->setCollectErrors(false))
192196
->setPrettyPrint(false)
193-
->setImplicitNull(true)
197+
->setImplicitNull($implicitNull)
194198
->setOutputEnabled(false);
195199

196200
if (!$originalClassNames) {
@@ -365,6 +369,14 @@ public function validationMethodDataProvider(): array {
365369
];
366370
}
367371

372+
public function implicitNullDataProvider(): array
373+
{
374+
return [
375+
'implicit null enabled' => [true],
376+
'implicit null disabled' => [false],
377+
];
378+
}
379+
368380
/**
369381
* Get the annotated type for an object property
370382
*

tests/Basic/BasicSchemaGenerationTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PHPModelGenerator\Tests\Basic;
44

5+
use JsonSerializable;
56
use PHPModelGenerator\Exception\FileSystemException;
67
use PHPModelGenerator\Exception\SchemaException;
78
use PHPModelGenerator\Interfaces\JSONModelInterface;
@@ -107,10 +108,12 @@ public function testSerializationFunctionsAreGeneratedWithEnabledSerialization()
107108
$object = new $className(['property' => 'Hello']);
108109

109110
$this->assertEquals(['property' => 'Hello'], $object->toArray());
111+
$this->assertEquals(['property' => 'Hello'], $object->jsonSerialize());
110112
$this->assertEquals('{"property":"Hello"}', $object->toJSON());
111113

112114
$this->assertTrue($object instanceof SerializationInterface);
113115
$this->assertTrue($object instanceof JSONModelInterface);
116+
$this->assertTrue($object instanceof JsonSerializable);
114117
}
115118

116119
public function testNestedSerializationFunctions(): void
@@ -131,6 +134,7 @@ public function testNestedSerializationFunctions(): void
131134
$object = new $className($input);
132135

133136
$this->assertEquals($input, $object->toArray());
137+
$this->assertEquals($input, $object->jsonSerialize());
134138
$this->assertEquals('{"name":"Hannes","address":{"street":"Test-Street","number":null}}', $object->toJSON());
135139

136140
$this->assertEquals(['name' => 'Hannes', 'address' => null], $object->toArray(1));

tests/Basic/ExplicitNullTest.php

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
namespace PHPModelGenerator\Tests\Basic;
4+
5+
use PHPModelGenerator\Exception\ValidationException;
6+
use PHPModelGenerator\Tests\AbstractPHPModelGeneratorTest;
7+
8+
/**
9+
* Class ExplicitNullTest
10+
*
11+
* @package PHPModelGenerator\Tests\Basic
12+
*/
13+
class ExplicitNullTest extends AbstractPHPModelGeneratorTest
14+
{
15+
public function testNullForOptionalValueWithoutImplicitNullThrowsAnException(): void
16+
{
17+
$this->expectException(ValidationException::class);
18+
$this->expectExceptionMessage('Invalid type for age. Requires int, got NULL');
19+
20+
$className = $this->generateClassFromFile('ImplicitNull.json', null, false, false);
21+
22+
new $className(['name' => 'Hannes', 'age' => null]);
23+
}
24+
25+
public function testNullForRequiredValueWithoutImplicitNullThrowsAnException(): void
26+
{
27+
$this->expectException(ValidationException::class);
28+
$this->expectExceptionMessage('Invalid type for name. Requires string, got NULL');
29+
30+
$className = $this->generateClassFromFile('ImplicitNull.json', null, false, false);
31+
32+
new $className(['name' => null]);
33+
}
34+
public function testNullForOptionalValueWithImplicitNullIsValid(): void
35+
{
36+
$className = $this->generateClassFromFile('ImplicitNull.json');
37+
38+
$object = new $className(['name' => 'Hannes', 'age' => null]);
39+
$this->assertNull($object->getAge());
40+
}
41+
42+
public function testNullForRequiredValueWithImplicitNullThrowsAnException(): void
43+
{
44+
$this->expectException(ValidationException::class);
45+
$this->expectExceptionMessage('Invalid type for name. Requires string, got NULL');
46+
47+
$className = $this->generateClassFromFile('ImplicitNull.json');
48+
49+
new $className(['name' => null]);
50+
}
51+
52+
/**
53+
* @dataProvider implicitNullDataProvider
54+
*
55+
* @param bool $implicitNull
56+
*/
57+
public function testNullForOptionalValueWithExplicitNullIsValid(bool $implicitNull): void
58+
{
59+
$className = $this->generateClassFromFile('ExplicitNull.json', null, false, $implicitNull);
60+
61+
$object = new $className(['name' => 'Hannes', 'age' => null]);
62+
$this->assertSame('Hannes', $object->getName());
63+
$this->assertNull($object->getAge());
64+
}
65+
66+
/**
67+
* @dataProvider implicitNullDataProvider
68+
*
69+
* @param bool $implicitNull
70+
*/
71+
public function testNullForRequiredValueWithExplicitNullIsValid(bool $implicitNull): void
72+
{
73+
$className = $this->generateClassFromFile('ExplicitNull.json', null, false, $implicitNull);
74+
75+
$object = new $className(['name' => null, 'age' => 31]);
76+
$this->assertNull($object->getName());
77+
$this->assertSame(31, $object->getAge());
78+
}
79+
}

tests/Basic/FilterTest.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,67 @@ public function testFilterChainWithTransformingFilter(): void
639639
$this->assertSame('2020-12-12T00:00:00+00:00', $object->getFilteredProperty()->format(DateTime::ATOM));
640640
}
641641

642+
/**
643+
* @dataProvider implicitNullDataProvider
644+
*
645+
* @param bool $implicitNull
646+
*/
647+
public function testFilterChainWithTransformingFilterOnMultiTypeProperty(bool $implicitNull): void
648+
{
649+
$className = $this->generateClassFromFile(
650+
'FilterChainMultiType.json',
651+
(new GeneratorConfiguration())
652+
->setImplicitNull($implicitNull)
653+
->addFilter(
654+
$this->getCustomFilter(
655+
[self::class, 'stripTimeFilter'],
656+
'stripTime',
657+
[DateTime::class]
658+
)
659+
),
660+
false
661+
);
662+
663+
$object = new $className(['filteredProperty' => '2020-12-12 12:12:12']);
664+
665+
$this->assertInstanceOf(DateTime::class, $object->getFilteredProperty());
666+
$this->assertSame('2020-12-12T00:00:00+00:00', $object->getFilteredProperty()->format(DateTime::ATOM));
667+
668+
$object->setFilteredProperty(null);
669+
$this->assertNull($object->getFilteredProperty());
670+
671+
$object->setFilteredProperty(new DateTime('2020-12-12 12:12:12'));
672+
$this->assertSame('2020-12-12T00:00:00+00:00', $object->getFilteredProperty()->format(DateTime::ATOM));
673+
}
674+
675+
public function testFilterWhichAppliesToMultiTypePropertyPartiallyThrowsAnException(): void
676+
{
677+
$this->expectException(SchemaException::class);
678+
$this->expectExceptionMessage(
679+
'Filter trim is not compatible with property type null for property filteredProperty'
680+
);
681+
682+
$this->generateClassFromFile(
683+
'FilterChainMultiType.json',
684+
(new GeneratorConfiguration())
685+
->addFilter(
686+
$this->getCustomFilter(
687+
[self::class, 'stripTimeFilter'],
688+
'trim',
689+
['string']
690+
)
691+
)
692+
->addFilter(
693+
$this->getCustomFilter(
694+
[self::class, 'stripTimeFilter'],
695+
'stripTime',
696+
[DateTime::class]
697+
)
698+
),
699+
false
700+
);
701+
}
702+
642703
public static function stripTimeFilter(?DateTime $value): ?DateTime
643704
{
644705
return $value !== null ? $value->setTime(0, 0) : null;

0 commit comments

Comments
 (0)