Repositories and Queries
Repositories and Queries
~15 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
With the first table in the database, let's finally connect controllers AND real data – via Doctrine's repository pattern, which make:entity already automatically created for us in chapter 19.
The generated repository
<?php
declare(strict_types=1);
namespace App\Repository;
use App\Entity\Project;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Project>
*/
class ProjectRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Project::class);
}
}ServiceEntityRepository is itself a Symfony SERVICE (block 6 explains this principle in depth) – we can inject it into a controller via autowiring EXACTLY like any other service, NO manual registration needed.
Built-in finder methods
use App\Repository\ProjectRepository;
#[Route('/projects', name: 'project_index', methods: ['GET'])]
public function index(ProjectRepository $projectRepository): Response
{
$projects = $projectRepository->findAll();
return $this->render('project/index.html.twig', [
'projects' => $projects,
]);
}
#[Route('/projects/{id}', name: 'project_show', requirements: ['id' => '\d+'])]
public function show(int $id, ProjectRepository $projectRepository): Response
{
$project = $projectRepository->find($id);
if ($project === null) {
throw $this->createNotFoundException('Project not found.');
}
return $this->render('project/show.html.twig', [
'project' => $project,
]);
}find($id) looks up by primary key, findAll() loads ALL rows. createNotFoundException() (from AbstractController) throws a NotFoundHttpException, which Symfony AUTOMATICALLY turns into a real 404 error page.
findBy() and findOneBy()
// All projects, sorted alphabetically:
$projects = $projectRepository->findBy([], ['name' => 'ASC']);
// Only ONE project by exact name:
$project = $projectRepository->findOneBy(['name' => 'Website Relaunch']);Doctrine generates these methods "magically" from the field name – there's also the shorthand findOneByName('Website Relaunch'), but the array syntax above is MORE EXPLICIT and therefore preferred here.
Saving a project
use Doctrine\ORM\EntityManagerInterface;
#[Route('/projects/new', name: 'project_new', methods: ['GET', 'POST'])]
public function new(Request $request, EntityManagerInterface $entityManager): Response
{
$form = $this->createForm(ProjectType::class, new Project());
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$project = $form->getData();
$entityManager->persist($project);
$entityManager->flush();
$this->addFlash('success', 'Project created successfully!');
return $this->redirectToRoute('project_index');
}
return $this->render('project/new.html.twig', ['form' => $form]);
}Two separate steps, ALWAYS in this order: persist() marks the entity as "should be saved" (NO database action yet!), flush() ACTUALLY writes ALL changes marked since the last flush() to the database – in ONE transaction, even if several entities were changed.
Achtung: A COMMON beginner mistake: calling persist() but forgetting flush() – the entity then NEVER ends up in the database, WITHOUT any error occurring. Since createForm(ProjectType::class, new Project()) gets a REAL entity as its second argument (instead of a *Data class as in chapters 15/16), $form->getData() now populates this entity instance DIRECTLY.
Updating and deleting a project
// Update: call NOTHING new, just change the values - Doctrine detects changes automatically
$project->setName('New Name');
$entityManager->flush(); // NO renewed persist() needed for already-managed entities
// Delete:
$entityManager->remove($project);
$entityManager->flush();Tipp: For entities ALREADY loaded from the database (and thus "managed" by Doctrine), a simple flush() is enough to update – Doctrine automatically compares the current state against the state saved at load time ("unit of work") and generates ONLY the actually needed UPDATE statements.