GithubHelp home page GithubHelp logo

wol-soft / php-json-schema-model-generator Goto Github PK

View Code? Open in Web Editor NEW
58.0 3.0 11.0 1.22 MB

Creates (immutable) PHP model classes from JSON-Schema files including all validation rules as PHP code

License: MIT License

PHP 100.00%
json-schema jsonapi code-generator code-generation model immutable-objects immutable middleware schema-model php-model

php-json-schema-model-generator's Introduction

Latest Version Minimum PHP Version Maintainability Build Status Coverage Status MIT License Documentation Status

php-json-schema-model-generator

Generates PHP model classes from JSON-Schema files including validation and providing a fluent auto completion for the generated classes.

Table of Contents

Motivation

Simple example from a PHP application: you define and document an API with swagger annotations and JSON-Schema models. Now you want to use models in your controller actions instead of manually accessing the request data (eg. array stuff). Additionally your schema already defines the validation rules for the models. Why duplicate this rules into your manually written code? Instead you can set up a middleware which instantiates models generated with this library and feed the model with the request data. Now you have a validated model which you can use in your controller action. With full auto completion when working with nested objects. Yay!

Requirements

  • Requires at least PHP 8.0
  • Requires the PHP extensions ext-json and ext-mbstring

Installation

The recommended way to install php-json-schema-model-generator is through Composer:

$ composer require --dev wol-soft/php-json-schema-model-generator
$ composer require wol-soft/php-json-schema-model-generator-production

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 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 (which is the recommended workflow).

Basic usage

Check out the docs for more details.

The base object for generating models is the ModelGenerator. After you have created a Generator you can use the object to generate your model classes without any further configuration:

(new ModelGenerator())
    ->generateModels(new RecursiveDirectoryProvider(__DIR__ . '/schema'), __DIR__ . '/result');

The first parameter of the generateModels method must be a class implementing the SchemaProviderInterface. The provider fetches the JSON schema files and provides them for the generator. The following providers are available:

Provider Description
RecursiveDirectoryProvider Fetches all *.json files from the given source directory. Each file must contain a JSON Schema object definition on the top level
OpenAPIv3Provider Fetches all objects defined in the #/components/schemas section of an Open API v3 spec file

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

$generator = new ModelGenerator(
    (new GeneratorConfiguration())
        ->setNamespacePrefix('MyApp\Model')
        ->setImmutable(false)
);

$generator
    ->generateModelDirectory(__DIR__ . '/result');
    ->generateModels(new RecursiveDirectoryProvider(__DIR__ . '/schema'), __DIR__ . '/result');

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.

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.

Let's have a look into an easy example. We create a simple model for a person with a name and an optional age. Our resulting JSON-Schema:

{
  "$id": "Person",
  "type": "object",
  "properties": {
    "name": {
      "type": "string"
    },
    "age": {
      "type": "integer",
      "minimum": 0
    }
  },
  "required": [
    "name"
  ]
}

After generating a class with this JSON-Schema our class with the name Person will provide the following interface:

// the constructor takes an array with data which is validated and applied to the model
public function __construct(array $modelData);

// the method getRawModelDataInput always delivers the raw input which was provided on instantiation
public function getRawModelDataInput(): array;

// getters to fetch the validated properties. Age is nullable as it's not required
public function getName(): string;
public function getAge(): ?int;

// setters to change the values of the model after instantiation (only generated if immutability is disabled)
public function setName(string $name): Person;
public function setAge(?int $age): Person;

Now let's have a look at the behaviour of the generated model:

// Throws an exception as the required name isn't provided.
// Exception: 'Missing required value for name'
$person = new Person([]);

// Throws an exception as the name provides an invalid value.
// Exception: 'Invalid type for name. Requires string, got int'
$person = new Person(['name' => 12]);

// Throws an exception as the age contains an invalid value due to the minimum definition.
// Exception: 'Value for age must not be smaller than 0'
$person = new Person(['name' => 'Albert', 'age' => -1]);

// A valid example as the age isn't required
$person = new Person(['name' => 'Albert']);
$person->getName(); // returns 'Albert'
$person->getAge(); // returns NULL
$person->getRawModelDataInput(); // returns ['name' => 'Albert']

// If setters are generated the setters also perform validations.
// Exception: 'Value for age must not be smaller than 0'
$person->setAge(-10);

More complex exception messages eg. from a allOf composition may look like:

Invalid value for Animal declined by composition constraint.
  Requires to match 3 composition elements but matched 1 elements.
  - Composition element #1: Failed
    * Value for age must not be smaller than 0
  - Composition element #2: Valid
  - Composition element #3: Failed
    * Value for legs must not be smaller than 2
    * Value for legs must be a multiple of 2

How the heck does this work?

The class generation process basically splits up into three to four steps:

  • Scan the given source directory to find all *.json files which should be processed.
  • Loop over all schemas which should be generated. This is the main step of the class generation. Now each schema is parsed and a Schema model class which holds the properties for the generated model is populated. All validation rules defined in the JSON-Schema are translated into plain PHP code. After the model is finished a RenderJob is generated and added to the RenderQueue. If a JSON-Schema contains nested objects or references multiple RenderJobs may be added to the RenderQueue for a given schema file.
  • If post processors are defined for the generation process the post processors will be applied.
  • After all schema files have been parsed without an error the RenderQueue will be worked off. All previous added RenderJobs will be executed and the PHP classes will be saved to the filesystem at the given destination directory.

Tests

The library is tested via PHPUnit.

After installing the dependencies of the library via composer update you can execute the tests with ./vendor/bin/phpunit (Linux) or vendor\bin\phpunit.bat (Windows). The test names are optimized for the usage of the --testdox output. Most tests are atomic integration tests which will set up a JSON-Schema file and generate a class from the schema and test the behaviour of the generated class afterwards.

During the execution the tests will create a directory PHPModelGeneratorTest in tmp where JSON-Schema files and PHP classes will be written to.

If a test which creates a PHP class from a JSON-Schema fails the JSON-Schema and the generated class(es) will be dumped to the directory ./failed-classes

Docs

The docs for the library is generated with Sphinx.

To generate the documentation install Sphinx, enter the docs directory and execute make html (Linux) or make.bat html (Windows). The generated documentation will be available in the directory ./docs/build.

The documentation hosted at Read the Docs is updated on each push.

php-json-schema-model-generator's People

Contributors

dktapps avatar konafets avatar ramon-b avatar raphaelhanneken avatar wol-soft avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

php-json-schema-model-generator's Issues

const values lead to invalid code

Schema

{
  "$id": "Person",
  "type": "object",
  "properties": {
    "age": {
      "const": 42
    }
  }
}

Generated code

/** @var integer */
protected $age = NULL;

public function getAge(): integer
public function setAge(integer $age): self

Don't create a merged property if the composition is on object level

Problem

Currently for each allOf, anyOf or oneOf composition which contains nested objects a merged property is created to represent the complete composition.

The merged property is required if the composition is used on property level (compare docs).

If the composition is used on object level the merged property is identical to the main model generated. Consequently the merged property can be skipped.

Class example:

{
  "type": "object",
  "allOf": [
    {
        "$ref": "....."
    },
    {
        "$ref": "....."
    }
  ]
}

Expected result

An object like the example above should produce three classes

  • two to validate the references independently of each other
  • main model.

Current behaviour

Four classes are generated.

  • two to validate the references independently of each other
  • merged property
  • main model.

Validation of nested composition causes fatal error

Describe the bug
Validation of nested composition causes a fatal error:

Call to undefined method Generated\CreateSession_CreateSession615efe9ce38a3::validateComposition_0() in Generated/CreateSession_CreateSession615efe9ce38a3.php(86)

#0 Generated/CreateSession_CreateSession615efe9ce38a3.php(62): Generated\CreateSession_CreateSession615efe9ce38a3->executeBaseValidators(Array)
#1 Generated/CreateSession.php(555): Generated\CreateSession_CreateSession615efe9ce38a3->__construct(Array)
#2 Generated/CreateSession.php(569): Generated\CreateSession->Generated\{closure}(Array)
#3 Generated/CreateSession.php(634): Generated\CreateSession->Generated\{closure}(Array)
#4 Generated/CreateSession.php(113): Generated\CreateSession->validateComposition_0(Array)
#5 Generated/CreateSession.php(79): Generated\CreateSession->executeBaseValidators(Array)
#6 CreateSessionAction.php(56): Generated\CreateSession->__construct(Array)

Expected behavior
A successful (or depending on the input unsuccessful) validation, without an unexpected exception.

Schema

{
    "$id": "CreateSession",
    "type": "object",
    "allOf": [
        {
            "type": "object",
            "properties": {
                "timeout": {
                    "type": "integer",
                    "example": 120,
                    "minimum": 1,
                    "description": "The sessions lifetime in minutes",
                    "filter": [
                        {
                            "filter": "timeInterval",
                            "intervalUnit": "minutes"
                        }
                    ]
                },
                "expires": {
                    "type": "string",
                    "example": "2021-10-21T13:52:00+02:00",
                    "description": "An expiry date formatted according to ISO-8106",
                    "filter": [
                        {
                            "filter": "dateTime",
                            "outputFormat": "ATOM"
                        }
                    ]
                }
            }
        },
        {
            "oneOf": [
                {
                    "required": [
                        "timeout"
                    ]
                },
                {
                    "required": [
                        "expires"
                    ]
                }
            ]
        }
    ]
}

Version:
0.21.0

Inherited properties are partially nullable

Describe the bug
When using compositions the properties of the compositions are transferred to the main object. Independently of the implicitNull setting and the required state of the properties which are transferred from a composition the function signatures for getters and the DocBlocks for the properties are nullable.

With implicitNull setters of required properties become also nullable (although the validation performs correct checks and it will consequently fail if null is provided). Should be solved via the function signature.

Schema

{
  "$id": "Person",
  "allOf": [
    {
      "type": "object",
      "properties": {
        "age": {
          "type": "integer",
          "description": "The age of the person",
          "example": 42
        }
      }
    },
    {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "description": "The name of the person",
          "example": "Lawrence"
        }
      },
      "required": [
        "name"
      ]
    }
  ]
}

implicitNull disabled

generated interface

        /** @var int|null The age of the person */
        protected $age = NULL;
        /** @var string|null The name of the person */
        protected $name = NULL;

        public function getAge(): ?int
        public function setAge(int $age): self
        public function getName(): ?string
        public function setName(string $name): self

expected interface

        /** @var int|null The age of the person */
        protected $age = NULL;
        /** @var string The name of the person */
        protected $name;

        public function getAge(): ?int
        public function setAge(int $age): self
        public function getName(): string
        public function setName(string $name): self

implicitNull enabled

generated interface

        /** @var int|null The age of the person */
        protected $age = NULL;
        /** @var string|null The name of the person */
        protected $name = NULL;

        public function getAge(): ?int
        public function setAge(?int $age): self
        public function getName(): ?string
        public function setName(?string $name): self

expected interface

        /** @var int|null The age of the person */
        protected $age = NULL;
        /** @var string The name of the person */
        protected $name;

        public function getAge(): ?int
        public function setAge(?int $age): self
        public function getName(): string
        public function setName(string $name): self

Version:
all versions

Incorrect native parameter type on nullable setters in mutable models

Describe the bug
Nullable field setters don't correctly account for null when generating typehints.
image

Again, found by PHPStan.

Expected behavior
setDisableBlockTicking() should accept null.

Schema

{
	"$schema": "http://json-schema.org/draft-04/schema#",
	"type": "object",
	"additionalProperties": false,
	"properties": {
		"disable-block-ticking": {
			"oneOf": [
				{
					"type": "array",
					"items": {
						"type": "integer"
					}
				},
				{
					"type": "null"
				}
			],
			"description": "IDs of blocks to disallow ticking.",
			"default": null
		}
	},
	"required": [
		"chunk-ticking"
	]
}

Config: https://github.com/pmmp/DataModels/blob/3f398d966d5180d68e33c8f4054ab592094ea54c/generate-schemas.php

Version:
0.21.5.

Support number property key

Unfortunately some of the data we had to describe in our Json Schema are objects with number as keys like so:

"Markets": {
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "2201": {
      "type": "string"
    },
    "2202": {
      "type": "string"
    },
    "2203": {
      "type": "string"
    },
    "2204": {
      "type": "string"
    },
  }
}

Could it be possible to support this?

Add an option to default arrays to an empty array

Currently arrays which aren't required default to null. This requires additional null checks in PHP code where an empty array fits much better. Consequently an option to default not provided array properties to an empty array may be useful.

Creation of dynamic property for composed item validators on properties

Describe the bug
Composed item validators on properties use the _propertyValidationState property inside the validator which is used for base validators to determine whether a property change must trigger a re-evaluation of the base validator. As the _propertyValidationState property is only added to the model when a composed item base validator is found the property might not be present and the access will trigger a deprecation warning in PHP >= 8.2.

Expected behavior
Don't use the _propertyValidationState outside of base validators.

Schema

{
  "$schema": "https://json-schema.org/draft/2019-09/schema",
  "type": "object",
  "definitions": {
    "number": {
      "type": "integer",
      "minimum": 0,
      "maximum": 1000
    },
    "values": {
      "type": "array",
      "items": {
        "allOf": [
          { "$ref": "#/definitions/number" },
          { "minimum": 1 }
        ]
      },
      "minItems": 1
    },
    "pet": {
      "oneOf": [
        { "$ref": "#/definitions/pets/dog" },
        { "$ref": "#/definitions/pets/spider" }
      ]
    },
    "pets": {
      "dog": {
        "type": "object",
        "properties": {
          "age": {
            "$ref": "#/definitions/number"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "spider": {
        "type": "object",
        "properties": {
          "age": {
            "$ref": "#/definitions/number"
          },
          "weight": {
            "type": "number"
          }
        }
      }
    }
  },
  "properties": {
    "values": {
      "$ref": "#/definitions/values"
    },
    "pet": {
      "$ref": "#/definitions/pet"
    }
  },
  "additionalProperties": false
}

Version:
0.23.4

Stable version

Is this package in a stable state and if not is there any plans to get it to a stable point?

I know this isn't technically tagged as stable (v0.x) but unsure if it;s just how this package has been tagged as I can't see any mention of it being in development

Crash on `"type": ["object"]` on object properties

Describe the bug
The below schema causes the following error on code generation:

Fatal error: Uncaught Error: Call to a member function getClassPath() on null in C:\Users\dylan-work\Documents\projects\pocketmine-mp\deps\DataModels\vendor\wol-soft\php-json-schema-model-generator\src\Property
Processor\Property\ObjectProcessor.php:51
Stack trace:
#0 C:\Users\dylan-work\Documents\projects\pocketmine-mp\deps\DataModels\vendor\wol-soft\php-json-schema-model-generator\src\PropertyProcessor\Property\MultiTypeProcessor.php(165): PHPModelGenerator\PropertyProc
essor\Property\ObjectProcessor->process('aliases', Object(PHPModelGenerator\Model\SchemaDefinition\JsonSchema))
#1 C:\Users\dylan-work\Documents\projects\pocketmine-mp\deps\DataModels\vendor\wol-soft\php-json-schema-model-generator\src\PropertyProcessor\Property\MultiTypeProcessor.php(84): PHPModelGenerator\PropertyProce
ssor\Property\MultiTypeProcessor->processSubProperties('aliases', Object(PHPModelGenerator\Model\SchemaDefinition\JsonSchema), Object(PHPModelGenerator\Model\Property\Property))
#2 C:\Users\dylan-work\Documents\projects\pocketmine-mp\deps\DataModels\vendor\wol-soft\php-json-schema-model-generator\src\PropertyProcessor\PropertyFactory.php(72): PHPModelGenerator\PropertyProcessor\Propert
y\MultiTypeProcessor->process('aliases', Object(PHPModelGenerator\Model\SchemaDefinition\JsonSchema))
#3 C:\Users\dylan-work\Documents\projects\pocketmine-mp\deps\DataModels\vendor\wol-soft\php-json-schema-model-generator\src\PropertyProcessor\Property\BaseProcessor.php(279): PHPModelGenerator\PropertyProcessor
\PropertyFactory->create(Object(PHPModelGenerator\PropertyProcessor\PropertyMetaDataCollection), Object(PHPModelGenerator\SchemaProcessor\SchemaProcessor), Object(PHPModelGenerator\Model\Schema), 'aliases', Obj
ect(PHPModelGenerator\Model\SchemaDefinition\JsonSchema))
#4 C:\Users\dylan-work\Documents\projects\pocketmine-mp\deps\DataModels\vendor\wol-soft\php-json-schema-model-generator\src\PropertyProcessor\Property\BaseProcessor.php(75): PHPModelGenerator\PropertyProcessor\
Property\BaseProcessor->addPropertiesToSchema(Object(PHPModelGenerator\Model\SchemaDefinition\JsonSchema))
#5 C:\Users\dylan-work\Documents\projects\pocketmine-mp\deps\DataModels\vendor\wol-soft\php-json-schema-model-generator\src\PropertyProcessor\PropertyFactory.php(72): PHPModelGenerator\PropertyProcessor\Propert
y\BaseProcessor->process('Crash', Object(PHPModelGenerator\Model\SchemaDefinition\JsonSchema))
#6 C:\Users\dylan-work\Documents\projects\pocketmine-mp\deps\DataModels\vendor\wol-soft\php-json-schema-model-generator\src\SchemaProcessor\SchemaProcessor.php(191): PHPModelGenerator\PropertyProcessor\Property
Factory->create(Object(PHPModelGenerator\PropertyProcessor\PropertyMetaDataCollection), Object(PHPModelGenerator\SchemaProcessor\SchemaProcessor), Object(PHPModelGenerator\Model\Schema), 'Crash', Object(PHPMode
lGenerator\Model\SchemaDefinition\JsonSchema))
#7 C:\Users\dylan-work\Documents\projects\pocketmine-mp\deps\DataModels\vendor\wol-soft\php-json-schema-model-generator\src\SchemaProcessor\SchemaProcessor.php(137): PHPModelGenerator\SchemaProcessor\SchemaProc
essor->generateModel('', 'Crash', Object(PHPModelGenerator\Model\SchemaDefinition\JsonSchema), Object(PHPModelGenerator\Model\SchemaDefinition\SchemaDefinitionDictionary), true)
#8 C:\Users\dylan-work\Documents\projects\pocketmine-mp\deps\DataModels\vendor\wol-soft\php-json-schema-model-generator\src\SchemaProcessor\SchemaProcessor.php(104): PHPModelGenerator\SchemaProcessor\SchemaProc
essor->processSchema(Object(PHPModelGenerator\Model\SchemaDefinition\JsonSchema), '', 'Crash', Object(PHPModelGenerator\Model\SchemaDefinition\SchemaDefinitionDictionary), true)
#9 C:\Users\dylan-work\Documents\projects\pocketmine-mp\deps\DataModels\vendor\wol-soft\php-json-schema-model-generator\src\ModelGenerator.php(125): PHPModelGenerator\SchemaProcessor\SchemaProcessor->process(Ob
ject(PHPModelGenerator\Model\SchemaDefinition\JsonSchema))
#10 C:\Users\dylan-work\Documents\projects\pocketmine-mp\deps\DataModels\generate-schemas.php(74): PHPModelGenerator\ModelGenerator->generateModels(Object(PHPModelGenerator\SchemaProvider\RecursiveDirectoryProv
ider), 'C:\\Users\\dylan-...')
#11 {main}
  thrown in C:\Users\dylan-work\Documents\projects\pocketmine-mp\deps\DataModels\vendor\wol-soft\php-json-schema-model-generator\src\PropertyProcessor\Property\ObjectProcessor.php on line 51

Expected behavior
The model should be generated exactly as if I'd used type: object.

Schema

{
	"$schema": "http://json-schema.org/draft-04/schema#",
	"type": "object",
	"properties": {
		"aliases": {
			"type": ["object"]
		}
	}
}

Generator config: https://github.com/pmmp/DataModels/blob/d91723d5cccb8879532430ba4aff80949c440034/generate-schemas.php

Version:
0.21.5

Additional context
I was trying to use type arrays to create explicitly nullable object fields.

Add an option to set additionalProperties to false by default

By default a JSON schema enforces the user to define "additionalProperties": false for each object which should accept only the defined properties. To generate strict models an option should be available to default additionalProperties to false. If the additionalProperties keyword is defined for an object the option is ignored.

Serialization Bug: Including Null Properties in JSON Output instead of Excluding Them

Describe the bug:
The serialization of models using the toArray and toJSON functions is including null properties instead of excluding them from the JSON string. These additional fields will cause an error when trying to instantiate the model with the saved JSON string.

Expected behavior:
I understand that using the object's properties is not the best approach. It is necessary to use only the information contained in the RawModelDataInput field, where the object's state is stored.

Schema:

"juros": {
	"$id": "#juros",
	"type": "object",
	"required": [
		"id"
	],
	"properties": {
		"id": {
			"type": ["integer", "string"],
			"description": "ID ou Sigla do tipo de indicador utilizado."
		},
		"dataInicioJuros": {
			"type": "string",
			"format": "datetime"
			"description": "Data de inรญcio de fluรชncia dos juros."
		},
		"dataFinalJuros": {
			"type": "string",
			"format": "datetime"
			"description": "Data de tรฉrmino de fluรชncia dos juros."
		},
		"jurosProRata": {
			"type": "boolean",
			"description": "Indica que o cรกlculo de juros considerarรก fraรงรตes de perรญodos"
		},
		"jurosCompostos": {
			"type": "boolean",
			"description": "Indica que o cรกlculo de juros serรก da modalidade de juros compostos"
		}
	}
},

Version:
0.23.0

Additional context:
Considering the schema, I would like to create a model with the JSON string {"id": "JUROS_0"} and serialize it to the same output. However, the class will generate the following output JSON string: {"id": "JUROS_0", "dataInicioJuros": null, "dataFimJuros": null, "jurosProRata": null, "jurosCompostos": null}.

Add validateOption to FilterInterface

To validate the provided options of a filter during the build step the function

validate(array $options): void

should be added to the FilterInterface (or a new interface to reduce overhead for filters not using the validation). This method can be used to validate the options array and throw an exception, if for example a required option is missing.

Error when generating classes with FedEx OpenAPI schema

Describe the bug
When I use this library to try to generate PHP classes from FedEx's OpenAPI schema for their new RESTful API, I get this error:

PHP Fatal error: Uncaught PHPModelGenerator\Exception\SchemaException: No nested schema for composed property RequestePackageLineItemDimensions in file /[...]/utils/../schemas/rate.json

(Note: that should be RequestedPackageLineItemDimensions, but the first 'd' is missing in the reference and the definition, so it shouldn't matter.)

This is mentioned in issue 57, but the solution there was to alter the schema. Since the goal is to be as hands-off as possible, I'd much prefer to simply download the schemas and run this function on them. I don't want to have to modify every schema we need (ship, rate, address validation, and more) each time we have to update to a new API release. I've installed a java-based tool that converts schemas into PHP, and it didn't encounter this problem when using this schema, so I know it's possible. I also validated the schema with two online validators, and both indicated that the JSON is valid for OpenAPI.

Expected behavior

Given that this is a valid OpenAPI 3 schema from a major company, I expect it to be processed into PHP classes properly.

Schema

https://developer.fedex.com/api/en-us/catalog/rate/v1/docs.html > click "download JSON schema". I'd link to it directly here, but the page uses some JS to actually serve the JSON file.

Here's the function I'm using to try to generate the classes:

function generate() {
	$generator = new ModelGenerator(
		(new GeneratorConfiguration())
		->setNamespacePrefix('models\fedex')
		->setSerialization(true)
		->setCollectErrors(false)
		->setImmutable(false)
	);
	$schemaPath = dirname(__FILE__) . '/../schemas/rate.json';
	$resultDirectory = dirname(__FILE__) . '/../models/fedex';
	$generator
		->generateModelDirectory($resultDirectory)
		->generateModels(new OpenAPIv3Provider($schemaPath), $resultDirectory);
}

Version:

0.23.3

Additional context

I don't think I have anything to add. I'm experienced with PHP, but very new to OpenAPI and class generation. If more information is required, please ask.

Path to EasyCodingStandard binary is wrong

Describe the bug
The model generator provides a pretty-print option which uses EasyCodingStandard (ECS) in the background.

Expected behavior
The generated models are pretty-printed after generation.

Actual behavior
sh: 1: /home/username/project/vendor/wol-soft/php-json-schema-model-generator/src/../vendor/bin/ecs: not found

Schema
Not related to a schema.

Config

    $config = new GeneratorConfiguration();
    $config
        ->setNamespacePrefix('ACME\Common\Models')
        ->setImmutable(false)
        ->setPrettyPrint(true)
        ->setSerialization(true);

    $generator = new ModelGenerator($config);
    $generator
        ->generateModelDirectory(__DIR__ . '/src/ACME/Common/Models')
        ->generateModels(
            new RecursiveDirectoryProvider(__DIR__ . '/resources'),
            __DIR__ . '/src/ACME/Common/Models'
        );

Version:
0.20.0

Additional context

  • In src/ModelGenerator.php ECS is called like this: shell_exec(__DIR__ . "/../vendor/bin/ecs check $destination --config " . __DIR__ . "/cs.yml --fix $out").
  • However, resolving this directory will end up in src/vendor/bin/ecs.
  • It should be called like this: shell_exec(__DIR__ . "/../../../../vendor/bin/ecs check $destination --config " . __DIR__ . "/cs.yml --fix $out");

uuid string type support

Hi,
Do you know if it's possible to support additional format type as UUID ?

I got this error when I try to use it :

PHP Fatal error: Uncaught PHPModelGenerator\Exception\SchemaException: Unsupported format uuid for property UUID in file openApi.json in vendor/wol-soft/php-json-schema-model-generator/src/PropertyProcessor/Property/StringProcessor.php:136

My schema is
"properties": { "UUID": { "type": "string", "minLength": 1, "format": "uuid", }
best regards,
Alban

Constructor with explicit argument

I'd like to be able to detect missing/badly typed properties with static analysis.
So the __construct(array $rawModelDataInput = []) is not ideal in that regards since dev will see their error at runtime.

Would it be possible, maybe via a config option, to generate constructor of the form __construct(string $propA, int $propB)?

With php 8.1 we would also be able to use named arguments to avoid unreadable constructors.

Integer property with default 0 is incorrectly nullable

Describe the bug
A property with a default value is not typically nullable, but if its default is 0, it's marked as nullable anyway.

Expected behavior
Integer properties with 0 as the default value shouldn't be nullable.

Schema

				"global-limit": {
					"type": "integer",
					"minimum": 0,
					"description": "Global soft memory limit (MB). When reached, low-memory triggers will fire to try and free memory.",
					"default": 1
				},

Version:
Which version of the library do you use?
dev-master as of about 1h ago.

Additional context
My bet is that there's some weak comparison == somewhere that's responsible for this bug.

Property validation of mutable objects which use composition after instantiation

Problem

Objects which inherit properties from compositions validate correctly during object instantiation. If the object is generated with setters the setters don't perform validations according to the composition rules.

Expected behaviour

  • Simple validations on properties inherited from compositions (eg. string length checks) are performed when calling a setter
  • If the model, as a result of the changed internal state, violates the composition rule (eg. an allOf composition is violated as the string property now doesn't match the length constraint and thus at least one of the composition elements fails) an exception will be thrown.

Simplify object notation for single-filter usage

Currently the library forces the user to define a list when using filter options even if only a single filter is applied:

{
    "type": "object",
    "properties": {
        "created": {
            "type": "string",
            "filter": [
                {
                    "filter": "dateTime",
                    "denyEmptyValue": true
                }
            ]
        }
    }
}

Instead of the list allow the direct usage of an object to define a single filter with options:

{
    "type": "object",
    "properties": {
        "created": {
            "type": "string",
            "filter": {
                "filter": "dateTime",
                "denyEmptyValue": true
            }
        }
    }
}

The above currently crashes with a message unsupported filter denyEmptyValues which is not helpful.

No nested schema for composed property

Describe the bug
With this schema I get this error I don't understand: No nested schema for composed property Address in file Adress.json found

Expected behavior
I don't see anything wrong with this schema so I'd expect it to generate the appropriate class.

Schema

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "oneOf": [
        { "type": "string" },
        {
            "type": "object",
            "properties": {
                "address": { "type": "string" },
                "specialDelivery": { "type": "string" },
                "zipCode": { "type": "string" },
                "city": { "type": "string" }
            },
            "additionalProperties": false
        }
    ]
}

No custom GeneratorConfiguration.

Version:
0.22.0

Optional property return type with a default value is nullable

Only applies if the option implicitNull is not enabled. If no default value is provided the property must be nullable (value is null if no value is provided). If implicitNull is enabled the return type must also be nullable as optional properties with this option accept null.

{
    "type": "object",
    "properties": {
        "test": {
            "type": "boolean",
            "default": false
    }
}

Expected interface:

public function getTest(): bool;

Generated interface:

public function getTest(): ?bool;

Incorrectly escaped backslashes in regex in generated code

Describe the bug

    },
    "main": {
      "description": "The fully-qualified name of the main class that extends PluginBase",
      "pattern": "([A-Za-z_]\\w+\\\\)*([A-Za-z_]\\w+)",
      "type": "string"
    },

This schema causes the following code to be generated:

                    if (is_string($value) && !preg_match('/([A-Za-z_]\w+\\)*([A-Za-z_]\w+)/', $value)) {
                        $this->_errorRegistry->addError(new \PHPModelGenerator\Exception\String\PatternException($value ?? null, ...array (
  0 => 'main',
  1 => '([A-Za-z_]\\w+\\\\)*([A-Za-z_]\\w+)',
)));
                    }
                

                return $value;
            }

This pattern is meant to allow backslashes, hence the 4 \\\\ (to match the literal backslash character).

You can see that the backslash given to preg_match() is incorrectly escaped: there are only 2 backslashes in the resulting string, which is interpreted by PHP as a single backslash even in a single-quoted string.

Expected behavior
The backslash pattern above should be correctly escaped.

Schema
plugin-schema.json.txt

Version:
0.21.0

Make code output stable across multiple runs

Is your feature request related to a problem? Please describe.
Currently, this library relies on uniqid() quite a lot to ensure uniqueness in generated code. This is quite annoying when the generated code is in a git repository, because schema code changes every time my build script runs even if the schema itself was untouched.

Describe the solution you'd like
For class names, I've had some success with a custom class name generator that suffixes with md5(json_encode(propertySchema)) instead of uniqid(). However, there's some additional places that I can't touch without modifying the library itself:

  • ArrayItemValidator generates variable names using uniqid(). I'm not actually very clear why this is needed at all, since validators are anyway split up into separate methods per property.
  • AbstractPropertyProcessor uses uniqid() to generate class names for rendering dependencies. Again, I'm not very clear why this is needed - isn't ClassNameGenerator enough to take care of this?

Additional context
image

Filter for a property inside a Composition inside an array is not executed

Describe the bug
Independent of a provided value or a default value the value for created is not transformed resulting in a fatal error when calling the generated getCreated:

Uncaught TypeError: ...::getCreated(): Return value must be of type ?DateTime, string returned

The issue also affects non-transforming filters like trim.

Expected behavior

The transformed filter should be executed and consequently a DateTime object should be returned.

Schema

{
  "type": "object",
  "properties": {
    "songs": {
      "type": "array",
      "items":{
        "allOf": [
          {
            "type": "object",
            "properties": {
              "title": {
                "type": "string"
              }
            },
            "required": [
              "title"
            ]
          },
          {
            "type": "object",
            "properties": {
              "created": {
                "type": "string",
                "filter": "dateTime",
                "default": "now"
              }
            }
          }
        ]
      }
    }
  }
}

Version:
0.23.2

Multiple levels of compositions lead to lost IDs

Describe the bug
If multiple levels of compositions are used (each composition with an ID) the generated merged properties will be named generic instead of utilizing the provided ID as described in https://php-json-schema-model-generator.readthedocs.io/en/latest/combinedSchemas/mergedProperty.html

Expected behavior
The classes for the merged properties shall utilize the $id field of the most outer composition level to provide a predictable name over multiple generation processes.

Schema
Schema.zip

Version:
0.19.0

Missing properties when multiple properties use the same ref

Describe the bug
When using a schema that has two keys that both reference the same definition in their values, only the first gets generated.

Expected behavior
Both properties exist in the generated PHP.
In the generated Test.php a search for the word personA returns 40 results, but a search for the word personB only returns 1.

Schema

{
  "$schema": "http://json-schema.org/draft-07/schema",
  "$id": "Test",

  "definitions": {
    "person": {
      "type": "object",
      "properties": {
        "id": { "type": "string" }
      },
      "required": ["id"],
      "additionalProperties": false
    }
  },

  "type": "object",
  "properties": {
    "personA": { "$ref": "#/definitions/person" },
    "personB": { "$ref": "#/definitions/person" }
  },
  "required": ["personA", "personB"],
  "additionalProperties": false
}

With GeneratorConfiguration:

$generator = new ModelGenerator((new GeneratorConfiguration())
    ->setNamespacePrefix('Demo')
    ->setSerialization(true)
    ->setImmutable(false)
);

Version:
0.19.0

Additional context
This is not my real schema, this is a minimal reproduction. The environment I did this in only has the following files:

  • composer.json
  • json-schema/test.json
  • codegen.php

...PropertyProxy::getProperty(): Return value must be of type ...PropertyInterface, null returned in

image

Note that the function return type was defined as non-null and, as you can see in the debugger, the specific key being looked up points to a null.

My suspicion (without digging deep into the code) is that the recursive declaration (line 530 of the schema) might be causing the null?

Describe the bug
Attempt to return null on a non-null return during generation.

Expected behavior
Generation should have succeeded.

Schema
https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json

Version:
0.23 (generator) / 0.18 (interfaces)


Additionally, the patternProperties regex check in BaseProcessor line 171 is triggering a failure for pattern ^/(html|css|js)/[^/\n\r]+$, which to me looks fine, but maybe I'm missing the purpose of the check.

Access to undeclared property `_propertyValidationState` on simple templates

Describe the bug
I initially found this and various other bugs using PHPStan:
image

The vast majority of these issues are the same thing, but only this one looks actively harmful as far as I can tell (most of the others are just unused use and dead code which could be prevented from generating with the right efforts).

Bear in mind that this is just PHPStan level 1, and higher levels might find more problems.

Expected behavior
The generated code shouldn't be trying to access an undeclared property. This happens in both mutable and immutable models.

Schema

{
	"$schema": "http://json-schema.org/draft-04/schema#",
	"type": "object",
	"additionalProperties": false,
	"properties": {
		"mode": {
			"enum": [
				"blacklist",
				"whitelist",
				"allow",
				"disallow"
			],
			"default": "disallow",
			"description": "The listed plugins will either be allowed or disallowed based on this setting"
		},
		"plugins": {
			"anyOf": [
				{
					"type": "array",
					"items": {
						"type": "string"
					}
				},
				{
					"type": "null"
				}
			],
			"default": [],
			"description": "List of plugins to allow or disallow"
		}
	}
}

My generator config can be seen here: https://github.com/pmmp/DataModels/blob/3f398d966d5180d68e33c8f4054ab592094ea54c/generate-schemas.php

Version:
0.21.5

Access additional properties

Problem

When a schema applies validation rules to additional properties the only way to access the values of the additional properties is to use the method getRawModelDataInput.

  • This method delivers all properties of the model, so there is no way to access only the additional properties
  • This method delivers the raw values which were applied to the model. This may be a problem in various situations:
    • The additional properties contain objects and processing the additional properties results in object instantiations. There is no way to access the objects
    • The additional properties are modified by filters. The filtered values can't be accessed
  • additional properties don't occur in serialized models

Possible solution

Access values on the model

If a schema defines constraints for additionalProperties add a method getAdditionalProperties to the model. This method returns an array indexed by the provided property key containing the processed values. If the properties provide type information (eg. an object structure which was generated) the return type of the method should be type hinted with the corresponding type to provide auto completion when using the method.

Additionally a method getAdditionalProperty will be added which accepts a property key. If an additional property with the requested key exists the property will be returned, null otherwise. The method is also type hinted.

Access the models via serialization

If a model with additional properties is serialized (requires serialization enabled) the returned array/JSON will contain all additional properties. If transforming filters are applied to the additional properties the corresponding serializer will be called for each additional property.

Modify values on the model

A method setAdditionalProperty will be added (if immutability is disabled) which accepts a property key and a property value (type hinted as well). If an additional property with the provided key already exists the existing property will be overwritten. When setting a new value the validation rules of the additionalProperties definition are applied to the provided value as well as propertyNames rules are applied to the provided property key.

Additionally a method removeAdditionalProperty will be added which accepts a property key and removes the additional property with the requested key if the property exists.

If new additional properties are added or existing properties are removed make sure the minProperties and the maxProperties validation rules aren't violated.

Inconsistency of generated code when using different methods to declare nullable structures

Describe the bug

The following diff:

diff --git a/schema/PluginListYml.json b/schema/PluginListYml.json
index 4500662..7d5fdea 100644
--- a/schema/PluginListYml.json
+++ b/schema/PluginListYml.json
@@ -14,17 +14,13 @@
                        "description": "The listed plugins will either be allowed or disallowed based on this setting"
                },
                "plugins": {
-                       "anyOf": [
-                               {
-                                       "type": "array",
-                                       "items": {
-                                               "type": "string"
-                                       }
-                               },
-                               {
-                                       "type": "null"
-                               }
+                       "type": [
+                               "array",
+                               "null"
                        ],
+                       "items": {
+                               "type": "string"
+                       },
                        "default": [],
                        "description": "List of plugins to allow or disallow"
                }

produces the following change:
pmmp/DataModels@46eab45

You can see that there's much less generated code for some reason, but what bothers me more is that the ?array native types disappeared from both getters and setters.

Expected behavior
The code should be the same in both cases, since they are semantically equivalent to the best of my understanding.

However, more importantly, the native types should not be missing after the change: the calculated types are exactly the same.

Schema
https://github.com/pmmp/DataModels/blob/46eab454f5e3c2498f3a84e8065ed9702f27a58f/schema/PluginListYml.json

https://github.com/pmmp/DataModels/blob/46eab454f5e3c2498f3a84e8065ed9702f27a58f/generate-schemas.php

Version:
0.12.7.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.