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

Doctrine Basics: Defining Entities

Doctrine Basics: Defining Entities

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

Finally, our task manager gets a REAL database. Doctrine is Symfony's standard ORM (object-relational mapper) – PHP classes turn into database tables, WITHOUT writing a single line of SQL by hand.

Installing Doctrine

composer require symfony/orm-pack
composer require --dev symfony/maker-bundle

The orm-pack recipe (chapter 6) automatically sets up config/packages/doctrine.yaml and DATABASE_URL in .env – EXACTLY the value we already prepared in chapter 2.

Generating the Project entity

php bin/console make:entity Project
# Asked interactively:
#   name (string, 255, nullable: no)
#   description (text, nullable: yes)
#   createdAt (datetime_immutable, nullable: no)

make:entity asks interactive questions and generates the complete PHP class from them – a deliberately guided process, which we'll follow through here manually to understand WHAT gets created:

src/Entity/Project.php
<?php

declare(strict_types=1);

namespace App\Entity;

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

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

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

    #[ORM\Column(type: 'text', nullable: true)]
    private ?string $description = null;

    #[ORM\Column]
    private \DateTimeImmutable $createdAt;

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

    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;
    }

    public function getDescription(): ?string
    {
        return $this->description;
    }

    public function setDescription(?string $description): static
    {
        $this->description = $description;

        return $this;
    }

    public function getCreatedAt(): \DateTimeImmutable
    {
        return $this->createdAt;
    }
}

The attributes in detail

  • #[ORM\Entity] – marks the class as a Doctrine entity, i.e. "gets stored in the database".
  • #[ORM\Id] – this field is the primary key.
  • #[ORM\GeneratedValue] – the database assigns the value automatically (AUTO_INCREMENT/SEQUENCE).
  • #[ORM\Column] – this PHP property becomes a database column. Without explicit options, the type is inferred from the PHP type declaration.

The fluent setter pattern: return $this

setName() returns $this instead of void – allows CHAINED calls:

$project = (new Project())
    ->setName('Website Relaunch')
    ->setDescription('Complete redesign of the company website');

getId() has NO setter – the ID gets assigned EXCLUSIVELY by the database, never set manually. The nullable return type ?int reflects that a NEW, not-yet-persisted entity has NO ID yet.

Validating the mapping

php bin/console doctrine:schema:validate

Tipp: Useful AFTER every manual change to an entity, to make sure Doctrine's internal metadata (derived from the attributes) is consistent, BEFORE a migration is created (chapter 20).

The remaining entities, a preview

The coming chapters create, analogously: User (chapter 26, with security interfaces), Task (chapter 22, with a relationship to Project), Comment (chapters 22/23) – EXACTLY the domain model from chapter 5.