Description
Draft06Constraint::check(), Draft07Constraint::check(), and Draft2019Constraint::check() each call checkForKeyword() for every registered keyword on every schema node, regardless of whether that keyword is present in the schema. Each call invokes $this->factory->createInstanceFor($keyword), creating a fresh constraint object that immediately returns after a property_exists guard inside its own check():
// same pattern in all three draft constraint classes
protected function checkForKeyword(string $keyword, $value, $schema = null, ...): void
{
$validator = $this->factory->createInstanceFor($keyword); // always instantiated
$validator->check($value, $schema, $path, $i); // immediately returns if keyword absent
$this->addErrors($validator->getErrors());
}
Keyword counts per draft:
Draft06Constraint: 27 unconditional instantiations per node
Draft07Constraint: 28 unconditional instantiations per node
Draft2019Constraint: 30 unconditional instantiations per node
For a typical schema with 3–5 keywords, 22–27 constraint objects are instantiated and discarded per schema node. In a deeply nested document this multiplies across every node visited.
Affected Files
src/JsonSchema/Constraints/Drafts/Draft06/Draft06Constraint.php
src/JsonSchema/Constraints/Drafts/Draft07/Draft07Constraint.php
src/JsonSchema/Constraints/Drafts/Draft2019/Draft2019Constraint.php
Suggested Fix
Guard the createInstanceFor call with a property_exists check before instantiating:
protected function checkForKeyword(string $keyword, $value, $schema = null, ?JsonPointer $path = null, $i = null): void
{
if (!\property_exists($schema, $keyword)) {
return;
}
$validator = $this->factory->createInstanceFor($keyword);
$validator->check($value, $schema, $path, $i);
$this->addErrors($validator->getErrors());
}
Note: the keyword name passed to checkForKeyword must match the schema property name for this guard to work. Most do ('required', 'type', 'pattern', etc.), but two do not:
'ref' maps to schema property '$ref'
'ifThenElse' maps to schema property 'if'
These two would need special handling or a keyword→schema-property mapping table.
Description
Draft06Constraint::check(),Draft07Constraint::check(), andDraft2019Constraint::check()each callcheckForKeyword()for every registered keyword on every schema node, regardless of whether that keyword is present in the schema. Each call invokes$this->factory->createInstanceFor($keyword), creating a fresh constraint object that immediately returns after aproperty_existsguard inside its owncheck():Keyword counts per draft:
Draft06Constraint: 27 unconditional instantiations per nodeDraft07Constraint: 28 unconditional instantiations per nodeDraft2019Constraint: 30 unconditional instantiations per nodeFor a typical schema with 3–5 keywords, 22–27 constraint objects are instantiated and discarded per schema node. In a deeply nested document this multiplies across every node visited.
Affected Files
src/JsonSchema/Constraints/Drafts/Draft06/Draft06Constraint.phpsrc/JsonSchema/Constraints/Drafts/Draft07/Draft07Constraint.phpsrc/JsonSchema/Constraints/Drafts/Draft2019/Draft2019Constraint.phpSuggested Fix
Guard the
createInstanceForcall with aproperty_existscheck before instantiating:Note: the keyword name passed to
checkForKeywordmust match the schema property name for this guard to work. Most do ('required','type','pattern', etc.), but two do not:'ref'maps to schema property'$ref''ifThenElse'maps to schema property'if'These two would need special handling or a keyword→schema-property mapping table.