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

Die Task-Entity mit ManyToOne zu Project

Die Task-Entity mit ManyToOne zu Project

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

GENAU das Domänenmodell aus der Symfony-Schulung: EIN Project hat VIELE Tasks. Diese Beziehung wird jetzt als ECHTE Doctrine-Assoziation abgebildet – UND automatisch Teil der API.

Die Task-Entity anlegen

docker compose exec php bin/console make:entity Task
api/src/Entity/Task.php
<?php

declare(strict_types=1);

namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use App\Repository\TaskRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Validator\Constraints as Assert;

#[ApiResource(
    normalizationContext: ['groups' => ['task:read']],
    denormalizationContext: ['groups' => ['task:write']]
)]
#[ORM\Entity(repositoryClass: TaskRepository::class)]
class Task
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    #[Groups(['task:read'])]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    #[Assert\NotBlank]
    #[Groups(['task:read', 'task:write'])]
    private string $title = '';

    #[ORM\Column]
    #[Groups(['task:read', 'task:write'])]
    private bool $done = false;

    #[ORM\ManyToOne(inversedBy: 'tasks')]
    #[ORM\JoinColumn(nullable: false)]
    #[Groups(['task:read', 'task:write'])]
    private ?Project $project = null;

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

    public function getTitle(): string
    {
        return $this->title;
    }

    public function setTitle(string $title): static
    {
        $this->title = $title;

        return $this;
    }

    public function isDone(): bool
    {
        return $this->done;
    }

    public function setDone(bool $done): static
    {
        $this->done = $done;

        return $this;
    }

    public function getProject(): ?Project
    {
        return $this->project;
    }

    public function setProject(?Project $project): static
    {
        $this->project = $project;

        return $this;
    }
}

#[ORM\ManyToOne(inversedBy: 'tasks')] ist GENAU dieselbe Annotation wie in der Symfony-Schulung (Kapitel 30) – inversedBy verweist auf eine Property tasks, die in Kapitel 38 auf Project ergänzt wird.

Migration erstellen

docker compose exec php bin/console make:migration
docker compose exec php bin/console doctrine:migrations:migrate --no-interaction

Eine Task erstellen

curl -k -X POST https://localhost/api/tasks \
  -H 'Content-Type: application/json' \
  -d '{"title": "Wireframes erstellen", "project": "/api/projects/1"}'

Achtung: "project": "/api/projects/1" – KEIN nackte Zahl 1, sondern der VOLLSTÄNDIGE API-Pfad als STRING. Kapitel 40 erklärt AUSFÜHRLICH, warum API Platform Beziehungen über solche IRIs (Internationalized Resource Identifiers) abbildet, statt über rohe Datenbank-IDs.

Tipp: #[ORM\JoinColumn(nullable: false)] erzwingt, dass JEDE Task ZWINGEND zu einem Project gehört – ein POST OHNE project-Feld schlägt mit 422 fehl, GENAU wie ein fehlendes #[Assert\NotBlank]-Feld aus Kapitel 19.