diff --git a/README.md b/README.md index ebe9980b..288a0554 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ The directory `./tests/manual` contains some easy examples which show the usage. 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: ```json { - "$id": "Person", + "title": "Person", "type": "object", "properties": { "name": { @@ -112,22 +112,22 @@ 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; +public function setName(string $name): static; +public function setAge(?int $age): static; ``` Now let's have a look at the behaviour of the generated model: ```php // Throws an exception as the required name isn't provided. -// Exception: 'Missing required value for name' +// 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' +// Exception: "Invalid type for 'name': requires 'string', got 'integer'" $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' +// 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 @@ -137,20 +137,20 @@ $person->getAge(); // returns NULL $person->meta()->rawInput(); // returns ['name' => 'Albert'] // If setters are generated the setters also perform validations. -// Exception: 'Value for age must not be smaller than 0' +// Exception: "Value for 'age' must not be smaller than 0" $person->setAge(-10); ``` More complex exception messages eg. from a [allOf](https://json-schema.org/understanding-json-schema/reference/combining.html#allof) composition may look like: ``` -Invalid value for Animal declined by composition constraint. - Requires to match 3 composition elements but matched 1 element. +Invalid value for 'Animal' declined by composition constraint + Requires to match all composition elements but matched 1 element - Composition element #1: Failed - * Value for age must not be smaller than 0 + * 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 + * Value for 'legs' must not be smaller than 2 + * Value for 'legs' must be a multiple of 2 ``` ## How the heck does this work? ## diff --git a/docs/source/combinedSchemas/allOf.rst b/docs/source/combinedSchemas/allOf.rst index 5e819efd..bcc08d95 100644 --- a/docs/source/combinedSchemas/allOf.rst +++ b/docs/source/combinedSchemas/allOf.rst @@ -6,7 +6,7 @@ The `allOf` keyword can be used to combine multiple subschemas. The provided val .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -31,7 +31,7 @@ Generated interface: .. code-block:: php public function setExample(float $example): static; - public function getExample(): float; + public function getExample(): ?float; Possible exception (eg. if a string is provided): diff --git a/docs/source/combinedSchemas/anyOf.rst b/docs/source/combinedSchemas/anyOf.rst index b117d973..3979ed67 100644 --- a/docs/source/combinedSchemas/anyOf.rst +++ b/docs/source/combinedSchemas/anyOf.rst @@ -6,7 +6,7 @@ The `anyOf` keyword can be used to combine multiple subschemas. The provided val .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -31,7 +31,7 @@ Generated interface: .. code-block:: php public function setExample(float $example): static; - public function getExample(): float; + public function getExample(): ?float; Possible exception (if a string is provided): @@ -98,7 +98,7 @@ The thrown exception will be a *PHPModelGenerator\\Exception\\ComposedValue\\Any construction time by which branches the provided data satisfies. When multiple matching branches define a default for the same property, those defaults must agree; the generator throws a ``SchemaException`` at generation time if they differ. Branch defaults are **not** included in - ``getRawModelDataInput()``. + ``meta()->rawInput()``. See `Default values <../generic/default.html#branch-defaults-in-compositions>`__ for the full explanation. diff --git a/docs/source/combinedSchemas/crossTypedComposition.rst b/docs/source/combinedSchemas/crossTypedComposition.rst index e44df299..8d5a0317 100644 --- a/docs/source/combinedSchemas/crossTypedComposition.rst +++ b/docs/source/combinedSchemas/crossTypedComposition.rst @@ -15,7 +15,7 @@ property type to the union of all branch types. .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "anyOf": [ { @@ -74,7 +74,7 @@ For example, with a two-branch ``oneOf`` where ``age`` is required in both branc .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "oneOf": [ { @@ -147,4 +147,4 @@ widen the property type. The same intersection behaviour also applies to properties defined via ``patternProperties`` when their names match declared properties. See - `Pattern properties <../object/patternProperties.html>`__ for details. \ No newline at end of file + `Pattern properties <../complexTypes/object.html#pattern-properties>`__ for details. \ No newline at end of file diff --git a/docs/source/combinedSchemas/if.rst b/docs/source/combinedSchemas/if.rst index e63d576b..f7b9cd99 100644 --- a/docs/source/combinedSchemas/if.rst +++ b/docs/source/combinedSchemas/if.rst @@ -6,7 +6,7 @@ The keywords `if`, `then` and `else` can be used to conditionally combine multip .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -77,7 +77,7 @@ An object level composition will result in an object which contains all properti .. code-block:: json { - "$id": "customer", + "title": "Customer", "type": "object", "properties": { "country": { @@ -117,7 +117,7 @@ Generated interface: public function setCountry(string $country): static; public function getCountry(): ?string; - public function setPostalCode(string $country): static; + public function setPostalCode(string $postalCode): static; public function getPostalCode(): ?string; When the ``then`` and ``else`` branches define the same property with **different types**, the generator produces a union type hint — consistent with the behaviour of ``anyOf``/``oneOf``: @@ -125,7 +125,7 @@ When the ``then`` and ``else`` branches define the same property with **differen .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "if": { "properties": { @@ -203,7 +203,7 @@ When only a ``then`` block is present (no ``else``), the branch may not apply at generator applies the branch default only when the relevant branch is active — the ``then`` default applies when the ``if`` condition is satisfied, and the ``else`` default applies when it is not. A user-supplied value always overrides the branch default. Branch defaults are **not** - included in ``getRawModelDataInput()``. + included in ``meta()->rawInput()``. When a ``then`` or ``else`` branch default conflicts with a root ``properties`` default or a ``patternProperties`` default for the same property, the generator throws a ``SchemaException`` diff --git a/docs/source/combinedSchemas/mergedProperty.rst b/docs/source/combinedSchemas/mergedProperty.rst index 43652884..3525bf6c 100644 --- a/docs/source/combinedSchemas/mergedProperty.rst +++ b/docs/source/combinedSchemas/mergedProperty.rst @@ -10,11 +10,11 @@ For example we combine two objects with `allOf` for an object property: .. code-block:: json { - "$id": "company", + "title": "Company", "type": "object", "properties": { "ceo": { - "$id": "CEO", + "title": "CEO", "allOf": [ { "type": "object", @@ -45,7 +45,7 @@ As the subschemas don't contain IDs they will be named with uniqIds (compare the * Company_Ceo5e4a82e39fe37.php * Company_Merged_CEO.php -If the allOf doesn't contain an $id field the merged class will also contain an uniqId. So if you want to use the class with a reproducible class name you must set the $id field. +If the property holding the allOf doesn't carry a ``title`` (or ``$id``) the merged class will also contain a uniqId. So if you want to use the class with a reproducible class name you must set ``title`` (or ``$id``) on the property. The classes Company_Ceo5e4a82e39edc3 and Company_Ceo5e4a82e39fe37 are only used for internal validation and can't be accessed via the generated interface of Company. Generated interface: @@ -53,7 +53,7 @@ Generated interface: .. code-block:: php # class Company - public function setCeo(Company_Merged_CEO $example): static; + public function setCeo(Company_Merged_CEO $ceo): static; public function getCeo(): ?Company_Merged_CEO; # class Company_Merged_CEO @@ -67,7 +67,7 @@ If your composition is defined on object level the object will gain access to al .. code-block:: json { - "$id": "CEO", + "title": "CEO", "type": "object", "allOf": [ { diff --git a/docs/source/combinedSchemas/not.rst b/docs/source/combinedSchemas/not.rst index 0c66593d..d70100c0 100644 --- a/docs/source/combinedSchemas/not.rst +++ b/docs/source/combinedSchemas/not.rst @@ -6,7 +6,7 @@ Used to validate a provided schema or property doesn't match the given schema. I .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { diff --git a/docs/source/combinedSchemas/oneOf.rst b/docs/source/combinedSchemas/oneOf.rst index 1a5c320f..be91c4d1 100644 --- a/docs/source/combinedSchemas/oneOf.rst +++ b/docs/source/combinedSchemas/oneOf.rst @@ -6,7 +6,7 @@ The `oneOf` keyword can be used to combine multiple subschemas. The provided val .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -31,7 +31,7 @@ Generated interface: .. code-block:: php public function setExample(float $example): static; - public function getExample(): float; + public function getExample(): ?float; Possible exception (if a string is provided): @@ -106,7 +106,7 @@ The thrown exception will be a *PHPModelGenerator\\Exception\\ComposedValue\\One Properties in object-level ``oneOf`` branches may carry a ``"default"`` value. The generator applies the branch default only when that branch is the active one — determined at construction time by which branch the provided data satisfies. A user-supplied value always overrides the - branch default. Branch defaults are **not** included in ``getRawModelDataInput()``. + branch default. Branch defaults are **not** included in ``meta()->rawInput()``. When two ``oneOf`` branches define a default for the same property, or when a branch default conflicts with a root ``properties`` default or a ``patternProperties`` default, the generator diff --git a/docs/source/complexTypes/array.rst b/docs/source/complexTypes/array.rst index 6fc6b909..cb9e419d 100644 --- a/docs/source/complexTypes/array.rst +++ b/docs/source/complexTypes/array.rst @@ -11,7 +11,7 @@ A simple array without further restrictions can be defined using `array`. .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -53,7 +53,7 @@ The items of a list can be restricted with a nested schema. All items of the sch .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -92,14 +92,14 @@ A more complex array may contain a nested object. .. code-block:: json { - "$id": "example", - "type": "family", + "title": "Family", + "type": "object", "properties": { "members": { "type": "array", "items": { "type": "object", - "$id": "member", + "title": "Member", "properties": { "name": { "type": "string" @@ -165,7 +165,7 @@ Items .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -223,7 +223,7 @@ Using the keyword `additionalItems` the array can be limited to not contain any .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -235,7 +235,7 @@ Using the keyword `additionalItems` the array can be limited to not contain any }, { "type": "integer" - }, + } ], "additionalItems": { "type": "object", @@ -299,7 +299,7 @@ The contains check uses a schema which must match at least one of the items prov .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -334,6 +334,69 @@ any element satisfies the constraint). ``contains: false`` — no element could ever satisfy the constraint; any array value raises a ``ContainsException`` at runtime. The generator also emits a warning at generation time. +.. note:: + + ``minContains`` and ``maxContains`` require `Draft 2019-09 or later <../gettingStarted.html#json-schema-draft-version>`__. + Under ``Draft_07`` (the default when no ``$schema`` is declared, or when ``$schema`` names a + draft ``AutoDetectionDraft`` doesn't recognise) both keywords are silently ignored, and only a + plain ``contains`` check (at least one match) is generated. + +Number of matches (minContains / maxContains) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The keywords ``minContains`` and ``maxContains`` refine ``contains`` to require a minimum and/or +maximum number of items matching the ``contains`` schema, instead of just "at least one". + +.. code-block:: json + + { + "title": "Example", + "type": "object", + "properties": { + "example": { + "type": "array", + "contains": { + "type": "string" + }, + "minContains": 2, + "maxContains": 3 + } + } + } + +Possible exceptions: + +* Array 'example' must not contain less than 2 items matching the contains constraint, 1 matching items provided +* Array 'example' must not contain more than 3 items matching the contains constraint, 4 matching items provided + +The thrown exception will be a *PHPModelGenerator\\Exception\\Arrays\\MinContainsException* or a +*PHPModelGenerator\\Exception\\Arrays\\MaxContainsException* which provides the following methods +to get further error details: + +.. code-block:: php + + // for a MinContainsException: get the minimum amount of required matching items + public function getMinContains(): int + // for a MaxContainsException: get the maximum amount of allowed matching items + public function getMaxContains(): int + // get the amount of items which actually matched the contains constraint + public function getMatches(): int + // get the name of the property which failed + public function getPropertyName(): string + // get the value provided to the property + public function getProvidedValue() + // get the JSON pointer to the schema keyword that rejected the value + public function getJsonPointer(): JsonPointer + +.. hint:: + + A ``minContains`` of ``0`` makes the plain ``contains`` check optional: an array with zero + matching items is valid as long as ``maxContains`` (if set) isn't violated either. + +Setting ``minContains`` to a negative number, or ``maxContains`` to a number smaller than ``1``, or +a ``minContains`` greater than ``maxContains``, is rejected with a ``SchemaException`` at +generation time. + Size validation --------------- @@ -342,7 +405,7 @@ To limit the size of an array use the `minItems` and `maxItems` keywords. .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -381,7 +444,7 @@ The items of an array can be forced to be unique with the `uniqueItems` keyword. .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { diff --git a/docs/source/complexTypes/enum.rst b/docs/source/complexTypes/enum.rst index 0223c951..1d8ccf68 100644 --- a/docs/source/complexTypes/enum.rst +++ b/docs/source/complexTypes/enum.rst @@ -10,7 +10,7 @@ Enums can be used to define a set of constant values a property must accept. .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -32,6 +32,13 @@ Possible exceptions: * Invalid type for 'example': requires 'string', got '__TYPE__' * Value for 'example' must be one of ["ABC","DEF"], got "GHI" +.. note:: + + If ``enum`` lists more than 8 allowed values, the exception message truncates the list to the + first 5 values followed by ``, ... (and N more)`` instead of printing every value (e.g. + ``Value for 'example' must be one of ["A","B","C","D","E", ... (and 3 more)], got "X"``). + ``getAllowedValues()`` on the exception always returns the complete, untruncated list. + Untyped Enum ------------ @@ -40,7 +47,7 @@ An enum can also be defined without a specific type. .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { diff --git a/docs/source/complexTypes/multiType.rst b/docs/source/complexTypes/multiType.rst index 33171b51..90341f0b 100644 --- a/docs/source/complexTypes/multiType.rst +++ b/docs/source/complexTypes/multiType.rst @@ -6,7 +6,7 @@ By providing an array with types for a property multiple types can be allowed. .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -48,7 +48,7 @@ For each type given in the allowed types array additional validators may be adde .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { diff --git a/docs/source/complexTypes/object.rst b/docs/source/complexTypes/object.rst index 83b391aa..ac74f452 100644 --- a/docs/source/complexTypes/object.rst +++ b/docs/source/complexTypes/object.rst @@ -6,14 +6,14 @@ Properties which contain an object will result in an additional PHP class. The P .. code-block:: json { - "$id": "person", + "title": "Person", "type": "object", "properties": { "name": { "type": "string" }, "car": { - "$id": "car", + "title": "Car", "type": "object", "properties": { "model": { @@ -35,13 +35,13 @@ Generated interface: public function setName(string $name): static; // As the property is not required it may be initialized with null. Consequently the return value is nullable public function getName(): ?string; - public function setCar(Car $name): static; + public function setCar(Car $car): static; public function getCar(): ?Car; // class Car - public function setModel(string $name): static; + public function setModel(string $model): static; public function getModel(): ?string; - public function setPs(int $name): static; + public function setPs(int $ps): static; public function getPs(): ?int; Possible exceptions: @@ -66,7 +66,7 @@ The thrown exception will be a *PHPModelGenerator\\Exception\\Generic\\InvalidTy // get the JSON pointer to the schema keyword that rejected the value public function getJsonPointer(): JsonPointer -The nested object will be validated in the nested class Car which may throw additional exceptions if invalid data is provided. If the internal validation of a nested object fails a *PHPModelGenerator\\Exception\\Generic\\NestedObjectException* will be thrown which provides the following methods to get further error details: +The nested object will be validated in the nested class Car which may throw additional exceptions if invalid data is provided. If the internal validation of a nested object fails a *PHPModelGenerator\\Exception\\Object\\NestedObjectException* will be thrown which provides the following methods to get further error details: .. code-block:: php @@ -98,7 +98,8 @@ Naming of classes ^^^^^^^^^^^^^^^^^ If the given main object in a JSON-Schema file contains a `title`, the title will be used as class name. -Otherwise, if an `$id` is present, the basename of the $id and as a last fallback the name of the file will be used. +Otherwise, if an `$id` is present, the basename of the ``$id`` is used. Otherwise, if an ``$anchor`` is +present (Draft 2019-09 and later), its value is used. As a last fallback the name of the file will be used. Naming of nested classes ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -161,9 +162,9 @@ Generated interface: .. code-block:: php - public function setUnderscorePropertyMinus(string $name): static; + public function setUnderscorePropertyMinus(string $underscorePropertyMinus): static; public function getUnderscorePropertyMinus(): ?string; - public function setCapsAndSpace100(string $name): static; + public function setCapsAndSpace100(string $capsAndSpace100): static; public function getCapsAndSpace100(): ?string; If the name normalization results in an empty attribute name (eg. '__ -- __') an exception will be thrown. @@ -177,7 +178,7 @@ Using the keyword `required` a list of properties may be defined which must be p .. code-block:: json { - "$id": "person", + "title": "Person", "type": "object", "properties": { "name": { @@ -238,7 +239,7 @@ With the keywords `minProperties` and `maxProperties` the number of allowed prop .. code-block:: json { - "$id": "person", + "title": "Person", "type": "object", "properties": { "name": { @@ -251,8 +252,8 @@ With the keywords `minProperties` and `maxProperties` the number of allowed prop Possible exceptions: -* Provided object for 'person' must not contain less than 2 properties -* Provided object for 'person' must not contain more than 3 properties +* Provided object for 'Person' must not contain less than 2 properties +* Provided object for 'Person' must not contain more than 3 properties The thrown exception will be a *PHPModelGenerator\\Exception\\Object\\MaxPropertiesException* or a *PHPModelGenerator\\Exception\\Object\\MinPropertiesException* which provides the following methods to get further error details: @@ -281,7 +282,7 @@ Using the keyword `additionalProperties` the object can be limited to not contai .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -303,7 +304,7 @@ Using the keyword `additionalProperties` the object can be limited to not contai Possible exceptions: -* Provided JSON for 'example' contains not allowed additional properties ['additional1', 'additional2'] +* Provided JSON for 'Example' contains not allowed additional properties ['additional1', 'additional2'] The thrown exception will be a *PHPModelGenerator\\Exception\\Object\\AdditionalPropertiesException* which provides the following methods to get further error details: @@ -322,7 +323,7 @@ If invalid additional properties are provided a detailed exception will be throw .. code-block:: none - Provided JSON for 'example' contains invalid additional properties + Provided JSON for 'Example' contains invalid additional properties - invalid additional property 'additional1' * Invalid type for 'name': requires 'string', got 'integer' - invalid additional property 'additional2' @@ -355,7 +356,7 @@ If objects are defined recursive the recursion will be resolved into a single cl { "definitions": { "person": { - "$id": "person", + "title": "Person", "type": "object", "properties": { "name": { @@ -370,7 +371,7 @@ If objects are defined recursive the recursion will be resolved into a single cl } } }, - "$id": "family", + "title": "Family", "type": "object", "properties": { "members": { @@ -393,7 +394,7 @@ Generated interface: // class Person, arrays type hinted in docblocks with Family_Person[] public function setName(string $name): static; public function getName(): ?string; - public function setChildren(array $name): static; + public function setChildren(array $children): static; public function getChildren(): ?array; Property Names @@ -404,7 +405,7 @@ With the keyword `propertyNames` rules can be defined which must be fulfilled by .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "propertyNames": { "pattern": "^test[0-9]+$", @@ -418,7 +419,7 @@ Exceptions contain detailed information about the violations: .. code-block:: none - Provided JSON for 'example' contains properties with invalid names + Provided JSON for 'Example' contains properties with invalid names - invalid property 'test12345a' * Value for 'property name' does not match pattern '^test[0-9]+$' * Value for 'property name' must not be longer than 8 @@ -524,7 +525,7 @@ Schema dependencies allow you to define a schema which must be fulfilled if a gi { "type": "object", - "$id": "CreditCardOwner" + "title": "CreditCardOwner", "properties": { "credit_card": { "type": "integer" @@ -603,7 +604,7 @@ Using the keyword `patternProperties` further restrictions for properties matchi .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { diff --git a/docs/source/development/testInfrastructure.rst b/docs/source/development/testInfrastructure.rst index fe51b037..a99da736 100644 --- a/docs/source/development/testInfrastructure.rst +++ b/docs/source/development/testInfrastructure.rst @@ -8,7 +8,7 @@ Test output directories During a test run two directories are used automatically: -* **Temp directory** — generated JSON Schema files and PHP classes are written to ``{sys_get_temp_dir()}/PHPModelGeneratorTest/Models/``. Each test method gets its own uniquely-named subdirectory so parallel runs do not collide. +* **Temp directory** — generated JSON Schema files and PHP classes are written to ``{sys_get_temp_dir()}/PHPModelGeneratorTest_/Models/``. The ```` suffix is generated once per test *process* (in ``tests/bootstrap.php``), so concurrent test runs/processes don't collide; within a single process, every test method shares that same directory, which ``setUp()`` wipes and regenerates before each test so each test still starts from a clean, empty directory. * ``./failed-classes/`` — when a test fails, all JSON Schema files and the generated PHP classes produced by that test are copied here for post-mortem inspection. The directory is cleaned automatically on bootstrap. Running the tests diff --git a/docs/source/examples/scenarioBasedTesting.rst b/docs/source/examples/scenarioBasedTesting.rst index 461e0c5d..18e684b3 100644 --- a/docs/source/examples/scenarioBasedTesting.rst +++ b/docs/source/examples/scenarioBasedTesting.rst @@ -50,7 +50,7 @@ To define our **scenario-schema** we look at our entities and add them to our sc .. code-block:: json { - "$id": "Scenario", + "title": "Scenario", "type": "object", "additionalProperties": false, "description": "This schema describes the structure of a test scenario which can be set up via the ScenarioBuilder", @@ -61,7 +61,7 @@ To define our **scenario-schema** we look at our entities and add them to our sc "users": { "type": "array", "items": { - "$id": "user", + "title": "User", "type": "object", "properties": { "username": { @@ -83,7 +83,7 @@ To define our **scenario-schema** we look at our entities and add them to our sc "pets": { "type": "array", "items": { - "$id": "pet", + "title": "Pet", "type": "object", "properties": { "name": { @@ -102,12 +102,12 @@ To define our **scenario-schema** we look at our entities and add them to our sc "orders": { "type": "array", "items": { - "$id": "order", + "title": "Order", "type": "object", "properties": { "id": { "type": "integer" - } + }, "user": { "type": "string" }, @@ -313,7 +313,7 @@ To start using our **ScenarioBuilder** we now write our first **scenario**. As a "user": "Bob", "pet": "doggie" } - ], + ] } .. hint:: diff --git a/docs/source/generator/builtin/additionalPropertiesAccessorPostProcessor.rst b/docs/source/generator/builtin/additionalPropertiesAccessorPostProcessor.rst index 44717f2a..ad6af51f 100644 --- a/docs/source/generator/builtin/additionalPropertiesAccessorPostProcessor.rst +++ b/docs/source/generator/builtin/additionalPropertiesAccessorPostProcessor.rst @@ -18,7 +18,7 @@ Added methods .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -34,8 +34,8 @@ Generated interface with the **AdditionalPropertiesAccessorPostProcessor**: .. code-block:: php - public function setExample(float $example): static; - public function getExample(): float; + public function setExample(string $example): static; + public function getExample(): ?string; public function meta(): Meta; public function additionalProperties(): AdditionalPropertiesAccessor; diff --git a/docs/source/generator/builtin/builderClassPostProcessor.rst b/docs/source/generator/builtin/builderClassPostProcessor.rst index 2a82af49..27e3b37c 100644 --- a/docs/source/generator/builtin/builderClassPostProcessor.rst +++ b/docs/source/generator/builtin/builderClassPostProcessor.rst @@ -20,7 +20,7 @@ Let's have a look at a simple object and the generated classes: .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -37,13 +37,13 @@ In this case the model generator will generate two classes: **Example** and **Ex .. code-block:: php // class ExampleBuilder - public function setExample(string $members): static; + public function setExample(string $example): static; public function getExample(): ?string; public function validate(): Example; // class Example - public function setExample(string $members): static; + public function setExample(string $example): static; public function getExample(): string; Note, that the *getExample* method of the **ExampleBuilder** can return null. @@ -76,11 +76,11 @@ As a third option, you can simply pass an array with the values for the nested o .. code-block:: json { - "$id": "location", + "title": "Location", "type": "object", "properties": { "coordinates": { - "$id": "coordinates", + "title": "Coordinates", "type": "object", "properties": { "latitude": { @@ -88,7 +88,7 @@ As a third option, you can simply pass an array with the values for the nested o }, "longitude": { "type": "string" - }, + } }, "required": [ "latitude", diff --git a/docs/source/generator/builtin/enumPostProcessor.rst b/docs/source/generator/builtin/enumPostProcessor.rst index 9b7de8f1..4fb66226 100644 --- a/docs/source/generator/builtin/enumPostProcessor.rst +++ b/docs/source/generator/builtin/enumPostProcessor.rst @@ -22,7 +22,7 @@ Let's have a look at the most simple case of a string-only enum: .. code-block:: json { - "$id": "offer", + "title": "Offer", "type": "object", "properties": { "state": { @@ -59,7 +59,7 @@ Each enum which is not a string-only enum must provide a mapping in the **enum-m .. code-block:: json { - "$id": "offer", + "title": "Offer", "type": "object", "properties": { "state": { diff --git a/docs/source/generator/builtin/patternPropertiesAccessorPostProcessor.rst b/docs/source/generator/builtin/patternPropertiesAccessorPostProcessor.rst index 1f8cedde..dc062d93 100644 --- a/docs/source/generator/builtin/patternPropertiesAccessorPostProcessor.rst +++ b/docs/source/generator/builtin/patternPropertiesAccessorPostProcessor.rst @@ -14,7 +14,7 @@ Added methods .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -26,9 +26,9 @@ Added methods "type": "string" }, "^b": { - "key": "numbers" + "key": "numbers", "type": "integer" - }, + } } } @@ -36,8 +36,8 @@ Generated interface with the **PatternPropertiesAccessorPostProcessor**: .. code-block:: php - public function setExample(float $example): static; - public function getExample(): float; + public function setExample(string $example): static; + public function getExample(): ?string; public function patternProperties(): PatternPropertiesAccessor; diff --git a/docs/source/generator/builtin/populatePostProcessor.rst b/docs/source/generator/builtin/populatePostProcessor.rst index aff6b489..41846d84 100644 --- a/docs/source/generator/builtin/populatePostProcessor.rst +++ b/docs/source/generator/builtin/populatePostProcessor.rst @@ -11,7 +11,7 @@ The **PopulatePostProcessor** adds a populate method to your generated model. Th .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -34,27 +34,27 @@ Now let's have a look at the behaviour of the generated model: .. code-block:: php // initialize the model with a valid value - $example = new Example(['value' => 'Hello World']); - $example->meta()->rawInput(); // returns ['value' => 'Hello World'] + $example = new Example(['example' => 'Hello World']); + $example->meta()->rawInput(); // returns ['example' => 'Hello World'] // add an additional property to the model. // if additional property constraints are defined in your JSON-Schema // each additional property will be validated against the defined constraints. $example->populate(['additionalValue' => 12]); - $example->meta()->rawInput(); // returns ['value' => 'Hello World', 'additionalValue' => 12] + $example->meta()->rawInput(); // returns ['example' => 'Hello World', 'additionalValue' => 12] // update an existing property with a valid value - $example->populate(['value' => 'Good night!']); - $example->meta()->rawInput(); // returns ['value' => 'Good night!', 'additionalValue' => 12] + $example->populate(['example' => 'Good night!']); + $example->meta()->rawInput(); // returns ['example' => 'Good night!', 'additionalValue' => 12] // update an existing property with an invalid value which will throw an exception try { - $example->populate(['value' => false]); + $example->populate(['example' => false]); } catch (Exception $e) { // perform error handling } // if the update of the model fails no values will be updated - $example->meta()->rawInput(); // returns ['value' => 'Good night!', 'additionalValue' => 12] + $example->meta()->rawInput(); // returns ['example' => 'Good night!', 'additionalValue' => 12] .. warning:: diff --git a/docs/source/generator/custom/customPostProcessor.rst b/docs/source/generator/custom/customPostProcessor.rst index 2793e00c..9592b148 100644 --- a/docs/source/generator/custom/customPostProcessor.rst +++ b/docs/source/generator/custom/customPostProcessor.rst @@ -42,7 +42,7 @@ What can you do inside your custom post processor? If a setter for a property is called with the same value which is already stored internally (consequently no update of the property is required), the setters will return directly and as a result of that the setter hooks will not be executed. - This behaviour also applies also to properties changed via the *populate* method added by the `PopulatePostProcessor <#populatepostprocessor>`__ and the *additionalProperties().set()* method added by the `AdditionalPropertiesAccessorPostProcessor <#additionalpropertiesaccessorpostprocessor>`__ + This behaviour also applies also to properties changed via the *populate* method added by the `PopulatePostProcessor <../builtin/populatePostProcessor.html>`__ and the *additionalProperties().set()* method added by the `AdditionalPropertiesAccessorPostProcessor <../builtin/additionalPropertiesAccessorPostProcessor.html>`__ To execute code before/after the processing of the schemas override the methods **preProcess** and **postProcess** inside your custom post processor. @@ -106,4 +106,4 @@ The constructor of **PhpAttribute** accepts: .. hint:: - The library implements a set of attributes which can be configured via the `attribute configuration <../../gettingStarted.html#serialization-methods>`__ on the **GeneratorConfiguration** + The library implements a set of attributes which can be configured via the `attribute configuration <../../gettingStarted.html#attributes>`__ on the **GeneratorConfiguration** diff --git a/docs/source/generic/combinedException.rst b/docs/source/generic/combinedException.rst index 678d2ae9..d0a26557 100644 --- a/docs/source/generic/combinedException.rst +++ b/docs/source/generic/combinedException.rst @@ -21,7 +21,7 @@ Our example schema provides an array of people. The people object is combined vi ] } }, - "$id": "example", + "title": "Example", "type": "object", "properties": { "people": { diff --git a/docs/source/generic/default.rst b/docs/source/generic/default.rst index 6861cbe4..8f53d775 100644 --- a/docs/source/generic/default.rst +++ b/docs/source/generic/default.rst @@ -6,7 +6,7 @@ Default values are set inside the model if a property which is not required isn' .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -54,7 +54,7 @@ precedence over the branch default. .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "oneOf": [ { @@ -96,13 +96,13 @@ precedence over the branch default. $example = new Example(['kind' => 'B', 'timeout' => 60]); $example->getTimeout(); // returns 60 -Branch defaults are **not** included in ``getRawModelDataInput()``. Only values explicitly +Branch defaults are **not** included in ``meta()->rawInput()``. Only values explicitly supplied by the caller appear in the raw input: .. code-block:: php $example = new Example(['kind' => 'B']); - $example->getRawModelDataInput(); // returns ['kind' => 'B'] + $example->meta()->rawInput(); // returns ['kind' => 'B'] **allOf** @@ -134,7 +134,7 @@ key matches the pattern: .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "retry_count": { diff --git a/docs/source/generic/meta.rst b/docs/source/generic/meta.rst index 033abc45..0aa0a62c 100644 --- a/docs/source/generic/meta.rst +++ b/docs/source/generic/meta.rst @@ -1,10 +1,14 @@ Meta data ========= -ID --- +Class naming +------------ -The ID of a schema is used to generate the class name. If no ID is present the filename of the JSON-Schema file will be used as class name +The class name generated for the main object of a JSON-Schema file is derived from ``title`` if +present, otherwise from the basename of ``$id`` if present, and finally falls back to the +filename of the JSON-Schema file. See +`Naming of classes <../complexTypes/object.html#naming-of-classes>`__ for the full priority +order, including how nested (non-root) classes are named. .. code-block:: json @@ -13,12 +17,13 @@ The ID of a schema is used to generate the class name. If no ID is present the f "type": "object", "properties": { "example": { - "type": "string", + "type": "string" } } } -The generated class will be **MyObject** in *MyObject.php* +As no ``title`` is present, the ``$id`` is used and the generated class will be **MyObject** in +*MyObject.php*. $comment -------- @@ -29,7 +34,7 @@ in the getter's PHPDoc and is not used for validation. .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -61,7 +66,7 @@ as an ``@example`` line in the getter's PHPDoc and is not used for validation. .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "status": { @@ -95,7 +100,7 @@ If a property provides a description this description will be adopted into the g .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { diff --git a/docs/source/generic/readonly.rst b/docs/source/generic/readonly.rst index 8d6cb79f..8116b36f 100644 --- a/docs/source/generic/readonly.rst +++ b/docs/source/generic/readonly.rst @@ -9,7 +9,7 @@ By default all classes are immutable. If the GeneratorConfiguration option for i .. code-block:: json { - "$id": "person", + "title": "Person", "type": "object", "properties": { "name": { @@ -28,7 +28,7 @@ Generated interface (with immutability disabled): public function getName(): ?string; - public function setAge(int $example): static; + public function setAge(int $age): static; public function getAge(): ?int; writeOnly @@ -39,7 +39,7 @@ Properties marked with ``writeOnly: true`` suppress getter generation. The prope .. code-block:: json { - "$id": "credentials", + "title": "Credentials", "type": "object", "properties": { "username": { diff --git a/docs/source/generic/references.rst b/docs/source/generic/references.rst index 5ade0531..0957f592 100644 --- a/docs/source/generic/references.rst +++ b/docs/source/generic/references.rst @@ -45,12 +45,12 @@ An example for properties referring to a definition inside the same schema (Draf } } }, - "$id": "team", + "$id": "https://example.com/schemas/team", "type": "object", "properties": { "leader": { "$ref": "#person" - } + }, "members": { "type": "array", "items": { @@ -77,7 +77,7 @@ Draft 2019-09 introduced ``$defs`` as the standard replacement for ``definitions } } }, - "$id": "team", + "$id": "https://example.com/schemas/team", "type": "object", "properties": { "leader": { diff --git a/docs/source/generic/required.rst b/docs/source/generic/required.rst index 66452eb5..0953ec7f 100644 --- a/docs/source/generic/required.rst +++ b/docs/source/generic/required.rst @@ -6,7 +6,7 @@ By default the values of a schema are not required. In this case the input is va .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -49,7 +49,7 @@ By setting the property to a required value the property must be always provided .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { diff --git a/docs/source/gettingStarted.rst b/docs/source/gettingStarted.rst index b6d47706..d211abcd 100644 --- a/docs/source/gettingStarted.rst +++ b/docs/source/gettingStarted.rst @@ -16,11 +16,11 @@ To avoid adding all dependencies of the php-json-model-generator to your product Generating classes ------------------ -The base object for generating models is the *Generator*. After you have created a Generator you can use the object to generate your model classes without any further configuration: +The base object for generating models is the *ModelGenerator*. After you have created a ModelGenerator you can use the object to generate your model classes without any further configuration: .. code-block:: php - (new Generator()) + (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: @@ -51,7 +51,7 @@ As an optional parameter you can set up a *GeneratorConfiguration* object to con .. code-block:: php - $generator = new Generator( + $generator = new ModelGenerator( (new GeneratorConfiguration()) ->setNamespacePrefix('MyApp\Model') ->setImmutable(false) @@ -85,7 +85,7 @@ column, and the same information is available as structured accessors: .. code-block:: php try { - (new Generator())->generateModels(new RecursiveDirectoryProvider(__DIR__ . '/schema'), __DIR__ . '/result'); + (new ModelGenerator())->generateModels(new RecursiveDirectoryProvider(__DIR__ . '/schema'), __DIR__ . '/result'); } catch (\PHPModelGenerator\Exception\SchemaException $e) { $e->getMessage(); // e.g. 'Invalid JSON-Schema file schema/Person.json at line 4, column 12' $e->getSchemaFile(); // 'schema/Person.json' @@ -107,7 +107,7 @@ The constructor of a generated model takes an array as argument. The array must .. code-block:: json { - "$id": "person", + "title": "Person", "type": "object", "properties": { "name": { @@ -135,8 +135,8 @@ After generating a class with this JSON-Schema our class with the name `Person` public function getAge(): ?int; // setters to change the values of the model after instantiation - public function setName(string $name): Person; - public function setAge(?int $age): Person; + public function setName(string $name): static; + public function setAge(?int $age): static; // meta()->rawInput() always delivers the raw input which was provided on instantiation public function meta(): Meta; @@ -479,17 +479,27 @@ draft instance (``DraftInterface``) to pin all schemas to one draft, or a factor (``DraftFactoryInterface``) to select the draft per schema file. By default ``AutoDetectionDraft`` is used. It implements ``DraftFactoryInterface`` and inspects -the ``$schema`` keyword of each schema file to select the appropriate draft automatically. When -the keyword is absent or unrecognised, it falls back to JSON Schema Draft 7 behaviour, so schemas -with different ``$schema`` declarations in the same generation run can use different drafts. +the ``$schema`` keyword of each schema file to select the appropriate draft automatically. It only +recognises the Draft 2019-09 ``$schema`` URI (``https://json-schema.org/draft/2019-09/schema``, +with or without a trailing ``#``, ``http`` or ``https``); when the keyword is absent or any other +value (including the Draft 2020-12 URI), it falls back to JSON Schema Draft 7 behaviour. Schemas +with different ``$schema`` declarations in the same generation run can therefore use different +drafts. Available draft classes: -============= ================================ -Draft class Description -============= ================================ -``Draft_07`` JSON Schema Draft 7 (default) -============= ================================ +``Draft_07`` + JSON Schema Draft 7 (default; used by auto-detection for any schema without a recognised + ``$schema`` URI). + +``Draft_2019_09`` + JSON Schema Draft 2019-09; adds ``minContains``/``maxContains`` support for the ``contains`` + keyword on top of ``Draft_07``. Selected automatically by ``AutoDetectionDraft`` via the + ``$schema`` URI. + +``Draft_2020_12`` + JSON Schema Draft 2020-12; currently behaves identically to ``Draft_2019_09``. Not + auto-detected — pass it explicitly via ``setDraft(new Draft_2020_12())``. .. seealso:: diff --git a/docs/source/nonStandardExtensions/filter.rst b/docs/source/nonStandardExtensions/filter.rst index 865b947c..d868a59a 100644 --- a/docs/source/nonStandardExtensions/filter.rst +++ b/docs/source/nonStandardExtensions/filter.rst @@ -8,7 +8,7 @@ Filters can be either supplied as a string or as a list of filters (multiple fil .. code-block:: json { - "$id": "person", + "title": "Person", "type": "object", "properties": { "firstname": { @@ -197,7 +197,7 @@ The trim filter accepts string and null values. Applied to a property that also .. code-block:: json { - "$id": "person", + "title": "Person", "type": "object", "properties": { "name": { @@ -241,7 +241,7 @@ The notEmpty filter is only valid for array and null properties. .. code-block:: json { - "$id": "family", + "title": "Family", "type": "object", "properties": { "members": { @@ -274,7 +274,7 @@ With the type of your property, you can limit the possible inputs, e.g. to accep .. code-block:: json { - "$id": "car", + "title": "Car", "type": "object", "properties": { "productionDate": { @@ -344,7 +344,7 @@ You can implement custom filter and use them in your schema files. You must add .. code-block:: php - $generator = new Generator( + $generator = new ModelGenerator( (new GeneratorConfiguration()) ->addFilter(new UppercaseFilter()) ); @@ -389,7 +389,7 @@ If the custom filter is added to the generator configuration you can now use the .. code-block:: json { - "$id": "person", + "title": "Person", "type": "object", "properties": { "name": { @@ -429,7 +429,7 @@ The option will be available if your JSON-Schema uses the object-notation for th .. code-block:: json { - "$id": "person", + "title": "Person", "type": "object", "properties": { "name": { diff --git a/docs/source/toc-generator.rst b/docs/source/toc-generator.rst index b7d34c6c..7ce90705 100644 --- a/docs/source/toc-generator.rst +++ b/docs/source/toc-generator.rst @@ -3,4 +3,3 @@ :maxdepth: 2 generator/postProcessor - generator/custom/customDraft diff --git a/docs/source/types/boolean.rst b/docs/source/types/boolean.rst index ba135813..f28031de 100644 --- a/docs/source/types/boolean.rst +++ b/docs/source/types/boolean.rst @@ -6,7 +6,7 @@ Used for properties containing boolean values. Converted to the PHP type `bool`. .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { diff --git a/docs/source/types/const.rst b/docs/source/types/const.rst index faf7a314..543d83e8 100644 --- a/docs/source/types/const.rst +++ b/docs/source/types/const.rst @@ -6,7 +6,7 @@ Used for properties which only accept a single value. .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -20,7 +20,7 @@ Generated interface (the typehint is auto-detected from the given constant value .. code-block:: php public function setExample(int $example): static; - public function getExample(): int; + public function getExample(): ?int; Possible exceptions: diff --git a/docs/source/types/null.rst b/docs/source/types/null.rst index 273de051..44209e97 100644 --- a/docs/source/types/null.rst +++ b/docs/source/types/null.rst @@ -6,7 +6,7 @@ Used for properties which only accept `null`. .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -26,7 +26,7 @@ Possible exceptions: * Invalid type for 'example': requires 'null', got '__TYPE__' -The main use case for the **null** type is a property with `multiple types `__ accepting for example a string and null values when using explicit null types. +The main use case for the **null** type is a property with `multiple types <../complexTypes/multiType.html>`__ accepting for example a string and null values when using explicit null types. The thrown exception will be a *PHPModelGenerator\\Exception\\Generic\\InvalidTypeException* which provides the following methods to get further error details: diff --git a/docs/source/types/number.rst b/docs/source/types/number.rst index 760048ca..c4430503 100644 --- a/docs/source/types/number.rst +++ b/docs/source/types/number.rst @@ -6,7 +6,7 @@ Used for properties containing numeric values. Properties with the type `integer .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example1": { @@ -22,11 +22,11 @@ Generated interface: .. code-block:: php - public function setExample1(int $example): static; + public function setExample1(int $example1): static; // As the property is not required it may be initialized with null. Consequently the return value is nullable public function getExample1(): ?int; - public function setExample2(float $example): static; + public function setExample2(float $example2): static; public function getExample2(): ?float; Possible exceptions: @@ -55,7 +55,7 @@ To add a range validation to the property use the `minimum`, `maximum` and `excl .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example1": { @@ -108,7 +108,7 @@ To add a multiple of validation to the property use the `multipleOf` keyword. .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -126,7 +126,7 @@ The thrown exception will be a *PHPModelGenerator\\Exception\\Number\\MultipleOf .. code-block:: php - // returns the multipleOd constraint + // returns the multipleOf constraint public function getMultipleOf() // get the name of the property which failed public function getPropertyName(): string diff --git a/docs/source/types/string.rst b/docs/source/types/string.rst index 9e8f2bad..f863bca1 100644 --- a/docs/source/types/string.rst +++ b/docs/source/types/string.rst @@ -6,7 +6,7 @@ Used for properties containing characters. Converted to the PHP type `string`. .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -48,7 +48,7 @@ To add a length validation to the property use the `minLength` and `maxLength` k .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -91,7 +91,7 @@ To add a pattern validation to the property use the `pattern` keyword. .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -126,7 +126,7 @@ To add a format validation to the property use the `format` keyword. .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "example": { @@ -208,7 +208,7 @@ You can implement custom format validators and use them in your schema files. Yo .. code-block:: php - $generator = new Generator( + $generator = new ModelGenerator( (new GeneratorConfiguration()) ->addFormat('customFormat', new MyCustomFormat()) ); @@ -219,7 +219,7 @@ If your custom format is representable by a regular expression you can bypass im .. code-block:: php - $generator = new Generator( + $generator = new ModelGenerator( (new GeneratorConfiguration()) ->addFormat('numeric', new FormatValidatorFromRegEx('/^\d*$/')) ); @@ -241,7 +241,7 @@ metadata. .. code-block:: json { - "$id": "example", + "title": "Example", "type": "object", "properties": { "avatar": {