ArrayCollection of root entity not filled with related entities. #7486

Closed
opened 2026-01-22 15:52:14 +01:00 by admin · 1 comment
Owner

Originally created by @AndreRudolph on GitHub (Mar 10, 2025).

Bug Report

Many To Many by using Pivot entity as One To Many <-> Many To One does not fill ArrayCollection with entities.

Q A
Version 3.3.2

Summary

I have a N:M relation which I split up into One To Many <-> Many to One by adding a pivot entity. More precisely I have the pivot Entity TeamUsers which references both sides, the Team and the User.

Current behavior

I have these entities:

`#[Entity]
#[Table(name: 'teams')]
#[HasLifecycleCallbacks]
class Team implements TeamInterface {

#[Id]
#[GeneratedValue(strategy: 'CUSTOM')]
#[CustomIdGenerator(class: UuidGenerator::class)]
#[Column(type: 'string', length: 36, unique: true)]
private string $id;

#[Column(type: 'string', length: 255, nullable: false)]
private string $name;

#[ManyToOne(targetEntity: Tenant::class, cascade: ['remove'])]
#[JoinColumn(name: 'tenant_id', referencedColumnName: 'id', nullable: false)]
private TenantInterface $tenant;

#[ManyToOne(targetEntity: User::class, cascade: ['remove'])]
#[JoinColumn(name: 'owner_id', referencedColumnName: 'id', nullable: false)]
private UserInterface $owner;

#[OneToMany(targetEntity: TeamUser::class, mappedBy: 'team')]
private Collection $teamUsers;

#[Column(type: 'datetime', nullable: false)]
public DateTimeInterface $createdAt;

public function __construct() {
    $this->teamUsers = new ArrayCollection();
}

public function getTeamUsers(): Collection {
    return $this->teamUsers;
}

public function setTeamUsers(Collection $teamUsers): TeamInterface {
    $this->teamUsers = $teamUsers;
    return $this;
}

public function addTeamUser(TeamUser $teamUser): self {
    if (!$this->teamUsers->contains($teamUser)) {
        $this->teamUsers->add($teamUser);
        $teamUser->setTeam($this);
    }
    return $this;
}

public function removeTeamUser(TeamUser $teamUser): self {
    if ($this->teamUsers->contains($teamUser)) {
        $this->teamUsers->removeElement($teamUser);
        if ($teamUser->getTeam() === $this) {
            $teamUser->setTeam(null);
        }
    }
    return $this;
}

}`

`
#[Entity]
#[Table(name: 'team_users')]
#[HasLifecycleCallbacks]
class TeamUser implements TeamUserInterface {

#[Id]
#[ManyToOne(targetEntity: User::class)]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id', nullable: false)]
private UserInterface $user;

#[Id]
#[ManyToOne(targetEntity: Team::class, inversedBy: 'teamUsers')]
#[JoinColumn(name: 'team_id', referencedColumnName: 'id', nullable: false)]
private TeamInterface $team;

#[Column(type: 'datetime', nullable: false)]
private DateTimeInterface $joinedAt;

// setters & getters

}`

`
#[Entity]
#[Table(name: 'users')]
#[HasLifecycleCallbacks]
class User implements UserInterface
{
#[Id]
#[GeneratedValue(strategy: 'CUSTOM')]
#[CustomIdGenerator(class: UuidGenerator::class)]
#[Column(type: 'string', length: 36, unique: true)]
private string $id;

#[Column(type: 'string', unique: true, nullable: false)]
public string $name;

#[Column(type: 'string', unique: true, nullable: false)]
public string $email;

#[Column(type: 'string', nullable: false)]
public string $password;

#[Column(type: 'boolean')]
public bool $active = true;

#[Column(type: 'datetime', nullable: false)]
public DateTime $createdAt;

#[Column(name: 'profile_photo_path', type: 'string', nullable: true)]
public ?string $profilePhotoPath = null;

#[ManyToOne(targetEntity: Tenant::class)]
#[JoinColumn(name: 'tenant_id', referencedColumnName: 'id', nullable: false)]
public TenantInterface $tenant;

// getter & setter

}`

When I run this following query

SELECT team, teamUser FROM App\Infrastructure\Entities\Team team LEFT JOIN team.teamUsers teamUser

Then I get the root entity (team) but with an empty TeamUser collection. I ran the actual SQL query and it returns the expected results and after some debugging I figured out that doctrine correctly creates the entities (Team, TeamUser) but it does not link them, so somehow it does not add the TeamUser entries to the ArrayCollection of the root entity.

Image
Image
Image

Am I doing something wrong with the mapping or do I oversee something?

PS: The doctrine command for schema validation also gives feedback, that the mapping files are correct.

Originally created by @AndreRudolph on GitHub (Mar 10, 2025). ### Bug Report Many To Many by using Pivot entity as One To Many <-> Many To One does not fill ArrayCollection with entities. | Q | A |-------------------------------------------- | ------ | Version | 3.3.2 #### Summary I have a N:M relation which I split up into One To Many <-> Many to One by adding a pivot entity. More precisely I have the pivot Entity TeamUsers which references both sides, the Team and the User. #### Current behavior I have these entities: `#[Entity] #[Table(name: 'teams')] #[HasLifecycleCallbacks] class Team implements TeamInterface { #[Id] #[GeneratedValue(strategy: 'CUSTOM')] #[CustomIdGenerator(class: UuidGenerator::class)] #[Column(type: 'string', length: 36, unique: true)] private string $id; #[Column(type: 'string', length: 255, nullable: false)] private string $name; #[ManyToOne(targetEntity: Tenant::class, cascade: ['remove'])] #[JoinColumn(name: 'tenant_id', referencedColumnName: 'id', nullable: false)] private TenantInterface $tenant; #[ManyToOne(targetEntity: User::class, cascade: ['remove'])] #[JoinColumn(name: 'owner_id', referencedColumnName: 'id', nullable: false)] private UserInterface $owner; #[OneToMany(targetEntity: TeamUser::class, mappedBy: 'team')] private Collection $teamUsers; #[Column(type: 'datetime', nullable: false)] public DateTimeInterface $createdAt; public function __construct() { $this->teamUsers = new ArrayCollection(); } public function getTeamUsers(): Collection { return $this->teamUsers; } public function setTeamUsers(Collection $teamUsers): TeamInterface { $this->teamUsers = $teamUsers; return $this; } public function addTeamUser(TeamUser $teamUser): self { if (!$this->teamUsers->contains($teamUser)) { $this->teamUsers->add($teamUser); $teamUser->setTeam($this); } return $this; } public function removeTeamUser(TeamUser $teamUser): self { if ($this->teamUsers->contains($teamUser)) { $this->teamUsers->removeElement($teamUser); if ($teamUser->getTeam() === $this) { $teamUser->setTeam(null); } } return $this; } }` ` #[Entity] #[Table(name: 'team_users')] #[HasLifecycleCallbacks] class TeamUser implements TeamUserInterface { #[Id] #[ManyToOne(targetEntity: User::class)] #[JoinColumn(name: 'user_id', referencedColumnName: 'id', nullable: false)] private UserInterface $user; #[Id] #[ManyToOne(targetEntity: Team::class, inversedBy: 'teamUsers')] #[JoinColumn(name: 'team_id', referencedColumnName: 'id', nullable: false)] private TeamInterface $team; #[Column(type: 'datetime', nullable: false)] private DateTimeInterface $joinedAt; // setters & getters }` ` #[Entity] #[Table(name: 'users')] #[HasLifecycleCallbacks] class User implements UserInterface { #[Id] #[GeneratedValue(strategy: 'CUSTOM')] #[CustomIdGenerator(class: UuidGenerator::class)] #[Column(type: 'string', length: 36, unique: true)] private string $id; #[Column(type: 'string', unique: true, nullable: false)] public string $name; #[Column(type: 'string', unique: true, nullable: false)] public string $email; #[Column(type: 'string', nullable: false)] public string $password; #[Column(type: 'boolean')] public bool $active = true; #[Column(type: 'datetime', nullable: false)] public DateTime $createdAt; #[Column(name: 'profile_photo_path', type: 'string', nullable: true)] public ?string $profilePhotoPath = null; #[ManyToOne(targetEntity: Tenant::class)] #[JoinColumn(name: 'tenant_id', referencedColumnName: 'id', nullable: false)] public TenantInterface $tenant; // getter & setter }` When I run this following query `SELECT team, teamUser FROM App\Infrastructure\Entities\Team team LEFT JOIN team.teamUsers teamUser` Then I get the root entity (team) but with an empty TeamUser collection. I ran the actual SQL query and it returns the expected results and after some debugging I figured out that doctrine correctly creates the entities (Team, TeamUser) but it does not link them, so somehow it does not add the TeamUser entries to the ArrayCollection of the root entity. ![Image](https://github.com/user-attachments/assets/28d4bb63-737f-48df-a017-b307d0a9503f) ![Image](https://github.com/user-attachments/assets/24289f00-8f20-4e08-a384-2492a1589140) ![Image](https://github.com/user-attachments/assets/3f9e77f4-0d12-4a23-9591-5cbea0df9188) Am I doing something wrong with the mapping or do I oversee something? PS: The doctrine command for schema validation also gives feedback, that the mapping files are correct.
admin closed this issue 2026-01-22 15:52:14 +01:00
Author
Owner

@AndreRudolph commented on GitHub (Mar 10, 2025):

After some more debugging I figured out that the issue was that the already created (and thus managed) entities did have not the same state as the retrieved ones by the query. A call $em->clear() worked and revealed that there was a stale state of the original entities.

@AndreRudolph commented on GitHub (Mar 10, 2025): After some more debugging I figured out that the issue was that the already created (and thus managed) entities did have not the same state as the retrieved ones by the query. A call `$em->clear()` worked and revealed that there was a stale state of the original entities.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#7486