Ein typisches PHP-Projekt startet klein: ein paar SQL-Queries im Controller, etwas Logik im Model, fertig. Spätestens wenn Features wachsen, ändern sich Anforderungen – und plötzlich muss eine Abfrage an fünf Stellen angepasst werden. Oder es soll statt MySQL eine API genutzt werden. Genau hier hilft ein Muster, das bewusst langweilig wirkt: Es ordnet den Zugriff auf Daten, ohne das Projekt zu verkomplizieren.
Das Repository Pattern sorgt dafür, dass Anwendungslogik (z. B. „User registrieren“) nicht mehr direkt weiß, wie Daten gespeichert oder geladen werden. Stattdessen fragt sie über eine klar definierte Schnittstelle: „Gib mir den User mit ID 5“ oder „Speichere diesen User“. Dadurch wird Code leichter testbar, besser lesbar und robuster gegen Änderungen am Speicherort.
Repository Pattern: Was damit gemeint ist
Ein Repository ist eine Klasse (oder ein Satz von Klassen), die als „Sammlung“ für Entities (fachliche Objekte wie User, Artikel, Bestellung) dient. Es versteckt die Details der Datenquelle: SQL, PDO, ORM, HTTP-API oder sogar eine Datei. Im Alltag bedeutet das: Controller und Services sprechen nicht mehr direkt mit der Datenbank, sondern mit dem Repository.
Warum „Datenzugriff trennen“ so viel ausmacht
In vielen Codebasen entsteht ein Mix aus SQL, Business-Regeln und Formatierung in derselben Methode. Das hat drei Folgen:
- Änderungen am Schema erzwingen Änderungen in vielen Dateien.
- Tests werden schwierig, weil für fast alles eine echte Datenbank nötig ist.
- Neue Datenquellen (z. B. zusätzliches Caching oder eine API) lassen sich kaum einbauen, ohne Logik zu duplizieren.
Mit einem Repository wandert SQL in genau einen Bereich. Der Rest des Systems nutzt Methoden mit sprechenden Namen.
Repository vs. „einfach ein Model“
In klassischen Active-Record-Ansätzen „kann“ das Model selbst speichern und laden. Das ist für kleine Projekte ok, vermischt aber oft Domain-Logik und Persistence (Speicherung). Ein Repository verfolgt die entgegengesetzte Idee: Die Entity bleibt möglichst „dumm“ und kennt weder SQL noch PDO. Das Repository kennt die Datenquelle – und stellt Methoden bereit, um Entities zu holen oder zu speichern.
Wann lohnt sich ein Repository wirklich – und wann nicht
Das Muster ist kein Muss. Es lohnt sich besonders dann, wenn Datenzugriff an mehreren Stellen gebraucht wird oder sich die Art der Speicherung ändern kann. Bei einer sehr kleinen Seite mit zwei Abfragen pro Request kann es zu viel Struktur sein.
Entscheidungsweg für die Praxis
- Wenn die App mehr als „ein CRUD-Screen“ ist:
- Wenn mehrere Features dieselben Abfragen benötigen → Repository hilft, Duplikate zu vermeiden.
- Wenn Logik unabhängig von SQL getestet werden soll → Repository hilft, Test-Doubles (Mocks/Stubs) einzusetzen.
- Wenn sich Datenquellen ändern können:
- Heute MySQL, morgen zusätzlich Redis-Cache oder externe API → Repository reduziert Umbauten.
- Wenn es wirklich nur eine Handvoll Abfragen gibt:
- Lieber sauber strukturierte Query-Klassen oder Funktionen nutzen und nicht „auf Teufel komm raus“ abstrahieren.
Eine sinnvolle Struktur: Entity, Interface, Repository
In PHP hat sich eine einfache Aufteilung bewährt:
- Entity: fachliche Datenstruktur, z. B.
Usermitid,email,passwordHash. - Repository Interface: beschreibt, welche Operationen möglich sind (z. B.
findById). - Repository Implementierung: konkrete Umsetzung, z. B. mit PDO und SQL.
Wichtig ist weniger die Ordnerstruktur, sondern die Richtung der Abhängigkeiten: Die Anwendung hängt am Interface, nicht an der Datenbanktechnik. Das passt gut zu sauberer Architektur, wie sie auch im Artikel zum Dependency Inversion Principle beschrieben wird.
Beispiel: Repository-Interface
Ein Interface hält Methoden knapp und fachlich. Statt getUserRowByEmail lieber findByEmail. Das beschreibt das Ziel, nicht den technischen Weg.
findById(int $id): ?UserfindByEmail(string $email): ?Usersave(User $user): void
Ob save intern ein INSERT oder UPDATE ist, darf die Anwendung nicht interessieren.
Implementierung mit PDO: Schritt für Schritt
Viele Projekte nutzen PDO. Das passt gut, weil es bewusst nah an SQL bleibt, aber trotzdem vorbereitete Statements (Prepared Statements) ermöglicht.
1) Entity bewusst schlicht halten
Eine Entity sollte nicht wissen, wie sie gespeichert wird. Sinnvoll sind Konstruktor, Getter und ggf. kleine Validierung (z. B. „E-Mail darf nicht leer sein“). Komplexe Eingabeprüfung gehört eher vor das Speichern – siehe auch Input-Validierung im Backend.
2) Repository baut Entities aus Datenbankzeilen
Der zentrale Job eines Repositories ist das Übersetzen: DB-Zeile → Entity und Entity → DB-Operation. Damit dieser Teil nicht ausufert, hilft ein klarer, wiederverwendbarer Mapper-Ansatz. Das kann eine private Methode sein wie mapRowToUser.
3) Save-Strategie definieren (Insert vs. Update)
Damit save klar bleibt, braucht es eine Regel. Häufig: Wenn id vorhanden ist, dann UPDATE, sonst INSERT. Alternativ kann man zwei Methoden anbieten (insert/update) – das ist manchmal transparenter, wenn IDs extern vergeben werden.
4) Fehler sichtbar machen, aber sinnvoll
PDO wirft bei Fehlern Exceptions (wenn korrekt konfiguriert). Diese sollten nicht verschluckt werden. In größeren Anwendungen lohnt sich eine kleine Übersetzung in fachliche Fehler, z. B. „E-Mail existiert schon“. Dafür ist es hilfreich, die Datenbank über Constraints abzusichern; dazu passt SQL Constraints verstehen.
Praktischer Ablauf: so bleibt der Code im Alltag sauber
- Pro Entity ein Repository, aber nur wenn wirklich Abfragen gebraucht werden.
- Methoden nach Fachsprache benennen:
findActiveByEmailist besser alsselectWhereStatus. - SQL nur im Repository (oder in klaren Query-Hilfsklassen), nicht im Controller.
- Prepared Statements verwenden, um SQL-Injection zu verhindern; vertiefend: Prepared Statements in PHP.
- Repository-Methoden klein halten: lieber zwei gezielte Methoden als eine „Super-Suche“ mit zehn optionalen Parametern.
Typische Stolperfallen und wie sie vermieden werden
In der Praxis scheitert das Muster selten an der Idee, sondern an der Umsetzung. Diese Punkte sind die häufigsten Ursachen für Frust:
Zu generische Repositories
Ein „BaseRepository“ mit findAll, findBy, deleteWhere wirkt zunächst praktisch, wird aber schnell zur Dumping-Zone. Abfragen werden dann als Arrays beschrieben und verteilen sich wieder im Code. Besser: gezielte Methoden, die eine fachliche Absicht ausdrücken.
Repository gibt Arrays statt Entities zurück
Arrays sind flexibel, aber sie verschieben Verantwortung: Jede aufrufende Stelle muss wissen, wie Felder heißen, welche optional sind und welche Typen gelten. Entities bündeln diese Regeln. Wenn ein Projekt bewusst „array-first“ ist (z. B. reine API-Response-Schicht), kann das ok sein – dann sollte aber konsequent ein DTO (Data Transfer Object) genutzt werden.
Business-Logik im Repository
Ein Repository sollte keine fachlichen Entscheidungen treffen wie „User darf nicht gelöscht werden, wenn …“. Das gehört in einen Service (Anwendungslogik). Das Repository soll Daten laden/speichern und ggf. technische Details kapseln (z. B. Transaktionen starten, wenn es wirklich nur Persistence betrifft).
Vergleich: Repository Pattern vs. direkte PDO-Queries
| Aspekt | Repository Pattern | Direkte Queries im Code |
|---|---|---|
| Änderungen am Schema | Meist nur im Repository anzupassen | Oft verteilt über mehrere Dateien |
| Tests ohne Datenbank | Gut möglich über Interfaces und Test-Doubles | Schwer, weil SQL überall steckt |
| Lesbarkeit | Methoden drücken Fachabsicht aus | SQL muss jedes Mal „mitgelesen“ werden |
| Startaufwand | Mehr Struktur am Anfang | Schneller Einstieg |
Tests: Repository austauschen, ohne die App umzubauen
Der große Gewinn entsteht, wenn die Anwendung nicht mehr an PDO hängt. Stattdessen hängt sie am Interface. In Tests kann ein InMemory-Repository eingesetzt werden: eine einfache Implementierung, die Entities in einem Array hält. So lassen sich Services testen, ohne Datenbank und ohne SQL-Fixtures.
Ein InMemory-Repository als Test-Double
Für Unit-Tests reicht oft:
- ein Array als Speicher
- eine ID-Vergabe (z. B. hochzählend)
- Methoden wie
findById/savemit minimaler Logik
Wichtig: Das InMemory-Repository sollte das Interface erfüllen, aber nicht versuchen, die Datenbank 1:1 zu simulieren. Es soll Tests beschleunigen, nicht neue Komplexität schaffen.
Saubere Erweiterungen: Caching und Transaktionen ohne Chaos
Wenn das Repository die Grenze zur Datenquelle ist, lassen sich Erweiterungen gezielt einbauen:
- Datenzugriff mit Cache: Ein Decorator-Repository kann zuerst im Cache nachsehen und bei Miss aus der DB laden.
- Transaktionen: Wenn mehrere Repositories in einem Use Case zusammenarbeiten, ist oft ein Service der bessere Ort, um eine Transaktion zu steuern. Hintergrund dazu bietet Transactions in SQL.
Der Grundgedanke bleibt: Die Anwendung beschreibt Absichten, die Repository-Schicht kümmert sich um Technik.
Quellen
- Keine externen Quellen angegeben.

