Skip to content

Commit 42d110b

Browse files
committed
Added an option to default arrays to empty arrays (#32)
Refactored type storage of properties to an object for the possibility to explicitly set nullability of a type Removed generator configuration options from the readme
1 parent 5325e8d commit 42d110b

34 files changed

+325
-93
lines changed

README.md

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ OpenAPIv3Provider | Fetches all objects defined in the `#/components/schemas sec
6060

6161
The second parameter must point to an existing and empty directory (you may use the `generateModelDirectory` helper method to create your destination directory). This directory will contain the generated PHP classes after the generator is finished.
6262

63-
As an optional parameter you can set up a *GeneratorConfiguration* object to configure your Generator and/or use the method *generateModelDirectory* to generate your model directory (will generate the directory if it doesn't exist; if it exists, all contained files and folders will be removed for a clean generation process):
63+
As an optional parameter you can set up a *GeneratorConfiguration* object (check out the docs for all available options) to configure your Generator and/or use the method *generateModelDirectory* to generate your model directory (will generate the directory if it doesn't exist; if it exists, all contained files and folders will be removed for a clean generation process):
6464

6565
```php
6666
$generator = new Generator(
@@ -76,22 +76,6 @@ $generator
7676

7777
The generator will check the given source directory recursive and convert all found *.json files to models. All JSON-Schema files inside the source directory must provide a schema of an object.
7878

79-
## Configuring using the GeneratorConfiguration ##
80-
81-
The *GeneratorConfiguration* object offers the following methods to configure the generator in a fluid interface:
82-
83-
Method | Configuration | Default
84-
--- | --- | ---
85-
``` setNamespacePrefix(string $prefix) ``` <br><br>Example:<br> ``` setNamespacePrefix('MyApp\Model') ``` | Configures a namespace prefix for all generated classes. The namespaces will be extended with the directory structure of the source directory. | Empty string so no namespace prefix will be used
86-
``` setImmutable(bool $immutable) ``` <br><br>Example:<br> ``` setImmutable(false) ``` | If set to true the generated model classes will be delivered without setter methods for the object properties. | true
87-
``` setImplicitNull(bool $allowImplicitNull) ``` <br><br>Example:<br> ``` setImplicitNull(true) ``` | By setting the implicit null option to true all of your object properties which aren't required will implicitly accept null. | false
88-
``` setCollectErrors(bool $collectErrors) ``` <br><br>Example:<br> ``` setCollectErrors(false) ``` | By default the complete input is validated and in case of failing validations all error messages will be thrown in a single exception. If set to false the first failing validation will throw an exception. | true
89-
``` setPrettyPrint(bool $prettyPrint) ``` <br><br>Example:<br> ``` setPrettyPrint(true) ``` | If set to false, the generated model classes won't follow coding guidelines (but the generation is faster). If enabled the package [Symplify/EasyCodingStandard](https://github.com/Symplify/EasyCodingStandard) will be used to clean up the generated code (the package must be installed manually: `composer require --dev symplify/easy-coding-standard`). By default pretty printing is disabled. | false
90-
``` setSerialization(bool $serialization) ``` <br><br>Example:<br> ``` setSerialization(true) ``` | If set to true the serialization methods `toArray` and `toJSON` will be added to the public interface of the generated classes. | false
91-
``` setOutputEnabled(bool $outputEnabled) ``` <br><br>Example:<br> ``` setOutputEnabled(false) ``` | Enable or disable output of the generation process to STDOUT | true
92-
``` setErrorRegistryClass(string $exceptionClass) ``` <br><br>Example:<br> ``` setErrorRegistryClass(CustomException::class) ``` | Define a custom exception implementing the ErrorRegistryExceptionInterface to be used. The exception will be thrown if a validation fails and error collection is **enabled** | ErrorRegistryException::class
93-
``` addFilter(FilterInterface $filter) ``` <br><br>Example:<br> ``` addFilter(new CustomFilter()) ``` | Add a custom filter to the generator. Check out the docs for more details. | -
94-
9579
## Examples ##
9680

9781
The directory `./tests/manual` contains some easy examples which show the usage. After installing the dependencies of the library via `composer update` you can execute `php ./tests/manual/test.php` to generate the examples and play around with some JSON-Schema files to explore the library.

docs/source/gettingStarted.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,33 @@ If the implicit null option is enabled the interface of your classes may change.
172172
(new GeneratorConfiguration())
173173
->setImplicitNull(true);
174174
175+
Default arrays to empty arrays
176+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
177+
178+
By default optional properties which contain an `array <complexTypes/array.html>`__ will contain **null** if no array is provided (or null is provided with the `implicit null <#implicit-null>`_ setting enabled). For using the generated getter methods for those properties without a fallback the generator can be configured to default not provided arrays and null values to an empty array (by default this setting is disabled). By enabling this setting it's ensured that all optional arrays will always contain an array even if no default value or null is provided.
179+
180+
.. code-block:: php
181+
182+
// accessing an array property which may contain null may require a fallback
183+
foreach ($generatedObject->getItems() ?? [] as $item) {
184+
185+
// by enabling the default to empty array setting the value returned by getItems will always contain an array
186+
// consequently no fallback is necessary
187+
foreach ($generatedObject->getItems() as $item) {
188+
189+
.. hint::
190+
191+
This setting affects only optional properties.
192+
193+
.. code-block:: php
194+
195+
setDefaultArraysToEmptyArray(bool $defaultArraysToEmptyArray);
196+
197+
.. code-block:: php
198+
199+
(new GeneratorConfiguration())
200+
->setDefaultArraysToEmptyArray(true);
201+
175202
Collect errors vs. early return
176203
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
177204

src/Model/GeneratorConfiguration.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ class GeneratorConfiguration
2828
/** @var bool */
2929
protected $allowImplicitNull = false;
3030
/** @var bool */
31+
protected $defaultArraysToEmptyArray = false;
32+
/** @var bool */
3133
protected $prettyPrint = false;
3234
/** @var bool */
3335
protected $outputEnabled = true;
@@ -157,6 +159,26 @@ public function setNamespacePrefix(string $namespacePrefix): self
157159
return $this;
158160
}
159161

162+
/**
163+
* @return bool
164+
*/
165+
public function isDefaultArraysToEmptyArrayEnabled(): bool
166+
{
167+
return $this->defaultArraysToEmptyArray;
168+
}
169+
170+
/**
171+
* @param bool $defaultArraysToEmptyArray
172+
*
173+
* @return GeneratorConfiguration
174+
*/
175+
public function setDefaultArraysToEmptyArray(bool $defaultArraysToEmptyArray): self
176+
{
177+
$this->defaultArraysToEmptyArray = $defaultArraysToEmptyArray;
178+
179+
return $this;
180+
}
181+
160182
/**
161183
* @return bool
162184
*/

src/Model/Property/Property.php

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
*/
2020
class Property extends AbstractProperty
2121
{
22-
/** @var string */
23-
protected $type = 'null';
24-
/** @var string|null */
22+
/** @var PropertyType */
23+
protected $type;
24+
/** @var PropertyType|null */
2525
protected $outputType = null;
2626
/** @var bool */
2727
protected $isPropertyRequired = true;
@@ -47,13 +47,13 @@ class Property extends AbstractProperty
4747
* Property constructor.
4848
*
4949
* @param string $name
50-
* @param string $type
50+
* @param PropertyType|null $type
5151
* @param JsonSchema $jsonSchema
5252
* @param string $description
5353
*
5454
* @throws SchemaException
5555
*/
56-
public function __construct(string $name, string $type, JsonSchema $jsonSchema, string $description = '')
56+
public function __construct(string $name, ?PropertyType $type, JsonSchema $jsonSchema, string $description = '')
5757
{
5858
parent::__construct($name, $jsonSchema);
5959

@@ -64,13 +64,17 @@ public function __construct(string $name, string $type, JsonSchema $jsonSchema,
6464
/**
6565
* @inheritdoc
6666
*/
67-
public function getType(bool $outputType = false): string
67+
public function getType(bool $outputType = false): ?PropertyType
6868
{
6969
// If the output type differs from an input type also accept the output type
7070
// (in this case the transforming filter is skipped)
7171
// TODO: PHP 8 use union types to accept multiple input types
72-
if (!$outputType && $this->outputType !== null && $this->outputType !== $this->type) {
73-
return '';
72+
if (!$outputType
73+
&& $this->type
74+
&& $this->outputType
75+
&& $this->outputType->getName() !== $this->type->getName()
76+
) {
77+
return null;
7478
}
7579

7680
return $outputType && $this->outputType !== null ? $this->outputType : $this->type;
@@ -79,7 +83,7 @@ public function getType(bool $outputType = false): string
7983
/**
8084
* @inheritdoc
8185
*/
82-
public function setType(string $type, ?string $outputType = null): PropertyInterface
86+
public function setType(PropertyType $type = null, PropertyType $outputType = null): PropertyInterface
8387
{
8488
$this->type = $type;
8589
$this->outputType = $outputType;
@@ -99,15 +103,17 @@ public function getTypeHint(bool $outputType = false): string
99103
$input = [$this->type, $this->outputType];
100104
}
101105

102-
$input = join('|', array_map(function (string $input) use ($outputType): string {
106+
$input = join('|', array_filter(array_map(function (?PropertyType $input) use ($outputType): string {
107+
$typeHint = $input ? $input->getName() : '';
108+
103109
foreach ($this->typeHintDecorators as $decorator) {
104-
$input = $decorator->decorate($input, $outputType);
110+
$typeHint = $decorator->decorate($typeHint, $outputType);
105111
}
106112

107-
return $input;
108-
}, $input));
113+
return $typeHint;
114+
}, $input)));
109115

110-
return $input ?? 'mixed';
116+
return $input ?: 'mixed';
111117
}
112118

113119
/**

src/Model/Property/PropertyInterface.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,18 @@ public function getAttribute(): string;
3131
/**
3232
* @param bool $outputType If set to true the output type will be returned (may differ from the base type)
3333
*
34-
* @return string
34+
* @return PropertyType|null
3535
*/
36-
public function getType(bool $outputType = false): string;
36+
public function getType(bool $outputType = false): ?PropertyType;
3737

3838
/**
39-
* @param string $type
40-
* @param string|null $outputType By default the output type will be equal to the base type but due to applied
41-
* filters the output type may change
39+
* @param PropertyType|null $type
40+
* @param PropertyType|null $outputType By default the output type will be equal to the base type but due to applied
41+
* filters the output type may change
4242
*
4343
* @return PropertyInterface
4444
*/
45-
public function setType(string $type, ?string $outputType = null): PropertyInterface;
45+
public function setType(PropertyType $type, PropertyType $outputType = null): PropertyInterface;
4646

4747
/**
4848
* @param bool $outputType If set to true the output type hint will be returned (may differ from the base type)

src/Model/Property/PropertyProxy.php

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

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

src/Model/Property/PropertyType.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PHPModelGenerator\Model\Property;
6+
7+
class PropertyType
8+
{
9+
/** @var string */
10+
private $name;
11+
/** @var bool|null */
12+
private $nullable;
13+
14+
/**
15+
* PropertyType constructor.
16+
*
17+
* @param string $name The name of the type (eg. 'array', 'int', ...)
18+
* @param bool|null $nullable Is the property nullable? if not provided the nullability will be determined
19+
* automatically from the required flag/implicitNull setting etc.
20+
*/
21+
public function __construct(string $name, bool $nullable = null)
22+
{
23+
$this->name = $name;
24+
$this->nullable = $nullable;
25+
}
26+
27+
/**
28+
* @return string
29+
*/
30+
public function getName(): string
31+
{
32+
return $this->name;
33+
}
34+
35+
/**
36+
* @return bool|null
37+
*/
38+
public function isNullable(): ?bool
39+
{
40+
return $this->nullable;
41+
}
42+
}

src/Model/Validator/AdditionalPropertiesValidator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public function __construct(
6262
);
6363

6464
parent::__construct(
65-
new Property($propertyName ?? $schema->getClassName(), '', $propertiesStructure),
65+
new Property($propertyName ?? $schema->getClassName(), null, $propertiesStructure),
6666
DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'AdditionalProperties.phptpl',
6767
[
6868
'validationProperty' => $this->validationProperty,

src/Model/Validator/ArrayTupleValidator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public function __construct(
5959
}
6060

6161
parent::__construct(
62-
new Property($propertyName, '', $propertiesStructure),
62+
new Property($propertyName, null, $propertiesStructure),
6363
DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'ArrayTuple.phptpl',
6464
[
6565
'tupleProperties' => &$this->tupleProperties,

src/Model/Validator/FilterValidator.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,13 +138,14 @@ private function validateFilterCompatibilityWithBaseType(FilterInterface $filter
138138
{
139139
if (!empty($filter->getAcceptedTypes()) &&
140140
$property->getType() &&
141-
!in_array($property->getType(), $this->mapDataTypes($filter->getAcceptedTypes()))
141+
$property->getType()->getName() &&
142+
!in_array($property->getType()->getName(), $this->mapDataTypes($filter->getAcceptedTypes()))
142143
) {
143144
throw new SchemaException(
144145
sprintf(
145146
'Filter %s is not compatible with property type %s for property %s in file %s',
146147
$filter->getToken(),
147-
$property->getType(),
148+
$property->getType()->getName(),
148149
$property->getName(),
149150
$property->getJsonSchema()->getFile()
150151
)

src/Model/Validator/InstanceOfValidator.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ public function __construct(PropertyInterface $property)
2525
$property,
2626
sprintf(
2727
'is_object($value) && !($value instanceof \Exception) && !($value instanceof %s)',
28-
$property->getType()
28+
$property->getType()->getName()
2929
),
3030
InvalidInstanceOfException::class,
31-
[$property->getType()]
31+
[$property->getType()->getName()]
3232
);
3333
}
3434
}

src/Model/Validator/PassThroughTypeCheckValidator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public function __construct(
4040
$typeCheckValidator->getCheck()
4141
),
4242
InvalidTypeException::class,
43-
[[$passThroughType->getName(), $property->getType()]]
43+
[[$passThroughType->getName(), $property->getType()->getName()]]
4444
);
4545
}
4646

src/Model/Validator/PropertyNamesValidator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public function __construct(
4545
});
4646

4747
parent::__construct(
48-
new Property($schema->getClassName(), '', $propertiesNames),
48+
new Property($schema->getClassName(), null, $propertiesNames),
4949
DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'PropertyNames.phptpl',
5050
[
5151
'nameValidationProperty' => $nameValidationProperty,

src/Model/Validator/SchemaDependencyValidator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public function __construct(SchemaProcessor $schemaProcessor, PropertyInterface
3939
'generatorConfiguration' => $schemaProcessor->getGeneratorConfiguration(),
4040
'transferProperties' => $schema->getProperties(),
4141
// set up a helper property for handling of the nested object
42-
'nestedProperty' => (new Property("{$property->getName()}Dependency", '', $schema->getJsonSchema()))
42+
'nestedProperty' => (new Property("{$property->getName()}Dependency", null, $schema->getJsonSchema()))
4343
->addDecorator(new ObjectInstantiationDecorator(
4444
$schema->getClassName(),
4545
$schemaProcessor->getGeneratorConfiguration()

src/PropertyProcessor/ComposedValue/AbstractComposedValueProcessor.php

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use PHPModelGenerator\Model\Property\CompositionPropertyDecorator;
99
use PHPModelGenerator\Model\Property\Property;
1010
use PHPModelGenerator\Model\Property\PropertyInterface;
11+
use PHPModelGenerator\Model\Property\PropertyType;
1112
use PHPModelGenerator\Model\Schema;
1213
use PHPModelGenerator\Model\SchemaDefinition\ComposedJsonSchema;
1314
use PHPModelGenerator\Model\SchemaDefinition\JsonSchema;
@@ -164,16 +165,18 @@ protected function getCompositionProperties(PropertyInterface $property, JsonSch
164165
private function transferPropertyType(PropertyInterface $property, array $compositionProperties)
165166
{
166167
$compositionPropertyTypes = array_unique(
167-
array_map(
168-
function (CompositionPropertyDecorator $property): string {
169-
return $property->getType();
170-
},
171-
$compositionProperties
168+
array_filter(
169+
array_map(
170+
function (CompositionPropertyDecorator $property): string {
171+
return $property->getType() ? $property->getType()->getName() : '';
172+
},
173+
$compositionProperties
174+
)
172175
)
173176
);
174177

175178
if (count($compositionPropertyTypes) === 1 && !($this instanceof NotProcessor)) {
176-
$property->setType($compositionPropertyTypes[0]);
179+
$property->setType(new PropertyType($compositionPropertyTypes[0]));
177180
}
178181
}
179182

@@ -219,7 +222,12 @@ private function createMergedProperty(
219222

220223
$mergedPropertySchema = new Schema($this->schema->getClassPath(), $mergedClassName, $propertySchema);
221224

222-
$mergedProperty = new Property('MergedProperty', $mergedClassName, $mergedPropertySchema->getJsonSchema());
225+
$mergedProperty = new Property(
226+
'MergedProperty',
227+
new PropertyType($mergedClassName),
228+
$mergedPropertySchema->getJsonSchema()
229+
);
230+
223231
self::$generatedMergedProperties[$mergedClassName] = $mergedProperty;
224232

225233
$this->transferPropertiesToMergedSchema($mergedPropertySchema, $compositionProperties);

0 commit comments

Comments
 (0)