Skip to content

Commit

Permalink
Merge pull request #35 from wol-soft/AddOptionToDefaultArraysToEmptyA…
Browse files Browse the repository at this point in the history
…rray

Added an option to default arrays to empty arrays (#32)
  • Loading branch information
wol-soft authored Feb 10, 2021
2 parents 014b1d8 + 5c57857 commit 9a25740
Show file tree
Hide file tree
Showing 44 changed files with 565 additions and 116 deletions.
18 changes: 1 addition & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ OpenAPIv3Provider | Fetches all objects defined in the `#/components/schemas sec

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.

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):
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):

```php
$generator = new Generator(
Expand All @@ -76,22 +76,6 @@ $generator

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.

## Configuring using the GeneratorConfiguration ##

The *GeneratorConfiguration* object offers the following methods to configure the generator in a fluid interface:

Method | Configuration | Default
--- | --- | ---
``` 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
``` 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
``` 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
``` 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
``` 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
``` 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
``` setOutputEnabled(bool $outputEnabled) ``` <br><br>Example:<br> ``` setOutputEnabled(false) ``` | Enable or disable output of the generation process to STDOUT | true
``` 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
``` addFilter(FilterInterface $filter) ``` <br><br>Example:<br> ``` addFilter(new CustomFilter()) ``` | Add a custom filter to the generator. Check out the docs for more details. | -

## Examples ##

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.
Expand Down
27 changes: 27 additions & 0 deletions docs/source/gettingStarted.rst
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,33 @@ If the implicit null option is enabled the interface of your classes may change.
(new GeneratorConfiguration())
->setImplicitNull(true);
Default arrays to empty arrays
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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.

.. code-block:: php
// accessing an array property which may contain null may require a fallback
foreach ($generatedObject->getItems() ?? [] as $item) {
// by enabling the default to empty array setting the value returned by getItems will always contain an array
// consequently no fallback is necessary
foreach ($generatedObject->getItems() as $item) {
.. hint::

This setting affects only optional properties.

.. code-block:: php
setDefaultArraysToEmptyArray(bool $defaultArraysToEmptyArray);
.. code-block:: php
(new GeneratorConfiguration())
->setDefaultArraysToEmptyArray(true);
Deny additional properties
^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
22 changes: 22 additions & 0 deletions src/Model/GeneratorConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class GeneratorConfiguration
/** @var bool */
protected $allowImplicitNull = false;
/** @var bool */
protected $defaultArraysToEmptyArray = false;
/** @var bool */
protected $denyAdditionalProperties = false;
/** @var bool */
protected $prettyPrint = false;
Expand Down Expand Up @@ -189,6 +191,26 @@ public function setNamespacePrefix(string $namespacePrefix): self
return $this;
}

/**
* @return bool
*/
public function isDefaultArraysToEmptyArrayEnabled(): bool
{
return $this->defaultArraysToEmptyArray;
}

/**
* @param bool $defaultArraysToEmptyArray
*
* @return GeneratorConfiguration
*/
public function setDefaultArraysToEmptyArray(bool $defaultArraysToEmptyArray): self
{
$this->defaultArraysToEmptyArray = $defaultArraysToEmptyArray;

return $this;
}

/**
* @return bool
*/
Expand Down
34 changes: 20 additions & 14 deletions src/Model/Property/Property.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
*/
class Property extends AbstractProperty
{
/** @var string */
protected $type = 'null';
/** @var string|null */
/** @var PropertyType */
protected $type;
/** @var PropertyType|null */
protected $outputType = null;
/** @var bool */
protected $isPropertyRequired = true;
Expand All @@ -47,13 +47,13 @@ class Property extends AbstractProperty
* Property constructor.
*
* @param string $name
* @param string $type
* @param PropertyType|null $type
* @param JsonSchema $jsonSchema
* @param string $description
*
* @throws SchemaException
*/
public function __construct(string $name, string $type, JsonSchema $jsonSchema, string $description = '')
public function __construct(string $name, ?PropertyType $type, JsonSchema $jsonSchema, string $description = '')
{
parent::__construct($name, $jsonSchema);

Expand All @@ -64,13 +64,17 @@ public function __construct(string $name, string $type, JsonSchema $jsonSchema,
/**
* @inheritdoc
*/
public function getType(bool $outputType = false): string
public function getType(bool $outputType = false): ?PropertyType
{
// If the output type differs from an input type also accept the output type
// (in this case the transforming filter is skipped)
// TODO: PHP 8 use union types to accept multiple input types
if (!$outputType && $this->outputType !== null && $this->outputType !== $this->type) {
return '';
if (!$outputType
&& $this->type
&& $this->outputType
&& $this->outputType->getName() !== $this->type->getName()
) {
return null;
}

return $outputType && $this->outputType !== null ? $this->outputType : $this->type;
Expand All @@ -79,7 +83,7 @@ public function getType(bool $outputType = false): string
/**
* @inheritdoc
*/
public function setType(string $type, ?string $outputType = null): PropertyInterface
public function setType(PropertyType $type = null, PropertyType $outputType = null): PropertyInterface
{
$this->type = $type;
$this->outputType = $outputType;
Expand All @@ -99,15 +103,17 @@ public function getTypeHint(bool $outputType = false): string
$input = [$this->type, $this->outputType];
}

$input = join('|', array_map(function (string $input) use ($outputType): string {
$input = join('|', array_filter(array_map(function (?PropertyType $input) use ($outputType): string {
$typeHint = $input ? $input->getName() : '';

foreach ($this->typeHintDecorators as $decorator) {
$input = $decorator->decorate($input, $outputType);
$typeHint = $decorator->decorate($typeHint, $outputType);
}

return $input;
}, $input));
return $typeHint;
}, $input)));

return $input ?? 'mixed';
return $input ?: 'mixed';
}

/**
Expand Down
12 changes: 6 additions & 6 deletions src/Model/Property/PropertyInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,18 @@ public function getAttribute(): string;
/**
* @param bool $outputType If set to true the output type will be returned (may differ from the base type)
*
* @return string
* @return PropertyType|null
*/
public function getType(bool $outputType = false): string;
public function getType(bool $outputType = false): ?PropertyType;

/**
* @param string $type
* @param string|null $outputType By default the output type will be equal to the base type but due to applied
* filters the output type may change
* @param PropertyType|null $type
* @param PropertyType|null $outputType By default the output type will be equal to the base type but due to applied
* filters the output type may change
*
* @return PropertyInterface
*/
public function setType(string $type, ?string $outputType = null): PropertyInterface;
public function setType(PropertyType $type, PropertyType $outputType = null): PropertyInterface;

/**
* @param bool $outputType If set to true the output type hint will be returned (may differ from the base type)
Expand Down
4 changes: 2 additions & 2 deletions src/Model/Property/PropertyProxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,15 @@ protected function getProperty(): PropertyInterface
/**
* @inheritdoc
*/
public function getType(bool $outputType = false): string
public function getType(bool $outputType = false): ?PropertyType
{
return $this->getProperty()->getType($outputType);
}

/**
* @inheritdoc
*/
public function setType(string $type, ?string $outputType = null): PropertyInterface
public function setType(PropertyType $type = null, PropertyType $outputType = null): PropertyInterface
{
return $this->getProperty()->setType($type, $outputType);
}
Expand Down
42 changes: 42 additions & 0 deletions src/Model/Property/PropertyType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace PHPModelGenerator\Model\Property;

class PropertyType
{
/** @var string */
private $name;
/** @var bool|null */
private $nullable;

/**
* PropertyType constructor.
*
* @param string $name The name of the type (eg. 'array', 'int', ...)
* @param bool|null $nullable Is the property nullable? if not provided the nullability will be determined
* automatically from the required flag/implicitNull setting etc.
*/
public function __construct(string $name, bool $nullable = null)
{
$this->name = $name;
$this->nullable = $nullable;
}

/**
* @return string
*/
public function getName(): string
{
return $this->name;
}

/**
* @return bool|null
*/
public function isNullable(): ?bool
{
return $this->nullable;
}
}
6 changes: 3 additions & 3 deletions src/Model/Validator/AbstractComposedPropertyValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@
abstract class AbstractComposedPropertyValidator extends PropertyTemplateValidator
{
/** @var string */
protected $composedProcessor;
protected $compositionProcessor;
/** @var CompositionPropertyDecorator[] */
protected $composedProperties;

/**
* @return string
*/
public function getComposedProcessor(): string
public function getCompositionProcessor(): string
{
return $this->composedProcessor;
return $this->compositionProcessor;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/Model/Validator/AdditionalPropertiesValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public function __construct(
);

parent::__construct(
new Property($propertyName ?? $schema->getClassName(), '', $propertiesStructure),
new Property($propertyName ?? $schema->getClassName(), null, $propertiesStructure),
DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'AdditionalProperties.phptpl',
[
'validationProperty' => $this->validationProperty,
Expand Down
2 changes: 1 addition & 1 deletion src/Model/Validator/ArrayTupleValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function __construct(
}

parent::__construct(
new Property($propertyName, '', $propertiesStructure),
new Property($propertyName, null, $propertiesStructure),
DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'ArrayTuple.phptpl',
[
'tupleProperties' => &$this->tupleProperties,
Expand Down
14 changes: 7 additions & 7 deletions src/Model/Validator/ComposedPropertyValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,24 @@ class ComposedPropertyValidator extends AbstractComposedPropertyValidator
*
* @param PropertyInterface $property
* @param CompositionPropertyDecorator[] $composedProperties
* @param string $composedProcessor
* @param string $compositionProcessor
* @param array $validatorVariables
*/
public function __construct(
PropertyInterface $property,
array $composedProperties,
string $composedProcessor,
string $compositionProcessor,
array $validatorVariables
) {
parent::__construct(
$property,
DIRECTORY_SEPARATOR . 'Validator' . DIRECTORY_SEPARATOR . 'ComposedItem.phptpl',
$validatorVariables,
$this->getExceptionByProcessor($composedProcessor),
$this->getExceptionByProcessor($compositionProcessor),
['&$succeededCompositionElements', '&$compositionErrorCollection']
);

$this->composedProcessor = $composedProcessor;
$this->compositionProcessor = $compositionProcessor;
$this->composedProperties = $composedProperties;
}

Expand Down Expand Up @@ -77,11 +77,11 @@ public function withoutNestedCompositionValidation(): self
/**
* Parse the composition type (allOf, anyOf, ...) from the given processor and get the corresponding exception class
*
* @param string $composedProcessor
* @param string $compositionProcessor
*
* @return string
*/
private function getExceptionByProcessor(string $composedProcessor): string
private function getExceptionByProcessor(string $compositionProcessor): string
{
return str_replace(
DIRECTORY_SEPARATOR,
Expand All @@ -90,7 +90,7 @@ private function getExceptionByProcessor(string $composedProcessor): string
) . '\\' . str_replace(
'Processor',
'',
substr($composedProcessor, strrpos($composedProcessor, '\\') + 1)
substr($compositionProcessor, strrpos($compositionProcessor, '\\') + 1)
) . 'Exception';
}
}
2 changes: 1 addition & 1 deletion src/Model/Validator/ConditionalPropertyValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public function __construct(
['&$ifException', '&$thenException', '&$elseException']
);

$this->composedProcessor = IfProcessor::class;
$this->compositionProcessor = IfProcessor::class;
$this->composedProperties = $composedProperties;
}

Expand Down
5 changes: 3 additions & 2 deletions src/Model/Validator/FilterValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,14 @@ private function validateFilterCompatibilityWithBaseType(FilterInterface $filter
{
if (!empty($filter->getAcceptedTypes()) &&
$property->getType() &&
!in_array($property->getType(), $this->mapDataTypes($filter->getAcceptedTypes()))
$property->getType()->getName() &&
!in_array($property->getType()->getName(), $this->mapDataTypes($filter->getAcceptedTypes()))
) {
throw new SchemaException(
sprintf(
'Filter %s is not compatible with property type %s for property %s in file %s',
$filter->getToken(),
$property->getType(),
$property->getType()->getName(),
$property->getName(),
$property->getJsonSchema()->getFile()
)
Expand Down
4 changes: 2 additions & 2 deletions src/Model/Validator/InstanceOfValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ public function __construct(PropertyInterface $property)
$property,
sprintf(
'is_object($value) && !($value instanceof \Exception) && !($value instanceof %s)',
$property->getType()
$property->getType()->getName()
),
InvalidInstanceOfException::class,
[$property->getType()]
[$property->getType()->getName()]
);
}
}
Loading

0 comments on commit 9a25740

Please sign in to comment.