Repository navigation
Keep the declaration of the main schema for properties of dependency schemas - #197
Merged
wol-soft merged 4 commits intoOct 21, 2026
Merged
Conversation
…schemas The properties of a schema dependency were transferred to the base schema while the properties were processed. A property which the dependency schema redeclares and which the main schema declares after the dependant property lost its declaration (type, validators, filters) to the unvalidated copy of the dependency schema. The transfer now happens after the properties of the base schema are registered. Document that filters of a dependency schema run inside the dependency schema only and pin it with a test. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GXn5VCTemp5rnjGrXMMvJs
Replace the deferred transfer by a replaceable registration: the copy of a property of a dependency schema keeps its place in the property order (the generated class and the serialization list it where the dependency schema transferred it) and is replaced in place by the declaration of the base schema if that is registered later. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GXn5VCTemp5rnjGrXMMvJs
Coverage Report for CI Build 39629000301Warning Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes. Coverage increased (+0.04%) to 98.932%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
…rtyMerger Schema::addProperty special-cased the copies of dependency schemas before the merger. The precedence rule is now part of PropertyMerger::merge(): a property which the schema declares itself replaces a property which was only transferred from a composition branch or a dependency schema, independent of the registration order (merge() returns whether the incoming property replaces the existing one). The dependency copies are registered as transferred properties instead of being tracked in a separate list. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GXn5VCTemp5rnjGrXMMvJs
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GXn5VCTemp5rnjGrXMMvJs
wol-soft
deleted the
claude/dependency-schema-properties-keep-main-declaration
branch
October 21, 2026 15:49
This was referenced Oct 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Found while checking how filters in schema dependencies behave (follow-up of #193); the bug exists on
master.The properties of a schema dependency (
"dependencies": {"credit_card": {"properties": {...}}}) are transferred to the base schema as untyped, unvalidated copies. The transfer happens while the properties of the base schema are processed. The declaration which is registered first wins, so if the main schema declares the same property after the property that carries the dependency, the copy of the dependency schema silently replaces it: type, validators and filters of the main declaration are lost (and the getter becomesmixed). The result depended on the key order ofproperties:{ "type": "object", "properties": { "credit_card": { "type": "integer" }, "billing_address": { "type": "string", "minLength": 3, "filter": "trim" } }, "dependencies": { "credit_card": { "properties": { "billing_address": { "type": "string" } }, "required": ["billing_address"] } } }With
credit_cardfirst,new Model(['billing_address' => ' ab '])was accepted and the value was not trimmed. Withbilling_addressfirst the declaration was kept.Filters in dependency schemas
The behaviour for filters in a dependency schema is already consistent with the condition of an
if(see #193): the filter runs inside the dependency schema, so the validators of the property see the filtered value and a failing filter fails the dependency, but the base model returns the value as provided (the schema dependency docs state that the transferred properties don't validate). No rejection is needed: unlike theanyOfcase nothing is applied ambiguously, as the transferred copy never carries a filter. This PR documents the rule and pins it with a test. A property which should be returned filtered has to be declared (with the filter) in the main schema, which now works independent of the property order.Changes
The precedence between a declared and a transferred property is decided by
PropertyMerger, the generic mechanismSchema::addProperty()already uses when a name is registered twice:PropertyMerger::merge()gets the parameter$incomingIsRootand now returns whether$incomingreplaces$existing. A property which the schema declares itself takes precedence over a property which was only transferred from a composition branch or a dependency schema ($isRootRegisteredis false), independent of the registration order. The opposite order was already covered byguardRootPrecedence()(root wins over branches). Existing callers ignore the return value.Schema::addProperty()lets the incoming property take over the slot when the merger says so (replaceRegisteredProperty()). The slot keeps its position, so the generated class and the serialization order don't change. TheonAllPropertiesResolvedbookkeeping is adjusted for a replacement which is not resolved yet ($ref).PropertyDependencyTraitregisters the copies as transferred properties (SchemaDependencyValidatoras the transferring component) instead of as properties of the base schema; this is what made them win before.object.rst.Design decisions
Schema-internal list of dependency copies whichaddProperty()replaced before the merger. It bypassedPropertyMergerand only covered dependency schemas; the precedence rule belongs next toguardRootPrecedence().GeneratedCodeFormattingTestpins the current position and I did not want to change the order for existing users.SchemaException. The filter has a defined effect (it runs as part of the validation) and nothing is dropped silently, unlike the filtered properties of ananyOfbranch.Test plan
SchemaDependencyTest::testPropertyOfTheMainSchemaWinsOverTheDeclarationOfADependencySchema(both property orders; fails onmasterfor the order that lost the declaration) andtestFilterInDependencySchemaRunsInsideTheDependencySchemaOnly.SchemaPropertyPrecedenceTest(unit): a declared property replaces a transferred one and keeps its position, a transferred property doesn't replace a declared one, the resolution bookkeeping with an unresolved replacement (mutation-checked).ReferencePropertyTestcases which fetchraw.githubusercontent.com(blocked in the sandbox).phpcsclean on the changed files. Also probed a$refmain declaration after the dependant property.🤖 Generated with Claude Code
https://claude.ai/code/session_01GXn5VCTemp5rnjGrXMMvJs