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