Context
Conditioned relationships shipped in eedada8. A relationship may declare a condition (a fixed column => scalar equality map, e.g. object_type => 'order') that scopes the related rows, so a polymorphic child table (one table with an object_id + object_type pair pointing at different parent types, as EDD's notes/logs/transactions/adjustments do) can be modeled as one relationship instead of hand-coded SQL.
The condition is rendered as AND {remote}.{column} = {value} across every SQL form (get_related traversal, the correlated EXISTS filter, and nested EXISTS), fails closed on any unknown/malformed path, is application-layer only (never a FOREIGN KEY — a discriminator can't be encoded in one), and defaults to the join/EXISTS filter strategy.
This closed the last EDD reunification-readiness "modeling" gap.
v1 scope (intentional) and the deferred follow-ups
Everything below is non-breaking to add later (the value-object shape, the m2m rejection, and the unprimed behavior all extend cleanly, and the relationship API is unreleased) — which is the whole reason they were deferred rather than shipped now. Each has a specific reason beyond "YAGNI":
1. Richer predicates (operators / IN), not just scalar equality
v1 accepts condition => array( 'object_type' => 'order' ) (equality, scalar values). To extend to array( 'status' => array( 'compare' => '!=', 'value' => 'trash' ) ) or IN lists:
- The filter path already routes through the shared
build_condition(), which handles !=, >, IN, casts — so richer predicates there are nearly free.
- The traversal path (
get_related) can only speak query-vars ({col}__in), which express = and IN but not >/!=/LIKE.
- So doing this halfway creates an asymmetry (a
!= condition would filter but silently do something else on traversal). The blocker isn't the filter side — it's deciding what traversal does with a non-IN operator (probably: fail closed, or fall back to a per-item query). Spec that first.
2. many_to_many support
v1 rejects a condition on a many_to_many (a get_validation_errors() error; traversal + filter fail closed). Deferred because there's no consumer and a real design fork: on a polymorphic pivot, does the condition scope the pivot row or the target row? Guessing wrong bakes in the wrong answer — resolve the semantics against a real use case first.
3. Cache priming for conditioned relationships
v1 leaves conditioned relationships unprimed (Traits\Query\Cache::prime_* early-returns when has_condition()). This is perf-only — get_related() still returns correct rows (its own conditioned query; its cache key includes the condition, so an unconditioned primed entry can never be a false hit). Threading the discriminator through the prime primitives (prime_has_many, prime_relationship_tuples) is a deeper change for a with-priming speedup nobody has measured needing yet.
Also worth noting (not a v1 limitation, a requirement)
For the query-var paths (traversal + the in strategy), a condition column must be in => true on the remote (they filter via {col}__in); the join/EXISTS path renders raw SQL and needs no flag. This is enforced (fail closed) and documented on the Relationship::$condition property.
Related
Context
Conditioned relationships shipped in
eedada8. A relationship may declare acondition(a fixedcolumn => scalarequality map, e.g.object_type => 'order') that scopes the related rows, so a polymorphic child table (one table with anobject_id+object_typepair pointing at different parent types, as EDD's notes/logs/transactions/adjustments do) can be modeled as one relationship instead of hand-coded SQL.The condition is rendered as
AND {remote}.{column} = {value}across every SQL form (get_related traversal, the correlated EXISTS filter, and nested EXISTS), fails closed on any unknown/malformed path, is application-layer only (never a FOREIGN KEY — a discriminator can't be encoded in one), and defaults to thejoin/EXISTS filter strategy.This closed the last EDD reunification-readiness "modeling" gap.
v1 scope (intentional) and the deferred follow-ups
Everything below is non-breaking to add later (the value-object shape, the m2m rejection, and the unprimed behavior all extend cleanly, and the relationship API is unreleased) — which is the whole reason they were deferred rather than shipped now. Each has a specific reason beyond "YAGNI":
1. Richer predicates (operators /
IN), not just scalar equalityv1 accepts
condition => array( 'object_type' => 'order' )(equality, scalar values). To extend toarray( 'status' => array( 'compare' => '!=', 'value' => 'trash' ) )orINlists:build_condition(), which handles!=,>,IN, casts — so richer predicates there are nearly free.get_related) can only speak query-vars ({col}__in), which express=andINbut not>/!=/LIKE.!=condition would filter but silently do something else on traversal). The blocker isn't the filter side — it's deciding what traversal does with a non-INoperator (probably: fail closed, or fall back to a per-item query). Spec that first.2.
many_to_manysupportv1 rejects a condition on a
many_to_many(aget_validation_errors()error; traversal + filter fail closed). Deferred because there's no consumer and a real design fork: on a polymorphic pivot, does the condition scope the pivot row or the target row? Guessing wrong bakes in the wrong answer — resolve the semantics against a real use case first.3. Cache priming for conditioned relationships
v1 leaves conditioned relationships unprimed (
Traits\Query\Cache::prime_*early-returns whenhas_condition()). This is perf-only —get_related()still returns correct rows (its own conditioned query; its cache key includes the condition, so an unconditioned primed entry can never be a false hit). Threading the discriminator through the prime primitives (prime_has_many,prime_relationship_tuples) is a deeper change for awith-priming speedup nobody has measured needing yet.Also worth noting (not a v1 limitation, a requirement)
For the query-var paths (traversal + the
instrategy), a condition column must bein => trueon the remote (they filter via{col}__in); thejoin/EXISTS path renders raw SQL and needs no flag. This is enforced (fail closed) and documented on theRelationship::$conditionproperty.Related
berlindb/readiness/edd-core-tablesmatrix).