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

235 Dokumentationen verfügbar

Wissensdatenbank

Repository Pattern Implementierung

Zuletzt aktualisiert: 20.01.2026 um 11:26 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

Enjix Beta

Enjyn AI Agent

Hallo 👋 Ich bin Enjix — wie kann ich dir helfen?
120