Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Beziehungen: ManyToMany

Beziehungen: ManyToMany

~16 Min. Lesezeit Zuletzt aktualisiert am 8. August 2026

Ein Nutzer kann Mitglied MEHRERER Projekte sein, ein Projekt hat MEHRERE Mitglieder – eine klassische ManyToMany-Beziehung, die eine EIGENE, unsichtbare Verbindungstabelle in der Datenbank braucht.

Vorgriff: eine minimale User-Entity

Die VOLLSTÄNDIGE User-Entity mit Security-Interfaces entsteht erst in Kapitel 26 – für dieses Kapitel reicht ein minimales Grundgerüst:

src/Entity/User.php
<?php

declare(strict_types=1);

namespace App\Entity;

use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity(repositoryClass: UserRepository::class)]
class User
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 180)]
    private string $name = '';

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function setName(string $name): static
    {
        $this->name = $name;

        return $this;
    }
}

Die ManyToMany-Beziehung in Project

src/Entity/Project.php
class Project
{
    // ... bisherige Felder ...

    /**
     * @var Collection<int, User>
     */
    #[ORM\ManyToMany(targetEntity: User::class, inversedBy: 'projekte')]
    private Collection $mitglieder;

    public function __construct()
    {
        $this->createdAt = new \DateTimeImmutable();
        $this->tasks = new ArrayCollection();
        $this->mitglieder = new ArrayCollection();
    }

    /**
     * @return Collection<int, User>
     */
    public function getMitglieder(): Collection
    {
        return $this->mitglieder;
    }

    public function addMitglied(User $user): static
    {
        if (!$this->mitglieder->contains($user)) {
            $this->mitglieder->add($user);
        }

        return $this;
    }

    public function removeMitglied(User $user): static
    {
        $this->mitglieder->removeElement($user);

        return $this;
    }
}

Die inverse Seite in User

src/Entity/User.php
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;

class User
{
    // ... bisherige Felder ...

    /**
     * @var Collection<int, Project>
     */
    #[ORM\ManyToMany(targetEntity: Project::class, mappedBy: 'mitglieder')]
    private Collection $projekte;

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

    /**
     * @return Collection<int, Project>
     */
    public function getProjekte(): Collection
    {
        return $this->projekte;
    }
}

Bei ManyToMany gibt es KEINE #[ORM\JoinColumn] auf der Owning Side – stattdessen legt Doctrine eine EIGENE Verbindungstabelle an, standardmäßig benannt nach BEIDEN Entity-Namen: project_user.

Migration für die ManyToMany-Beziehung

php bin/console make:migration
// Generiertes SQL (Ausschnitt):
CREATE TABLE project_user (
    project_id INT NOT NULL,
    user_id INT NOT NULL,
    PRIMARY KEY(project_id, user_id)
);
ALTER TABLE project_user ADD CONSTRAINT FK_... FOREIGN KEY (project_id) REFERENCES project (id);
ALTER TABLE project_user ADD CONSTRAINT FK_... FOREIGN KEY (user_id) REFERENCES "user" (id);

Der ZUSAMMENGESETZTE Primärschlüssel (project_id, user_id) stellt sicher, dass dieselbe Mitgliedschaft NICHT doppelt gespeichert werden kann – eine Datenbank-Ebenen-Garantie, die über reine Anwendungslogik hinausgeht.

Mitglieder hinzufügen

$projekt->addMitglied($user);
$entityManager->flush();

// Von der ANDEREN Seite aus funktioniert es NICHT automatisch synchronisiert -
// bei ManyToMany OHNE eigene Zwischen-Entity ist das in der Praxis meist unproblematisch,
// da man üblicherweise NUR über die Owning Side (hier: Project) hinzufügt.

Achtung: Anders als bei OneToMany/ManyToOne in Kapitel 22 synchronisiert addMitglied() hier NICHT automatisch die andere Seite ($user->getProjekte() würde ohne einen erneuten Datenbank-Reload NICHT aktualisiert). In der Praxis unproblematisch, solange Sie KONSEQUENT über EINE Seite hinzufügen – bei Bedarf lässt sich die Synchronisierung analog zu Kapitel 22 selbst ergänzen.

Welche Seite ist Owning Side bei ManyToMany?

Bei ManyToMany ist die Wahl WILLKÜRLICH (anders als bei ManyToOne, wo die Seite mit dem Fremdschlüssel zwingend die Owning Side ist) – Faustregel: die Seite, über die Sie in der Praxis HÄUFIGER Mitglieder hinzufügen, wird Owning Side (inversedBy), die andere Seite Inverse Side (mappedBy). Für unseren Aufgaben-Manager: Projekte verwalten typischerweise ihre eigenen Mitglieder, deshalb ist Project hier die Owning Side.

Damit sind die vier Kern-Entities aus Kapitel 5 (User, Project, Task, Comment folgt in Kapitel 24) vollständig über echte Beziehungen verbunden – Kapitel 24 nutzt diese Struktur für fortgeschrittenere Abfragen mit dem Query Builder.