Eigene Services erstellen
Eigene Services erstellen
~15 Min. Lesezeit Zuletzt aktualisiert am 8. August 2026
Zeit, unsere ERSTE eigene Geschäftslogik in einen dedizierten Service auszulagern, statt sie im Controller zu belassen – die Statistik-Berechnung aus Kapitel 24.
Warum Geschäftslogik aus dem Controller gehört
Controller SOLLTEN dünn bleiben: Anfrage entgegennehmen, an die richtige Logik delegieren, Antwort zurückgeben. Komplexere Berechnungen, die MEHRERE Repositories kombinieren oder wiederverwendbar sein sollen (z. B. auch von einem Console Command in Block 7 aus), gehören in einen EIGENEN Service.
Einen Service generieren
php bin/console make:service ProjectStatistikService<?php
declare(strict_types=1);
namespace App\Service;
use App\Entity\Project;
use App\Repository\TaskRepository;
class ProjectStatistikService
{
public function __construct(
private readonly TaskRepository $taskRepository,
) {
}
/**
* @return array{offen: int, in_arbeit: int, erledigt: int, fortschritt: float}
*/
public function berechneStatistik(Project $project): array
{
$rohStatistik = $this->taskRepository->zaehleAufgabenNachStatus($project);
$anzahlProStatus = ['offen' => 0, 'in_arbeit' => 0, 'erledigt' => 0];
foreach ($rohStatistik as $zeile) {
$anzahlProStatus[$zeile['status']] = $zeile['anzahl'];
}
$gesamt = array_sum($anzahlProStatus);
$fortschritt = $gesamt > 0
? round(($anzahlProStatus['erledigt'] / $gesamt) * 100, 1)
: 0.0;
return [
...$anzahlProStatus,
'fortschritt' => $fortschritt,
];
}
}KEINE Attribute, KEINE spezielle Basisklasse nötig – JEDE gewöhnliche PHP-Klasse mit typisierten Constructor-Parametern ist AUTOMATISCH als Service via Autowiring nutzbar (Symfonys "Services sind standardmäßig autowired und autoconfigured"-Konvention aus config/services.yaml, dazu mehr in Kapitel 34).
Den Service im Controller nutzen
use App\Service\ProjectStatistikService;
#[Route('/projects/{id}', name: 'project_show', requirements: ['id' => '\d+'])]
public function show(
int $id,
ProjectRepository $projectRepository,
ProjectStatistikService $statistikService,
): Response {
$projekt = $projectRepository->find($id);
if ($projekt === null) {
throw $this->createNotFoundException();
}
$this->denyAccessUnlessGranted(ProjectVoter::VIEW, $projekt);
return $this->render('project/show.html.twig', [
'projekt' => $projekt,
'statistik' => $statistikService->berechneStatistik($projekt),
]);
}GENAU dasselbe Autowiring-Prinzip wie bei LoggerInterface oder ProjectRepository – der Controller weiß NICHT, wie ProjectStatistikService intern funktioniert, nur DASS er berechneStatistik() aufrufen kann.
Services können andere Services nutzen
Autowiring funktioniert GENAUSO zwischen Services – ein Service kann im eigenen Constructor beliebig viele ANDERE Services anfordern, GENAU wie ein Controller. ProjectStatistikService selbst könnte z. B. später einen CacheInterface-Service injiziert bekommen (Kapitel 45), OHNE dass sich der aufrufende Controller-Code ändern müsste.
Faustregel: wann lohnt sich ein eigener Service?
| Situation | Empfehlung |
|---|---|
| Einfache, EINMALIGE Logik in EINER Controller-Methode | Direkt im Controller belassen – ein eigener Service wäre unnötige Indirektion. |
| Logik, die von MEHREREN Stellen gebraucht wird (Controller UND Console Command UND Event Listener) | In einen Service auslagern – EIN Ort für die Wahrheit, statt Code-Duplikation. |
| Komplexe Berechnung/Orchestrierung MEHRERER Repositories | In einen Service auslagern – hält den Controller lesbar und die Logik isoliert testbar (Block 7). |
Tipp: Ein guter Namenskonvention-Hinweis: Service-Klassennamen enden oft auf Service, Manager oder beschreiben direkt ihre Aufgabe (z. B. ProjectStatistikBerechner) – WICHTIGER als der exakte Namensstil ist KONSISTENZ innerhalb eines Projekts.