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

Controller schreiben

Controller schreiben

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

Ein Controller ist im Kern eine EINFACHE PHP-Methode: nimmt einen Request entgegen (implizit oder explizit) und gibt eine Response zurück. AbstractController stellt dabei nützliche Hilfsmethoden bereit, die wir jetzt kennenlernen.

AbstractController: nützliche Hilfsmethoden

Symfony-Controller erben üblicherweise von AbstractController – NICHT verpflichtend (jede aufrufbare Funktion funktioniert als Controller), aber es erspart wiederkehrenden Code:

  • $this->render(...) – ein Twig-Template rendern (Kapitel 13).
  • $this->redirectToRoute(...) – zu einer anderen Route weiterleiten (Kapitel 11).
  • $this->json(...) – eine JSON-Response erzeugen (Kapitel 11).
  • $this->getUser() – den eingeloggten Nutzer abrufen (Block 5).
  • $this->addFlash(...) – eine Flash-Nachricht setzen (Kapitel 12).

Vorerst: mit hartcodierten Beispieldaten arbeiten

Doctrine folgt erst in Block 4 – bis dahin arbeiten wir bewusst mit einem simplen, hartcodierten Array, um uns auf Routing/Controller/Twig konzentrieren zu können, ohne gleichzeitig eine Datenbank zu benötigen:

src/Controller/ProjectController.php
<?php

declare(strict_types=1);

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class ProjectController extends AbstractController
{
    private const array BEISPIEL_PROJEKTE = [
        ['id' => 1, 'name' => 'Website-Relaunch'],
        ['id' => 2, 'name' => 'Mobile App'],
        ['id' => 3, 'name' => 'Interne Tools'],
    ];

    #[Route('/projects', name: 'project_index', methods: ['GET'])]
    public function index(): Response
    {
        $ausgabe = 'Projekte:';
        foreach (self::BEISPIEL_PROJEKTE as $projekt) {
            $ausgabe .= sprintf("\n- %s", $projekt['name']);
        }

        return new Response($ausgabe, Response::HTTP_OK, [
            'Content-Type' => 'text/plain',
        ]);
    }
}

Response::HTTP_OK ist eine benannte Konstante statt der "magischen Zahl" 200 – Symfonys Response-Klasse definiert Konstanten für ALLE gängigen HTTP-Status-Codes, was Lesbarkeit deutlich verbessert.

Abhängigkeiten per Constructor Property Promotion

Braucht ein Controller einen Service (Block 6 behandelt das systematisch), wird dieser per Autowiring automatisch in den Constructor injiziert – wir müssen NICHTS manuell registrieren:

src/Controller/ProjectController.php
// ... use-Statements wie oben ...
use Psr\Log\LoggerInterface;

class ProjectController extends AbstractController
{
    public function __construct(
        private readonly LoggerInterface $logger,
    ) {
    }

    #[Route('/projects', name: 'project_index', methods: ['GET'])]
    public function index(): Response
    {
        $this->logger->info('Projektliste aufgerufen');

        return new Response('Projekte werden geladen...');
    }
}

LoggerInterface ist bereits ab der Standard-Installation als Service verfügbar (Monolog). Kapitel 32-34 erklären das Autowiring-Prinzip dahinter im Detail – für jetzt reicht: JEDE Typ-Deklaration im Constructor, für die Symfony eine passende Service-Definition kennt, wird AUTOMATISCH aufgelöst.

Mehrere Actions pro Controller

Ein Controller darf (und sollte typischerweise) MEHRERE zusammengehörige Routen bündeln – für unser Projekt: eine Klasse pro Domänen-Konzept, mehrere Methoden ("Actions") pro Klasse:

class ProjectController extends AbstractController
{
    #[Route('/projects', name: 'project_index', methods: ['GET'])]
    public function index(): Response { /* ... */ }

    #[Route('/projects/new', name: 'project_new', methods: ['GET', 'POST'])]
    public function new(): Response { /* ... */ }

    #[Route('/projects/{id}', name: 'project_show', methods: ['GET'])]
    public function show(int $id): Response { /* ... */ }
}

Tipp: #[Route('/projects', name: 'project_')] lässt sich zusätzlich auf KLASSEN-Ebene setzen, um allen Actions einen gemeinsamen Pfad-/Namens-Präfix zu geben – praktisch, sobald ein Controller wächst. Wir führen das ein, sobald ProjectController mehrere Routen hat.