Repository Pattern implementieren | Enjyn Gruppe
Hallo Welt
→
→
Hallo Welt
Original Lingva Deutsch
Übersetzung wird vorbereitet...
Dieser Vorgang kann bis zu 60 Sekunden dauern.
Diese Seite wird erstmalig übersetzt und dann für alle Besucher gespeichert.
0%
DE Zurück zu Deutsch
Übersetzung durch Lingva Translate

330 Dokumentationen verfügbar

Wissensdatenbank

Repository Pattern Implementierung

Zuletzt aktualisiert: 14.07.2026 um 12:29 Uhr

Repository Pattern implementieren

Das Repository Pattern abstrahiert den Datenzugriff und entkoppelt die Geschäftslogik von der Persistenzschicht. Lernen Sie die Implementierung und Best Practices.

Was ist das Repository Pattern?

┌─────────────────────────────────────────────────────────────┐
│                    APPLICATION LAYER                        │
│                                                             │
│   ┌─────────────┐         ┌─────────────────────┐          │
│   │   Service   │ ──────► │ Repository Interface│          │
│   └─────────────┘         └──────────┬──────────┘          │
│                                      │                      │
├──────────────────────────────────────┼──────────────────────┤
│                 INFRASTRUCTURE LAYER │                      │
│                                      │                      │
│         ┌────────────────────────────┴───────────────┐     │
│         │                                            │     │
│   ┌─────┴─────┐   ┌────────────┐   ┌──────────────┐ │     │
│   │ MySQL     │   │ PostgreSQL │   │ InMemory     │ │     │
│   │ Repository│   │ Repository │   │ Repository   │ │     │
│   └───────────┘   └────────────┘   └──────────────┘ │     │
│                                                      │     │
└──────────────────────────────────────────────────────┘     │
                                                              │

Grundlegendes Interface

// Generic Repository Interface
interface RepositoryInterface {
    public function find(int $id): ?object;
    public function findAll(): array;
    public function save(object $entity): void;
    public function delete(object $entity): void;
}

// Spezifisches Repository Interface
interface UserRepositoryInterface {
    public function find(int $id): ?User;
    public function findAll(): array;
    public function findByEmail(string $email): ?User;
    public function findActiveUsers(): array;
    public function save(User $user): void;
    public function delete(User $user): void;
}

Konkrete Implementierung

class MySQLUserRepository implements UserRepositoryInterface {
    public function __construct(
        private PDO $pdo
    ) {}

    public function find(int $id): ?User {
        $stmt = $this->pdo->prepare(
            'SELECT * FROM users WHERE id = :id'
        );
        $stmt->execute(['id' => $id]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        return $row ? $this->hydrate($row) : null;
    }

    public function findAll(): array {
        $stmt = $this->pdo->query('SELECT * FROM users');
        $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

        return array_map([$this, 'hydrate'], $rows);
    }

    public function findByEmail(string $email): ?User {
        $stmt = $this->pdo->prepare(
            'SELECT * FROM users WHERE email = :email'
        );
        $stmt->execute(['email' => $email]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        return $row ? $this->hydrate($row) : null;
    }

    public function findActiveUsers(): array {
        $stmt = $this->pdo->query(
            'SELECT * FROM users WHERE status = "active"'
        );
        return array_map([$this, 'hydrate'], $stmt->fetchAll(PDO::FETCH_ASSOC));
    }

    public function save(User $user): void {
        if ($user->getId() === null) {
            $this->insert($user);
        } else {
            $this->update($user);
        }
    }

    private function insert(User $user): void {
        $stmt = $this->pdo->prepare(
            'INSERT INTO users (name, email, status) VALUES (:name, :email, :status)'
        );
        $stmt->execute([
            'name' => $user->getName(),
            'email' => $user->getEmail(),
            'status' => $user->getStatus()
        ]);
        $user->setId((int) $this->pdo->lastInsertId());
    }

    private function update(User $user): void {
        $stmt = $this->pdo->prepare(
            'UPDATE users SET name = :name, email = :email, status = :status WHERE id = :id'
        );
        $stmt->execute([
            'id' => $user->getId(),
            'name' => $user->getName(),
            'email' => $user->getEmail(),
            'status' => $user->getStatus()
        ]);
    }

    public function delete(User $user): void {
        $stmt = $this->pdo->prepare('DELETE FROM users WHERE id = :id');
        $stmt->execute(['id' => $user->getId()]);
    }

    private function hydrate(array $row): User {
        return new User(
            id: (int) $row['id'],
            name: $row['name'],
            email: $row['email'],
            status: $row['status']
        );
    }
}

In-Memory Repository für Tests

class InMemoryUserRepository implements UserRepositoryInterface {
    private array $users = [];
    private int $nextId = 1;

    public function find(int $id): ?User {
        return $this->users[$id] ?? null;
    }

    public function findAll(): array {
        return array_values($this->users);
    }

    public function findByEmail(string $email): ?User {
        foreach ($this->users as $user) {
            if ($user->getEmail() === $email) {
                return $user;
            }
        }
        return null;
    }

    public function findActiveUsers(): array {
        return array_filter(
            $this->users,
            fn(User $user) => $user->getStatus() === 'active'
        );
    }

    public function save(User $user): void {
        if ($user->getId() === null) {
            $user->setId($this->nextId++);
        }
        $this->users[$user->getId()] = $user;
    }

    public function delete(User $user): void {
        unset($this->users[$user->getId()]);
    }

    // Hilfsmethoden für Tests
    public function clear(): void {
        $this->users = [];
        $this->nextId = 1;
    }

    public function count(): int {
        return count($this->users);
    }
}

Verwendung im Service

class UserService {
    public function __construct(
        private UserRepositoryInterface $userRepository,
        private MailerInterface $mailer
    ) {}

    public function register(string $name, string $email, string $password): User {
        // Prüfen ob Email bereits existiert
        if ($this->userRepository->findByEmail($email)) {
            throw new UserAlreadyExistsException($email);
        }

        // User erstellen
        $user = new User(
            id: null,
            name: $name,
            email: $email,
            status: 'pending'
        );
        $user->setPassword(password_hash($password, PASSWORD_DEFAULT));

        // Speichern
        $this->userRepository->save($user);

        // Willkommens-Email senden
        $this->mailer->sendWelcomeEmail($user);

        return $user;
    }

    public function activate(int $userId): void {
        $user = $this->userRepository->find($userId);

        if (!$user) {
            throw new UserNotFoundException($userId);
        }

        $user->setStatus('active');
        $this->userRepository->save($user);
    }

    public function getActiveUsers(): array {
        return $this->userRepository->findActiveUsers();
    }
}

Criteria/Specification Pattern

// Für komplexe Abfragen: Specification Pattern

interface Specification {
    public function toSql(): string;
    public function getParameters(): array;
}

class EmailSpecification implements Specification {
    public function __construct(private string $email) {}

    public function toSql(): string {
        return 'email = :email';
    }

    public function getParameters(): array {
        return ['email' => $this->email];
    }
}

class StatusSpecification implements Specification {
    public function __construct(private string $status) {}

    public function toSql(): string {
        return 'status = :status';
    }

    public function getParameters(): array {
        return ['status' => $this->status];
    }
}

class AndSpecification implements Specification {
    private array $specifications;

    public function __construct(Specification ...$specifications) {
        $this->specifications = $specifications;
    }

    public function toSql(): string {
        $parts = array_map(
            fn($spec) => $spec->toSql(),
            $this->specifications
        );
        return '(' . implode(' AND ', $parts) . ')';
    }

    public function getParameters(): array {
        return array_merge(
            ...array_map(
                fn($spec) => $spec->getParameters(),
                $this->specifications
            )
        );
    }
}

// Erweitertes Repository Interface
interface UserRepositoryInterface {
    // ... andere Methoden ...
    public function findBySpecification(Specification $spec): array;
}

// Verwendung
$activeAdmins = $userRepository->findBySpecification(
    new AndSpecification(
        new StatusSpecification('active'),
        new RoleSpecification('admin')
    )
);

Mit Doctrine ORM

use Doctrine\ORM\EntityRepository;

class DoctrineUserRepository extends EntityRepository implements UserRepositoryInterface {
    public function findByEmail(string $email): ?User {
        return $this->findOneBy(['email' => $email]);
    }

    public function findActiveUsers(): array {
        return $this->findBy(['status' => 'active']);
    }

    public function save(User $user): void {
        $this->getEntityManager()->persist($user);
        $this->getEntityManager()->flush();
    }

    public function delete(User $user): void {
        $this->getEntityManager()->remove($user);
        $this->getEntityManager()->flush();
    }

    // Komplexe Abfrage mit QueryBuilder
    public function findRecentlyActive(int $days = 30): array {
        $date = new DateTime("-{$days} days");

        return $this->createQueryBuilder('u')
            ->where('u.lastLoginAt >= :date')
            ->andWhere('u.status = :status')
            ->setParameter('date', $date)
            ->setParameter('status', 'active')
            ->orderBy('u.lastLoginAt', 'DESC')
            ->getQuery()
            ->getResult();
    }
}

Unit Tests

class UserServiceTest extends TestCase {
    private InMemoryUserRepository $userRepository;
    private FakeMailer $mailer;
    private UserService $service;

    protected function setUp(): void {
        $this->userRepository = new InMemoryUserRepository();
        $this->mailer = new FakeMailer();
        $this->service = new UserService(
            $this->userRepository,
            $this->mailer
        );
    }

    public function testRegisterCreatesUser(): void {
        $user = $this->service->register('Max', 'max@test.de', 'secret');

        $this->assertNotNull($user->getId());
        $this->assertEquals('Max', $user->getName());
        $this->assertEquals('pending', $user->getStatus());
    }

    public function testRegisterThrowsExceptionForDuplicateEmail(): void {
        // Existing user
        $existing = new User(null, 'Max', 'max@test.de', 'active');
        $this->userRepository->save($existing);

        $this->expectException(UserAlreadyExistsException::class);
        $this->service->register('Another', 'max@test.de', 'secret');
    }

    public function testActivateChangesStatus(): void {
        $user = new User(null, 'Max', 'max@test.de', 'pending');
        $this->userRepository->save($user);

        $this->service->activate($user->getId());

        $updated = $this->userRepository->find($user->getId());
        $this->assertEquals('active', $updated->getStatus());
    }
}
💡 Vorteile des Repository Patterns: 1. Testbarkeit durch austauschbare Implementierungen
2. Entkopplung von Datenbank-Details
3. Zentrale Stelle für Datenzugriffs-Logik
4. Einfacher Wechsel des Speichersystems
5. Klare Schnittstellen für Datenzugriff

Weitere Informationen

Texterstellung mit KI-Unterstützung

Enjix Beta

Enjyn AI Agent

Hallo 👋 Ich bin Enjix — wie kann ich dir helfen?
LLMs können Fehler machen. Ihre Nachrichten werden an Google Gemini weitergeleitet.
120