Skip to content

Draft06Constraint, Draft07Constraint, and Draft2019Constraint instantiate all constraint objects unconditionally per schema node #920

@DannyvdSluijs

Description

@DannyvdSluijs

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions