- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
27 KiB
Entwurfsmuster
Diese Dokumentation beschreibt die wichtigsten Entwurfsmuster, die im Framework verwendet werden. Das Verständnis dieser Muster hilft bei der effektiven Nutzung des Frameworks und bei der Erweiterung seiner Funktionalität.
Strukturelle Muster
Adapter
Das Adapter-Muster wird verwendet, um Schnittstellen von inkompatiblen Klassen anzupassen, sodass sie zusammenarbeiten können.
Verwendung im Framework:
- Datenbank-Adapter für verschiedene Datenbanktypen
- Cache-Adapter für verschiedene Cache-Backends
- Logging-Adapter für verschiedene Logging-Systeme
Beispiel:
// Interface für die Zielschnittstelle
interface CacheInterface
{
public function get(string $key, mixed $default = null): mixed;
public function set(string $key, mixed $value, int $ttl = null): bool;
public function delete(string $key): bool;
}
// Adapter für Redis
class RedisCacheAdapter implements CacheInterface
{
private $redis;
public function __construct(Redis $redis)
{
$this->redis = $redis;
}
public function get(string $key, mixed $default = null): mixed
{
$value = $this->redis->get($key);
return $value !== false ? unserialize($value) : $default;
}
public function set(string $key, mixed $value, int $ttl = null): bool
{
return $this->redis->set($key, serialize($value), $ttl);
}
public function delete(string $key): bool
{
return $this->redis->del($key) > 0;
}
}
Composite
Das Composite-Muster wird verwendet, um Objekte in Baumstrukturen zu organisieren und Einzelobjekte und Kompositionen einheitlich zu behandeln.
Verwendung im Framework:
- Menüsystem mit verschachtelten Menüpunkten
- Validierungsregeln mit komplexen Bedingungen
- Routengruppen mit verschachtelten Routen
Beispiel:
// Basiskomponente
interface MenuItemInterface
{
public function render(): string;
}
// Blattkomponente
class MenuItem implements MenuItemInterface
{
private string $label;
private string $url;
public function __construct(string $label, string $url)
{
$this->label = $label;
$this->url = $url;
}
public function render(): string
{
return '<li><a href="' . $this->url . '">' . $this->label . '</a></li>';
}
}
// Kompositkomponente
class MenuGroup implements MenuItemInterface
{
private string $label;
private array $items = [];
public function __construct(string $label)
{
$this->label = $label;
}
public function addItem(MenuItemInterface $item): void
{
$this->items[] = $item;
}
public function render(): string
{
$output = '<li>' . $this->label . '<ul>';
foreach ($this->items as $item) {
$output .= $item->render();
}
$output .= '</ul></li>';
return $output;
}
}
Decorator
Das Decorator-Muster wird verwendet, um Objekten dynamisch zusätzliche Verantwortlichkeiten hinzuzufügen, ohne ihre Struktur zu ändern.
Verwendung im Framework:
- HTTP-Middleware-Stack
- Caching-Dekoratoren für Repository-Klassen
- Logging-Dekoratoren für Service-Klassen
Beispiel:
// Basiskomponente
interface RepositoryInterface
{
public function find(int $id): ?Model;
public function findAll(): array;
}
// Konkrete Komponente
class UserRepository implements RepositoryInterface
{
public function find(int $id): ?Model
{
// Benutzer aus der Datenbank abrufen
}
public function findAll(): array
{
// Alle Benutzer aus der Datenbank abrufen
}
}
// Basisdekorator
abstract class RepositoryDecorator implements RepositoryInterface
{
protected RepositoryInterface $repository;
public function __construct(RepositoryInterface $repository)
{
$this->repository = $repository;
}
public function find(int $id): ?Model
{
return $this->repository->find($id);
}
public function findAll(): array
{
return $this->repository->findAll();
}
}
// Konkreter Dekorator
class CachingRepositoryDecorator extends RepositoryDecorator
{
private CacheInterface $cache;
public function __construct(RepositoryInterface $repository, CacheInterface $cache)
{
parent::__construct($repository);
$this->cache = $cache;
}
public function find(int $id): ?Model
{
$cacheKey = 'user:' . $id;
if ($this->cache->has($cacheKey)) {
return $this->cache->get($cacheKey);
}
$user = $this->repository->find($id);
if ($user) {
$this->cache->set($cacheKey, $user, 3600);
}
return $user;
}
}
Facade
Das Facade-Muster bietet eine vereinfachte Schnittstelle zu einem komplexen Subsystem.
Verwendung im Framework:
- Datenbank-Facade für einfachen Zugriff auf Datenbankoperationen
- Cache-Facade für einfachen Zugriff auf Cache-Operationen
- Auth-Facade für einfachen Zugriff auf Authentifizierungsfunktionen
Beispiel:
// Komplexes Subsystem
class Authentication
{
public function checkCredentials(string $username, string $password): bool
{
// Überprüfen der Anmeldeinformationen
}
}
class Session
{
public function set(string $key, mixed $value): void
{
// Wert in der Sitzung speichern
}
public function get(string $key): mixed
{
// Wert aus der Sitzung abrufen
}
}
class User
{
public function findByUsername(string $username): ?User
{
// Benutzer nach Benutzernamen suchen
}
}
// Facade
class Auth
{
private Authentication $authentication;
private Session $session;
private User $user;
public function __construct(Authentication $authentication, Session $session, User $user)
{
$this->authentication = $authentication;
$this->session = $session;
$this->user = $user;
}
public function login(string $username, string $password): bool
{
if ($this->authentication->checkCredentials($username, $password)) {
$user = $this->user->findByUsername($username);
$this->session->set('user_id', $user->id);
return true;
}
return false;
}
public function logout(): void
{
$this->session->set('user_id', null);
}
public function check(): bool
{
return $this->session->get('user_id') !== null;
}
}
Proxy
Das Proxy-Muster stellt einen Platzhalter für ein anderes Objekt bereit, um den Zugriff darauf zu kontrollieren.
Verwendung im Framework:
- Lazy-Loading-Proxies für ressourcenintensive Objekte
- Remote-Proxies für API-Aufrufe
- Zugriffssteuerung-Proxies für Autorisierung
Beispiel:
// Subjektschnittstelle
interface ImageInterface
{
public function display(): void;
}
// Reales Subjekt
class Image implements ImageInterface
{
private string $filename;
public function __construct(string $filename)
{
$this->filename = $filename;
$this->loadImageFromDisk(); // Teure Operation
}
private function loadImageFromDisk(): void
{
echo "Loading image {$this->filename} from disk...\n";
// Bild laden
}
public function display(): void
{
echo "Displaying image {$this->filename}\n";
}
}
// Proxy
class LazyImage implements ImageInterface
{
private ?Image $realImage = null;
private string $filename;
public function __construct(string $filename)
{
$this->filename = $filename;
}
public function display(): void
{
if ($this->realImage === null) {
$this->realImage = new Image($this->filename);
}
$this->realImage->display();
}
}
Verhaltensmuster
Chain of Responsibility
Das Chain of Responsibility-Muster ermöglicht es, eine Anfrage entlang einer Kette von Handlern zu senden, wobei jeder Handler entscheidet, ob er die Anfrage verarbeitet oder an den nächsten Handler weiterleitet.
Verwendung im Framework:
- Middleware-Pipeline für HTTP-Anfragen
- Ereignislistener-Kette
- Validierungskette
Beispiel:
// Handler-Schnittstelle
interface RequestHandlerInterface
{
public function handle(Request $request): Response;
}
// Konkreter Handler
class AuthenticationMiddleware implements RequestHandlerInterface
{
private RequestHandlerInterface $next;
public function setNext(RequestHandlerInterface $next): void
{
$this->next = $next;
}
public function handle(Request $request): Response
{
if (!$this->isAuthenticated($request)) {
return new Response('Unauthorized', 401);
}
return $this->next->handle($request);
}
private function isAuthenticated(Request $request): bool
{
// Überprüfen, ob der Benutzer authentifiziert ist
}
}
// Konkreter Handler
class AuthorizationMiddleware implements RequestHandlerInterface
{
private RequestHandlerInterface $next;
public function setNext(RequestHandlerInterface $next): void
{
$this->next = $next;
}
public function handle(Request $request): Response
{
if (!$this->isAuthorized($request)) {
return new Response('Forbidden', 403);
}
return $this->next->handle($request);
}
private function isAuthorized(Request $request): bool
{
// Überprüfen, ob der Benutzer autorisiert ist
}
}
Command
Das Command-Muster kapselt eine Anfrage als Objekt, wodurch es möglich wird, Anfragen zu parametrisieren, in eine Warteschlange zu stellen, zu protokollieren und rückgängig zu machen.
Verwendung im Framework:
- Konsolenbefehle
- Aufgaben in der Warteschlange
- Transaktionen
Beispiel:
// Befehlsschnittstelle
interface CommandInterface
{
public function execute(): void;
}
// Konkreter Befehl
class CreateUserCommand implements CommandInterface
{
private UserRepository $userRepository;
private string $name;
private string $email;
public function __construct(UserRepository $userRepository, string $name, string $email)
{
$this->userRepository = $userRepository;
$this->name = $name;
$this->email = $email;
}
public function execute(): void
{
$user = new User();
$user->name = $this->name;
$user->email = $this->email;
$this->userRepository->save($user);
}
}
// Aufrufer
class CommandBus
{
public function dispatch(CommandInterface $command): void
{
$command->execute();
}
}
Observer
Das Observer-Muster definiert eine Eins-zu-viele-Abhängigkeit zwischen Objekten, sodass alle abhängigen Objekte automatisch benachrichtigt und aktualisiert werden, wenn ein Objekt seinen Zustand ändert.
Verwendung im Framework:
- Ereignissystem
- Modell-Beobachter
- Konfigurationsänderungen
Beispiel:
// Subjektschnittstelle
interface SubjectInterface
{
public function attach(ObserverInterface $observer): void;
public function detach(ObserverInterface $observer): void;
public function notify(): void;
}
// Beobachterschnittstelle
interface ObserverInterface
{
public function update(SubjectInterface $subject): void;
}
// Konkretes Subjekt
class User implements SubjectInterface
{
private array $observers = [];
private string $name;
private string $email;
public function attach(ObserverInterface $observer): void
{
$this->observers[] = $observer;
}
public function detach(ObserverInterface $observer): void
{
$index = array_search($observer, $this->observers, true);
if ($index !== false) {
unset($this->observers[$index]);
}
}
public function notify(): void
{
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
public function setName(string $name): void
{
$this->name = $name;
$this->notify();
}
public function setEmail(string $email): void
{
$this->email = $email;
$this->notify();
}
public function getName(): string
{
return $this->name;
}
public function getEmail(): string
{
return $this->email;
}
}
// Konkreter Beobachter
class UserLogger implements ObserverInterface
{
public function update(SubjectInterface $subject): void
{
if ($subject instanceof User) {
echo "User updated: {$subject->getName()} ({$subject->getEmail()})\n";
}
}
}
Strategy
Das Strategy-Muster definiert eine Familie von Algorithmen, kapselt jeden einzelnen und macht sie austauschbar. Das Muster ermöglicht es, den Algorithmus unabhängig von den Clients zu variieren, die ihn verwenden.
Verwendung im Framework:
- Validierungsstrategien
- Caching-Strategien
- Authentifizierungsstrategien
Beispiel:
// Strategieschnittstelle
interface PaymentStrategyInterface
{
public function pay(float $amount): bool;
}
// Konkrete Strategien
class CreditCardPaymentStrategy implements PaymentStrategyInterface
{
private string $cardNumber;
private string $expiryDate;
private string $cvv;
public function __construct(string $cardNumber, string $expiryDate, string $cvv)
{
$this->cardNumber = $cardNumber;
$this->expiryDate = $expiryDate;
$this->cvv = $cvv;
}
public function pay(float $amount): bool
{
// Kreditkartenzahlung verarbeiten
return true;
}
}
class PayPalPaymentStrategy implements PaymentStrategyInterface
{
private string $email;
private string $password;
public function __construct(string $email, string $password)
{
$this->email = $email;
$this->password = $password;
}
public function pay(float $amount): bool
{
// PayPal-Zahlung verarbeiten
return true;
}
}
// Kontext
class PaymentProcessor
{
private PaymentStrategyInterface $paymentStrategy;
public function setPaymentStrategy(PaymentStrategyInterface $paymentStrategy): void
{
$this->paymentStrategy = $paymentStrategy;
}
public function processPayment(float $amount): bool
{
return $this->paymentStrategy->pay($amount);
}
}
Template Method
Das Template Method-Muster definiert das Skelett eines Algorithmus in einer Methode und verschiebt einige Schritte in Unterklassen. Das Muster ermöglicht es Unterklassen, bestimmte Schritte eines Algorithmus zu überschreiben, ohne die Struktur des Algorithmus zu ändern.
Verwendung im Framework:
- Controller-Basisklasse mit Hooks
- Migrationsprozess
- Testfälle
Beispiel:
// Abstrakte Klasse mit Template-Methode
abstract class Report
{
// Template-Methode
public function generate(): string
{
$data = $this->fetchData();
$processedData = $this->processData($data);
$report = $this->formatReport($processedData);
$this->saveReport($report);
return $report;
}
// Abstrakte Methoden, die von Unterklassen implementiert werden müssen
abstract protected function fetchData(): array;
abstract protected function formatReport(array $data): string;
// Konkrete Methoden, die von Unterklassen überschrieben werden können
protected function processData(array $data): array
{
// Standardverarbeitung
return $data;
}
protected function saveReport(string $report): void
{
// Standardspeicherung
file_put_contents('report.txt', $report);
}
}
// Konkrete Unterklasse
class SalesReport extends Report
{
protected function fetchData(): array
{
// Verkaufsdaten abrufen
return [
['product' => 'Product A', 'sales' => 100],
['product' => 'Product B', 'sales' => 200],
];
}
protected function processData(array $data): array
{
// Spezielle Verarbeitung für Verkaufsdaten
$processedData = [];
$totalSales = 0;
foreach ($data as $item) {
$totalSales += $item['sales'];
$processedData[] = $item;
}
$processedData[] = ['product' => 'Total', 'sales' => $totalSales];
return $processedData;
}
protected function formatReport(array $data): string
{
// Formatierung als CSV
$report = "Product,Sales\n";
foreach ($data as $item) {
$report .= "{$item['product']},{$item['sales']}\n";
}
return $report;
}
}
Erzeugungsmuster
Factory Method
Das Factory Method-Muster definiert eine Schnittstelle zum Erstellen eines Objekts, lässt aber Unterklassen entscheiden, welche Klasse instanziiert werden soll. Das Muster ermöglicht es einer Klasse, die Instanziierung an Unterklassen zu delegieren.
Verwendung im Framework:
- Controller-Factory
- Repository-Factory
- Service-Factory
Beispiel:
// Produktschnittstelle
interface LoggerInterface
{
public function log(string $message): void;
}
// Konkrete Produkte
class FileLogger implements LoggerInterface
{
private string $filePath;
public function __construct(string $filePath)
{
$this->filePath = $filePath;
}
public function log(string $message): void
{
file_put_contents($this->filePath, $message . PHP_EOL, FILE_APPEND);
}
}
class DatabaseLogger implements LoggerInterface
{
private PDO $pdo;
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}
public function log(string $message): void
{
$stmt = $this->pdo->prepare('INSERT INTO logs (message) VALUES (:message)');
$stmt->execute(['message' => $message]);
}
}
// Creator-Schnittstelle mit Factory-Methode
interface LoggerFactoryInterface
{
public function createLogger(): LoggerInterface;
}
// Konkrete Creators
class FileLoggerFactory implements LoggerFactoryInterface
{
private string $filePath;
public function __construct(string $filePath)
{
$this->filePath = $filePath;
}
public function createLogger(): LoggerInterface
{
return new FileLogger($this->filePath);
}
}
class DatabaseLoggerFactory implements LoggerFactoryInterface
{
private PDO $pdo;
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}
public function createLogger(): LoggerInterface
{
return new DatabaseLogger($this->pdo);
}
}
Abstract Factory
Das Abstract Factory-Muster bietet eine Schnittstelle zum Erstellen von Familien verwandter oder abhängiger Objekte, ohne ihre konkreten Klassen zu spezifizieren.
Verwendung im Framework:
- UI-Komponenten-Factory
- Datenbankabstraktions-Factory
- Service-Factory für verschiedene Umgebungen
Beispiel:
// Abstrakte Produktschnittstellen
interface ButtonInterface
{
public function render(): string;
}
interface InputInterface
{
public function render(): string;
}
// Konkrete Produkte für Bootstrap
class BootstrapButton implements ButtonInterface
{
private string $label;
public function __construct(string $label)
{
$this->label = $label;
}
public function render(): string
{
return '<button class="btn btn-primary">' . $this->label . '</button>';
}
}
class BootstrapInput implements InputInterface
{
private string $name;
private string $placeholder;
public function __construct(string $name, string $placeholder)
{
$this->name = $name;
$this->placeholder = $placeholder;
}
public function render(): string
{
return '<input type="text" class="form-control" name="' . $this->name . '" placeholder="' . $this->placeholder . '">';
}
}
// Konkrete Produkte für Material Design
class MaterialButton implements ButtonInterface
{
private string $label;
public function __construct(string $label)
{
$this->label = $label;
}
public function render(): string
{
return '<button class="mdc-button mdc-button--raised">' . $this->label . '</button>';
}
}
class MaterialInput implements InputInterface
{
private string $name;
private string $placeholder;
public function __construct(string $name, string $placeholder)
{
$this->name = $name;
$this->placeholder = $placeholder;
}
public function render(): string
{
return '<div class="mdc-text-field"><input type="text" name="' . $this->name . '" placeholder="' . $this->placeholder . '" class="mdc-text-field__input"></div>';
}
}
// Abstrakte Factory-Schnittstelle
interface UIFactoryInterface
{
public function createButton(string $label): ButtonInterface;
public function createInput(string $name, string $placeholder): InputInterface;
}
// Konkrete Factories
class BootstrapUIFactory implements UIFactoryInterface
{
public function createButton(string $label): ButtonInterface
{
return new BootstrapButton($label);
}
public function createInput(string $name, string $placeholder): InputInterface
{
return new BootstrapInput($name, $placeholder);
}
}
class MaterialUIFactory implements UIFactoryInterface
{
public function createButton(string $label): ButtonInterface
{
return new MaterialButton($label);
}
public function createInput(string $name, string $placeholder): InputInterface
{
return new MaterialInput($name, $placeholder);
}
}
Singleton
Das Singleton-Muster stellt sicher, dass eine Klasse nur eine Instanz hat und bietet einen globalen Zugriffspunkt auf diese Instanz.
Verwendung im Framework:
- Konfigurationsmanager
- Datenbankverbindung
- Logger
Beispiel:
class DatabaseConnection
{
private static ?DatabaseConnection $instance = null;
private PDO $pdo;
private function __construct(string $dsn, string $username, string $password)
{
$this->pdo = new PDO($dsn, $username, $password);
}
public static function getInstance(string $dsn, string $username, string $password): DatabaseConnection
{
if (self::$instance === null) {
self::$instance = new self($dsn, $username, $password);
}
return self::$instance;
}
public function getConnection(): PDO
{
return $this->pdo;
}
// Verhindern des Klonens
private function __clone() {}
// Verhindern der Deserialisierung
public function __wakeup()
{
throw new \Exception("Cannot unserialize a singleton.");
}
}
Builder
Das Builder-Muster trennt die Konstruktion eines komplexen Objekts von seiner Repräsentation, sodass derselbe Konstruktionsprozess verschiedene Repräsentationen erstellen kann.
Verwendung im Framework:
- Query Builder
- Form Builder
- Response Builder
Beispiel:
// Produkt
class Query
{
private string $select = '*';
private string $from = '';
private array $where = [];
private array $orderBy = [];
private ?int $limit = null;
private ?int $offset = null;
public function setSelect(string $select): void
{
$this->select = $select;
}
public function setFrom(string $from): void
{
$this->from = $from;
}
public function addWhere(string $condition): void
{
$this->where[] = $condition;
}
public function addOrderBy(string $column, string $direction = 'ASC'): void
{
$this->orderBy[] = "$column $direction";
}
public function setLimit(?int $limit): void
{
$this->limit = $limit;
}
public function setOffset(?int $offset): void
{
$this->offset = $offset;
}
public function getSql(): string
{
$sql = "SELECT {$this->select} FROM {$this->from}";
if (!empty($this->where)) {
$sql .= " WHERE " . implode(' AND ', $this->where);
}
if (!empty($this->orderBy)) {
$sql .= " ORDER BY " . implode(', ', $this->orderBy);
}
if ($this->limit !== null) {
$sql .= " LIMIT {$this->limit}";
}
if ($this->offset !== null) {
$sql .= " OFFSET {$this->offset}";
}
return $sql;
}
}
// Builder-Schnittstelle
interface QueryBuilderInterface
{
public function select(string $columns): QueryBuilderInterface;
public function from(string $table): QueryBuilderInterface;
public function where(string $condition): QueryBuilderInterface;
public function orderBy(string $column, string $direction = 'ASC'): QueryBuilderInterface;
public function limit(int $limit): QueryBuilderInterface;
public function offset(int $offset): QueryBuilderInterface;
public function getQuery(): Query;
}
// Konkreter Builder
class QueryBuilder implements QueryBuilderInterface
{
private Query $query;
public function __construct()
{
$this->query = new Query();
}
public function select(string $columns): QueryBuilderInterface
{
$this->query->setSelect($columns);
return $this;
}
public function from(string $table): QueryBuilderInterface
{
$this->query->setFrom($table);
return $this;
}
public function where(string $condition): QueryBuilderInterface
{
$this->query->addWhere($condition);
return $this;
}
public function orderBy(string $column, string $direction = 'ASC'): QueryBuilderInterface
{
$this->query->addOrderBy($column, $direction);
return $this;
}
public function limit(int $limit): QueryBuilderInterface
{
$this->query->setLimit($limit);
return $this;
}
public function offset(int $offset): QueryBuilderInterface
{
$this->query->setOffset($offset);
return $this;
}
public function getQuery(): Query
{
return $this->query;
}
}
// Director
class QueryDirector
{
private QueryBuilderInterface $builder;
public function __construct(QueryBuilderInterface $builder)
{
$this->builder = $builder;
}
public function buildUserQuery(): Query
{
return $this->builder
->select('id, name, email')
->from('users')
->where('active = 1')
->orderBy('name')
->limit(10)
->getQuery();
}
public function buildPostQuery(int $userId): Query
{
return $this->builder
->select('id, title, content')
->from('posts')
->where("user_id = $userId")
->where('published = 1')
->orderBy('created_at', 'DESC')
->getQuery();
}
}
Weitere Informationen
- Architekturübersicht: Überblick über die Architektur des Frameworks.
- Komponenten: Detaillierte Informationen zu den Hauptkomponenten des Frameworks.
- Komponentendokumentation: Dokumentation der einzelnen Komponenten.