Menu
Coddy logo textTech

Repository-Muster

Teil des Abschnitts Objektorientierte Programmierung der PHP-Journey von Coddy — Lektion 81 von 91.

Das Repository-Pattern ist ein Entwurfsmuster, das zwischen der Geschäftslogik Ihrer Anwendung und der Datenspeicherung vermittelt. Es bietet eine kollektionsartige Schnittstelle für den Zugriff auf Domänenobjekte und verbirgt die Details darüber, wie Daten tatsächlich gespeichert oder abgerufen werden.

Stellen Sie sich ein Repository als eine spezialisierte Sammlung vor, die weiß, wie Objekte persistiert und abgerufen werden. Ihr Anwendungscode fragt das Repository nach einem Benutzer anhand der ID oder speichert ein neues Produkt, ohne zu wissen, ob die Daten in einer Datenbank, einer Datei oder einer API liegen. Diese Trennung macht Ihren Code testbarer und flexibler.

<?php
interface UserRepositoryInterface {
    public function find(int $id): ?User;
    public function findAll(): array;
    public function save(User $user): void;
    public function delete(int $id): void;
}

class User {
    public function __construct(
        public int $id,
        public string $name,
        public string $email
    ) {}
}

Die Repository-Implementierung übernimmt den eigentlichen Speichermechanismus:

<?php
class InMemoryUserRepository implements UserRepositoryInterface {
    private array $users = [];
    
    public function find(int $id): ?User {
        return $this->users[$id] ?? null;
    }
    
    public function findAll(): array {
        return array_values($this->users);
    }
    
    public function save(User $user): void {
        $this->users[$user->id] = $user;
    }
    
    public function delete(int $id): void {
        unset($this->users[$id]);
    }
}

$repo = new InMemoryUserRepository();
$repo->save(new User(1, "Alice", "alice@example.com"));
$repo->save(new User(2, "Bob", "bob@example.com"));

echo $repo->find(1)->name . "\n";
echo count($repo->findAll());

Ausgabe:

Alice
2

Die Schönheit dieses Entwurfsmusters liegt darin, dass Sie Implementierungen austauschen können, ohne Ihre Geschäftslogik zu ändern. Müssen Sie von der In-Memory-Speicherung zu einer Datenbank wechseln? Erstellen Sie eine neue Repository-Klasse, die dasselbe Interface implementiert. Ihr Anwendungscode bleibt unberührt, da er vom Interface und nicht von der konkreten Implementierung abhängt.

challenge icon

Aufgabe

Einfach

Lassen Sie uns ein Produktinventarsystem mit dem Repository-Pattern erstellen. Sie werden eine saubere Trennung zwischen Ihren Domänenobjekten und deren Speicherung schaffen, sodass Ihr Anwendungscode mit Produkten arbeiten kann, ohne die Details der Speicherung zu kennen.

Sie werden Ihren Code über vier Dateien organisieren:

  • Product.php — Erstellen Sie eine Product-Klasse, die Artikel in Ihrem Inventar repräsentiert. Jedes Produkt hat eine id (int), einen name (string) und einen price (float). Verwenden Sie Constructor Promotion mit öffentlichen Eigenschaften für einen einfachen Zugriff.
  • ProductRepositoryInterface.php — Definieren Sie ein ProductRepositoryInterface, das den Vertrag für jede Produktspeicher-Implementierung festlegt. Ihr Interface sollte diese Methoden deklarieren:
    • find(int $id): ?Product — Ruft ein Produkt anhand der ID ab oder null, falls es nicht gefunden wurde
    • findAll(): array — Ruft alle Produkte als Array ab
    • save(Product $product): void — Speichert oder aktualisiert ein Produkt
    • delete(int $id): void — Entfernt ein Produkt anhand der ID
  • InMemoryProductRepository.php — Binden Sie sowohl die Product-Klasse als auch das Interface ein. Erstellen Sie eine InMemoryProductRepository-Klasse, die das Interface implementiert und ein privates Array zum Speichern von Produkten verwendet. Produkte sollten für eine effiziente Suche über ihre ID indiziert werden. Die Methode findAll() sollte nur die Produktobjekte zurückgeben (verwenden Sie array_values(), um die Array-Keys zurückzusetzen).
  • main.php — Binden Sie die Repository-Datei ein. Sie erhalten eine Eingabe: einen JSON-String, der ein Array von hinzuzufügenden Produkten enthält, gefolgt von einer ID, nach der gesucht werden soll.

    Das JSON-Format wird wie folgt aussehen:

    [{"id": 1, "name": "Laptop", "price": 999.99}, {"id": 2, "name": "Mouse", "price": 29.99}]

    Erstellen Sie ein InMemoryProductRepository, speichern Sie alle Produkte aus der JSON-Eingabe und verwenden Sie dann die zweite Eingabe (die ID), um ein bestimmtes Produkt zu finden. Geben Sie die Ergebnisse in diesem Format aus:

    Total products: [count]
    Found: [name] - $[price]

    Formatieren Sie den Preis auf zwei Dezimalstellen. Wenn das Produkt nicht gefunden wird, geben Sie stattdessen Found: Not found aus.

Das Repository-Pattern glänzt, wenn Sie Speicherimplementierungen austauschen müssen. Ihr Hauptcode hängt nur vom Interface ab – der Wechsel von In-Memory-Speicherung zu einer Datenbank würde bedeuten, eine neue Repository-Klasse zu erstellen, ohne die Anwendungslogik zu ändern.

Spickzettel

Das Repository Pattern vermittelt zwischen Geschäftslogik und Datenspeicherung und bietet eine kollektionsähnliche Schnittstelle für den Zugriff auf Domänenobjekte, während die Details der Speicherimplementierung verborgen bleiben.

Definieren Sie ein Repository-Interface, das den Vertrag für den Datenzugriff festlegt:

<?php
interface UserRepositoryInterface {
    public function find(int $id): ?User;
    public function findAll(): array;
    public function save(User $user): void;
    public function delete(int $id): void;
}

Erstellen Sie eine Domänenklasse unter Verwendung von Constructor Promotion:

<?php
class User {
    public function __construct(
        public int $id,
        public string $name,
        public string $email
    ) {}
}

Implementieren Sie das Repository-Interface mit einem konkreten Speichermechanismus:

<?php
class InMemoryUserRepository implements UserRepositoryInterface {
    private array $users = [];
    
    public function find(int $id): ?User {
        return $this->users[$id] ?? null;
    }
    
    public function findAll(): array {
        return array_values($this->users);
    }
    
    public function save(User $user): void {
        $this->users[$user->id] = $user;
    }
    
    public function delete(int $id): void {
        unset($this->users[$id]);
    }
}

Verwenden Sie das Repository über sein Interface:

$repo = new InMemoryUserRepository();
$repo->save(new User(1, "Alice", "alice@example.com"));
$user = $repo->find(1);
$allUsers = $repo->findAll();

Das Pattern ermöglicht das Austauschen von Implementierungen, ohne die Geschäftslogik zu ändern – Ihr Code hängt vom Interface ab, nicht von der konkreten Implementierung.

Probier es selbst

<?php

require_once 'InMemoryProductRepository.php';

// Eingabe lesen
$jsonInput = trim(fgets(STDIN));
$searchId = intval(trim(fgets(STDIN)));

// Die JSON-Eingabe in ein Array umwandeln
$productsData = (array)json_decode($jsonInput, true);

// TODO: Eine Instanz von InMemoryProductRepository erstellen

// TODO: Durch $productsData iterieren und jedes Produkt speichern
// Jedes Element hat die Schlüssel 'id', 'name' und 'price'

// TODO: Alle Produkte abrufen und die Gesamtzahl ausgeben
// Format: "Total products: [count]"

// TODO: Das Produkt mit der $searchId finden
// Falls gefunden, ausgeben: "Found: [name] - $[price]" (Preis auf 2 Dezimalstellen formatiert)
// Falls nicht gefunden, ausgeben: "Found: Not found"

?>
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung