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,thenis ignored.if: true— condition always matches;then(if present) is applied unconditionally,elseis ignored.if: false, else: falseorif: true, then: false— the composition is always unsatisfiable; providing any value raises aConditionalExceptionat runtime. The generator also emits a warning at generation time.then: false/else: false(with a real schema forif) — when the relevant branch is entered, the value would always be invalid; the generator throws aSchemaExceptionat 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.