Skip to content

Commit 9a25740

Browse files
authored
Merge pull request #35 from wol-soft/AddOptionToDefaultArraysToEmptyArray
Added an option to default arrays to empty arrays (#32)
2 parents 014b1d8 + 5c57857 commit 9a25740

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+565
-116
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
Deny additional properties
176203
^^^^^^^^^^^^^^^^^^^^^^^^^^
177204

src/Model/GeneratorConfiguration.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ class GeneratorConfiguration
3030
/** @var bool */
3131
protected $allowImplicitNull = false;
3232
/** @var bool */
33+
protected $defaultArraysToEmptyArray = false;
34+
/** @var bool */
3335
protected $denyAdditionalProperties = false;
3436
/** @var bool */
3537
protected $prettyPrint = false;
@@ -189,6 +191,26 @@ public function setNamespacePrefix(string $namespacePrefix): self
189191
return $this;
190192
}
191193

194+
/**
195+
* @return bool
196+
*/
197+
public function isDefaultArraysToEmptyArrayEnabled(): bool
198+
{
199+
return $this->defaultArraysToEmptyArray;
200+
}
201+
202+
/**
203+
* @param bool $defaultArraysToEmptyArray
204+
*
205+
* @return GeneratorConfiguration
206+
*/
207+
public function setDefaultArraysToEmptyArray(bool $defaultArraysToEmptyArray): self
208+
{
209+
$this->defaultArraysToEmptyArray = $defaultArraysToEmptyArray;
210+
211+
return $this;
212+
}
213+
192214
/**
193215
* @return bool
194216
*/

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/AbstractComposedPropertyValidator.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@
1414
abstract class AbstractComposedPropertyValidator extends PropertyTemplateValidator
1515
{
1616
/** @var string */
17-
protected $composedProcessor;
17+
protected $compositionProcessor;
1818
/** @var CompositionPropertyDecorator[] */
1919
protected $composedProperties;
2020

2121
/**
2222
* @return string
2323
*/
24-
public function getComposedProcessor(): string
24+
public function getCompositionProcessor(): string
2525
{
26-
return $this->composedProcessor;
26+
return $this->compositionProcessor;
2727
}
2828

2929
/**

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/ComposedPropertyValidator.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,24 @@ class ComposedPropertyValidator extends AbstractComposedPropertyValidator
2121
*
2222
* @param PropertyInterface $property
2323
* @param CompositionPropertyDecorator[] $composedProperties
24-
* @param string $composedProcessor
24+
* @param string $compositionProcessor
2525
* @param array $validatorVariables
2626
*/
2727
public function __construct(
2828
PropertyInterface $property,
2929
array $composedProperties,
30-
string $composedProcessor,
30+
string $compositionProcessor,
3131
array $validatorVariables
3232
) {
3333
parent::__construct(
3434
$property,
3535
DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'ComposedItem.phptpl',
3636
$validatorVariables,
37-
$this->getExceptionByProcessor($composedProcessor),
37+
$this->getExceptionByProcessor($compositionProcessor),
3838
['&$succeededCompositionElements', '&$compositionErrorCollection']
3939
);
4040

41-
$this->composedProcessor = $composedProcessor;
41+
$this->compositionProcessor = $compositionProcessor;
4242
$this->composedProperties = $composedProperties;
4343
}
4444

@@ -77,11 +77,11 @@ public function withoutNestedCompositionValidation(): self
7777
/**
7878
* Parse the composition type (allOf, anyOf, ...) from the given processor and get the corresponding exception class
7979
*
80-
* @param string $composedProcessor
80+
* @param string $compositionProcessor
8181
*
8282
* @return string
8383
*/
84-
private function getExceptionByProcessor(string $composedProcessor): string
84+
private function getExceptionByProcessor(string $compositionProcessor): string
8585
{
8686
return str_replace(
8787
DIRECTORY_SEPARATOR,
@@ -90,7 +90,7 @@ private function getExceptionByProcessor(string $composedProcessor): string
9090
) . '\\' . str_replace(
9191
'Processor',
9292
'',
93-
substr($composedProcessor, strrpos($composedProcessor, '\\') + 1)
93+
substr($compositionProcessor, strrpos($compositionProcessor, '\\') + 1)
9494
) . 'Exception';
9595
}
9696
}

src/Model/Validator/ConditionalPropertyValidator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public function __construct(
3636
['&$ifException', '&$thenException', '&$elseException']
3737
);
3838

39-
$this->composedProcessor = IfProcessor::class;
39+
$this->compositionProcessor = IfProcessor::class;
4040
$this->composedProperties = $composedProperties;
4141
}
4242

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
}

0 commit comments

Comments
 (0)