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

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

src/Repository/ProjectRepository.php
<?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

src/Controller/ProjectController.php
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.