Doctrine: Many-to-One Beziehungen definieren

Dieser Beitrag ist kein „How-To“ oder „Guide“ in diesem Sinne, sondern eher meine erste Erfahrung mit einer ORM und ihren Wegen, eine N-zu-1 bzw. 1-zu-N Beziehung zu definieren. Als ORM findet man vor allem Doctrine in einem Symfony Projekt vor und ich war am Anfang doch sehr verwirrt, wenn es um das Mapping zwischen SQL und PHP Klassen ging. Hier also die Geschichte für das Definieren von Many-to-One Beziehungen mit Doctrine.

Das Problem kennzeichnen

In meinem kleinen Testprojekt habe ich versucht, Notizen mit möglichst vielen Meta-Informationen zu verknüpfen. Dazu gehören z.B. ein Fertigstellungsdatum, Fortschritt in Prozent oder eine Priorität. Außerdem sollten Notizen noch nach Kategorien sortiert werden können.

Kategorien entsprechen einer Typischen N-zu-1 Verbindung, mehrere Notizen können einer Kategorie zugeordnet werden. Da ich auch die Kategorien verwalten möchte, mussten diese dynamisch zugeordnet werden. Also ist erst mal eine Kombobox (<select>) sinnvoll.

So eine Kategorie brauchte nicht viel: Eine Id und einen Namen. Die Id erledigt Doctrine automatisch für uns. Die fertige Entität sieht dann so aus:

/**
 * @ORM\Table()
 */
class Category
{
    /**
     * @var int|null
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     * @ORM\Id()
     */
    private $id = null;

    /**
     * @var string|null
     *
     * @ORM\Column(name="name", type="string", length=255, unique=true)
     */
    private $name = null;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="createdate", type="datetime")
     */
    private $created = null;


    /**
     * Category constructor.
     */
    public function __construct()
    {
        $this->created = new \DateTime('now');
    }

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

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

    public function setName($name)
    {
            $this->name = $name;
            return $this;
    }

    public function getCreated()
    {
        return $this->created;
    }
}

Soweit so gut, trotzdem schon eine ganze Menge Text für das bisschen Funktionalität.

Jetzt noch die Verknüpfung einfügen. Mit der entsprechenden Annotation erkennt Symfony die Kategorie auch sofort als eigenes Objekt, welches wiederum ein Parameter einer Notiz ist.

/**
 * @ORM\Table()
 */
class Note
{
    // ...

    /**
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Category")
     * @ORM\JoinColumn(name="category_id", referencedColumnName="id")
     */
    private $category = null;

    // ...
}

Das Formular

Ein separates Formular zum Erstellen und Bearbeiten von Kategorien zu schreiben war auch einfach. Der Clou war dann eher die Auswahl einer der erstellten Kategorien in dem Notiz-Formular…

Für das Formular-Element in der Notiz verwendet man den EntityType, der zu einer Kombobox, mehreren Checkboxen oder einer Mehrfachauswahl mutieren kann.

use AppBundle\Entity\Category;
use AppBundle\Entity\Note;

class NoteForm extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array                $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name', TextType::class, [
                'label' => 'Name der Kategorie',
                'required' => true,
            ])
            ->add('category', EntityType::class, [
                'label' => 'Kategorie',
                'choice_label' => 'name',   // name of the field or empty to call `__toString()` of Entity
                'class' => Category::class,
                'query_builder' => function(EntityRepository $er) {
                    return $er->createQueryBuilder('c')
                            ->orderBy('c.name', 'ASC')
                    ;
                },
            ])
            // ...
        ;
    }

    // ...
}

Um eine Kombobox zur Auswahl einer einzelnen Kategorie in das Formular zu bringen, braucht man nun entweder eine __toString() Methode, die einen sprechenden Namen der Kategorie ausgibt oder man setzt das Feld choice_label im Formular mit dem zu lesenden Variablennamen.

Fertig. Die Id der Kategorie wird beim Absenden des Formulars übergeben und von Doctrine wieder zu einem Objekt zusammengesetzt. Danach kann im Controller mit der Vollständigen Entität gearbeitet werden.

Die 1-zu-N Beziehung vervollständigen

Vielleicht ist dem einen oder anderen aufgefallen, dass diese Beziehung bisher nur einseitig definiert ist. Wir können von der Notiz auf die Kategorie zugreifen, aber was ist mit der umgekehrten Richtung? Auch dies ist recht einfach zu erledigen. Dazu ergänzen wir beide Entitäten wie folgt:

class Category
{
    // ...

    /**
     * @var Collection
     *
     * @ORM\OneToMany(targetEntity="AppBundle\Entity\Note", cascade={"persist", "remove"}, mappedBy="category")
     */
    private $notes;

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

    public function getNotes()
    {
        return $this->notes;
    }

    // ...
}

class Note
{
    // ...

    /**
     * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Category", cascade={"persist"}, inversedBy="notes")
     * @ORM\JoinColumn(name="category_id", referencedColumnName="id")
     */
    private $category = null;

    // ...
}

In der Kategorie wurden inverse Notizen hinzugefügt. Eine einzelne Kategorie zeigt uns somit alle Notizen, die diese Kategorie ausgewählt haben. Man achte dabei auf die Attribute inversedBy und mappedBy, die Doctrine die Verbindung der Attribute mitteilt. Außerdem wird hier das Cascading verwendet: Mit „persist“ hat das Persistieren in Doctrine auch Auswirkungen auf die untergeordneten Elemente, wenn nur das Übergeordnete gespeichert wird. Genauso würden mit „remove“ rückwärtig alle verknüpften Notizen gelöscht, wenn eine Kategorie aus der Datenbank entfernt wird.

So viel zu meinen ersten Erfahrungen mit Doctrine und darin Many-to-One Beziehungen zu definieren. Dies war nur ein kleiner Ausschnitt von allen Möglichkeiten, die Symfony und Doctrine für die Verwaltung von Datenbanken mit sich bringen. Da kann also noch einiges passieren, auch in diesem Blog!

Kommentar hinterlassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert