If

The keywords if, then and else can be used to conditionally combine multiple subschemas. Conditional compositions can be used on property level and on object level.

{
    "$id": "example",
    "type": "object",
    "properties": {
        "example": {
            "type": "number",
            "if": {
                "multipleOf": 5
            },
            "then": {
                "minimum": 100
            },
            "else": {
                "maximum": 100
            }
        }
    }
}

Valid values are eg. 100, 105, 99. Invalid values are eg. 50, 101 or any non numeric values.

Generated interface:

public function setExample(float $example): static;
public function getExample(): ?float;

Possible exception (in this case 50 was provided so the if condition succeeds but the then branch failed):

Invalid value for example declined by conditional composition constraint
  - Condition: Valid
  - Conditional branch failed:
    * Value for example must not be smaller than 100

Another example exception with 101 as value for the property:

Invalid value for example declined by conditional composition constraint
  - Condition: Failed
    * Value for example must be a multiple of 5
  - Conditional branch failed:
    * Value for example must not be larger than 100

The thrown exception will be a PHPModelGenerator\Exception\ComposedValue\ConditionalException which provides the following methods to get further error details:

// get the exception which triggered the condition to fail
// if error collection is enabled an ErrorRegistryException will be returned
public function getIfException(): ?Exception

// get the exception which triggered the conditional branch to fail
// if error collection is enabled an ErrorRegistryException will be returned
public function getThenException(): ?Exception
public function getElseException(): ?Exception

// 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

An object level composition will result in an object which contains all properties contained in the three possible blocks of the condition.

{
    "$id": "customer",
    "type": "object",
    "properties": {
        "country": {
            "enum": ["United States of America", "Canada"]
        }
    },
    "if": {
        "type": "object",
        "properties": {
            "country": {
                "const": "United States of America"
            }
        }
    },
    "then": {
        "type": "object",
        "properties": {
            "postal_code": {
                "pattern": "[0-9]{5}(-[0-9]{4})?"
            }
        }
    },
    "else": {
        "type": "object",
        "properties": {
            "postal_code": {
                "pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]"
            }
        }
    }
}

Generated interface:

public function setCountry(string $country): static;
public function getCountry(): ?string;

public function setPostalCode(string $country): 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:

{
    "$id": "example",
    "type": "object",
    "if": {
        "properties": {
            "name": {
                "const": "Alice"
            }
        }
    },
    "then": {
        "properties": {
            "age": {
                "type": "integer"
            }
        }
    },
    "else": {
        "properties": {
            "age": {
                "type": "string"
            }
        }
    }
}

Generated interface:

public function setAge(int | string | null $age): static;
public function getAge(): int | string | null;

When only a then block is present (no else), the branch may not apply at runtime, so the property is always nullable:

public function setAge(?int $age): static;
public function getAge(): ?int;

Note

Any of the three branches (if, then, else) can be the boolean literal true or false. The generator resolves these statically at generation time:

  • if: false — condition never matches; else (if present) is applied unconditionally, then is ignored.

  • if: true — condition always matches; then (if present) is applied unconditionally, else is ignored.

  • if: false, else: false or if: true, then: false — the composition is always unsatisfiable; providing any value raises a ConditionalException at runtime. The generator also emits a warning at generation time.

  • then: false / else: false (with a real schema for if) — when the relevant branch is entered, the value would always be invalid; the generator throws a SchemaException at generation time.

  • then: true / else: true — when the relevant branch is entered, any value is accepted; treated as absent (no additional constraint).

Hint

The union-widening and nullability rules for if/then/else follow the same logic as anyOf/oneOf. See Cross-typed compositions for the full explanation.

Note

For object-level if/then/else compositions, when a property appears in the required array of both then and else, the generator promotes that property to non-nullable. Exactly one of the two branches applies at runtime, and both guarantee the property’s presence. If there is no else block, the property is never promoted — the schema is silent when the condition fails, so the property may be absent. See Cross-typed compositions for the full promotion rules.

Note

Properties in object-level then or else branches may carry a "default" value. The 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().

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 at generation time.

See Default values for the full explanation.