Avoiding Circular References
Avoiding Circular References
~13 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
Chapter 40 showed embedding ON PURPOSE – this chapter explains WHY tasks on Project must DELIBERATELY NEVER show embedded project references.
Provoking the problem
Suppose project:read:embedded were set on BOTH Project::tasks AND Task::project: a project embeds its tasks, EACH embedded task embeds its project IN TURN, which embeds its tasks IN TURN – an ENDLESS nesting.
{
"id": 1,
"tasks": [
{"id": 1, "project": {"id": 1, "tasks": [{"id": 1, "project": { /* ... infinite ... */ } }]}}
]
}Achtung: The Symfony Serializer AUTOMATICALLY detects CIRCULAR references and throws a CircularReferenceException instead of recursing ENDLESSLY – the symptom is a 500 error, NOT a cleanly caught state.
The solution: separate embedded groups
Chapter 40 ALREADY used the correct strategy, here made EXPLICIT once more: Task::project gets task:read:embedded, Project::tasks gets NO corresponding embedded group for project:read:embedded – embedding is thereby DIRECTIONAL, NEVER bidirectional.
// Project.php - tasks deliberately stays ONLY project:read (chapter 38), NEVER embedded
#[Groups(['project:read'])]
private Collection $tasks;
// Task.php - project is allowed to be embedded (chapter 40)
#[Groups(['task:read', 'task:write', 'task:read:embedded'])]
private ?Project $project = null;The mnemonic: a tree, not a circle
Relationships should be thought of as a TREE, NOT a circle: Project → Task → Tag is a CLEAR direction. As soon as an embedding would lead BACK to the starting point, an IRI (instead of embedding) is the RIGHT choice.
maxDepth as an additional safeguard
#[Groups(['task:read', 'task:write', 'task:read:embedded'])]
#[\Symfony\Component\Serializer\Annotation\MaxDepth(1)]
private ?Project $project = null;MaxDepth is an ADDITIONAL safety net (requires enable_max_depth: true in normalizationContext) – EVEN IF the group configuration accidentally allowed a cycle, the nesting depth would be HARD-capped.
Tipp: When UNSURE, the rule is: BETTER an extra IRI request from the client (one more click) THAN a circular, potentially ERROR-PRONE embedding configuration – performance optimization through embedding is ONLY worth it for DEMONSTRABLY problematic N+1 situations.