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-bundleThe 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:
<?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:validateTipp: 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.