Summary
When a schema file's entire top level is a single {"$ref": "..."}, only the referenced schema's
properties are transferred onto the referencing schema. Its own object-level constraints are
dropped and never validated:
additionalProperties
minProperties / maxProperties
propertyNames
- any root-level composition (
allOf / anyOf / oneOf / if)
Input that violates those constraints is accepted.
Why this is worse than a missing check
For the composition case the generated object can be constructed in a state its own type contract
forbids. The failure then surfaces later, at an arbitrary call site, as a plain PHP TypeError
rather than a validation exception at construction time.
Reproduction
Root.json:
{
"$ref": "Composed.json"
}
Composed.json:
{
"type": "object",
"allOf": [
{
"type": "object",
"required": ["name"],
"properties": {
"name": {
"type": "string"
}
}
}
]
}
new Root([]) violates the allOf branch's required: ["name"].
Expected: a validation exception at construction.
Actual: the constructor succeeds. name was promoted to a non-nullable string because it is
required, but nothing populated it, so the first call to the getter throws:
TypeError: Root::getName(): Return value must be of type string, null returned
A second reproduction for the non-composition constraints — referenced schema with
"additionalProperties": false and "minProperties": 2:
new Root(['name' => 'Hannes', 'extra' => 1]); // accepted, additionalProperties not enforced
new Root(['name' => 'Hannes']); // accepted, minProperties not enforced
Cause
The referenced schema enforces these through its own base validators, not through validators
attached to the individual properties — those are merged/redirected and carry no validation of
their own. The base-level reference transfer copied only the properties.
Affected versions
Present in 0.26.2 (BasereferenceProcessor) and on master after the processor rework
(PropertyFactory::processBaseReference()), so this has been present across the refactor rather
than introduced by it.
Fix
Fixed in #166: the referenced schema's base validators are now transferred alongside its
properties. Covered by tests for both the composition case and the
additionalProperties/minProperties/propertyNames case.
Summary
When a schema file's entire top level is a single
{"$ref": "..."}, only the referenced schema'sproperties are transferred onto the referencing schema. Its own object-level constraints are
dropped and never validated:
additionalPropertiesminProperties/maxPropertiespropertyNamesallOf/anyOf/oneOf/if)Input that violates those constraints is accepted.
Why this is worse than a missing check
For the composition case the generated object can be constructed in a state its own type contract
forbids. The failure then surfaces later, at an arbitrary call site, as a plain PHP
TypeErrorrather than a validation exception at construction time.
Reproduction
Root.json:{ "$ref": "Composed.json" }Composed.json:{ "type": "object", "allOf": [ { "type": "object", "required": ["name"], "properties": { "name": { "type": "string" } } } ] }new Root([])violates theallOfbranch'srequired: ["name"].Expected: a validation exception at construction.
Actual: the constructor succeeds.
namewas promoted to a non-nullablestringbecause it isrequired, but nothing populated it, so the first call to the getter throws:
A second reproduction for the non-composition constraints — referenced schema with
"additionalProperties": falseand"minProperties": 2:Cause
The referenced schema enforces these through its own base validators, not through validators
attached to the individual properties — those are merged/redirected and carry no validation of
their own. The base-level reference transfer copied only the properties.
Affected versions
Present in 0.26.2 (
BasereferenceProcessor) and onmasterafter the processor rework(
PropertyFactory::processBaseReference()), so this has been present across the refactor ratherthan introduced by it.
Fix
Fixed in #166: the referenced schema's base validators are now transferred alongside its
properties. Covered by tests for both the composition case and the
additionalProperties/minProperties/propertyNamescase.