Verstreute Konstruktor-Aufrufe im Code werden spätestens dann zum Problem, wenn eine Implementierung ausgetauscht werden soll. Ein bewährtes Erzeugungsmuster bündelt die Objektkonstruktion an einer Stelle und hält den restlichen Code frei von konkreten Klassennamen. Dieses Tutorial stellt die drei Varianten des Factory Pattern in PHP vor und zeigt, wann sich der Aufwand wirklich lohnt.
Was leistet das PHP Factory Pattern?
Wer in einer größeren PHP-Anwendung schon einmal mit verschiedenen Storage-Backends, Logger-Varianten oder Payment-Gateways gearbeitet hat, kennt das Problem: Der Aufruf new ConcreteClass(...) tauchte überall verstreut im Code auf, und jeder Wechsel der Implementierung wurde zur Wartungsübung. Genau hier setzt das PHP Factory Pattern an. Als eines der bekanntesten Designmuster kapselt es die Erzeugung von Objekten in einer eigenen Klasse oder Methode und entkoppelt den restlichen Code von den konkreten Typen, mit denen gearbeitet wird. Dieses Designmuster bietet damit eine zentrale Stelle für jede Objektkonstruktion.

Bevor die drei Ausprägungen Simple Factory, Factory Method und Abstract Factory im Detail folgen, ein kurzer Überblick über die Aufteilung dieses Tutorials.
Das PHP Factory Pattern ist eines der am häufigsten genutzten Erzeugungsmuster und tritt in drei Ausprägungen auf: Simple Factory als pragmatische Variante, Factory Method als klassisches GoF-Muster und Abstract Factory für Familien zusammengehöriger Objekte. Dieses Tutorial zeigt jede Variante anhand eines konkreten Beispiels und nennt am Ende eine klare Faustregel, wann sich der Aufwand wirklich lohnt.
Simple Factory als pragmatische Variante
Die Simple Factory ist streng genommen kein offizielles GoF-Pattern, in der Praxis aber die mit Abstand häufigste Variante. Sie besteht aus einem Interface, mehreren konkreten Implementierungen und einer statischen Factory-Methode, die auf Basis eines Parameters die richtige Klasse zurückgibt. Mit dem match-Ausdruck seit PHP 8 wird die Auswahl besonders kompakt.
<?php
interface Logger
{
public function log(string $message): void;
}
final class FileLogger implements Logger
{
public function log(string $message): void
{
file_put_contents('app.log', $message . PHP_EOL, FILE_APPEND);
}
}
final class ConsoleLogger implements Logger
{
public function log(string $message): void
{
echo "[LOG] $message" . PHP_EOL;
}
}
final class LoggerFactory
{
public static function create(string $type): Logger
{
return match($type) {
'file' => new FileLogger(),
'console' => new ConsoleLogger(),
default => throw new InvalidArgumentException("Unbekannter Logger: $type"),
};
}
}
$logger = LoggerFactory::create('console');
$logger->log('Anwendung gestartet');
Der Client-Code arbeitet ausschließlich mit dem Interface Logger. Er weiß nicht, ob er einen FileLogger oder ConsoleLogger benutzt. Das ist der Schlüssel: Wer später einen JsonLogger oder SyslogLogger ergänzen möchte, fügt nur eine neue Klasse und einen Eintrag in der Factory hinzu, der restliche Code bleibt unangetastet.
Wichtig ist die default-Klausel im match-Ausdruck. Sie wirft eine InvalidArgumentException, sobald ein unbekannter Typ angefragt wird. Das verhindert stille Fehler und liefert im Stack-Trace sofort einen klaren Hinweis darauf, dass ein neuer Typ noch registriert werden muss. In älteren Codebasen sieht man stattdessen oft switch-Anweisungen mit fehlendem default-Branch, die im Fehlerfall einfach null zurückgeben. Das ist eine bekannte Fehlerquelle und sollte heute vermieden werden.
Factory Method als klassisches GoF-Muster
Die Factory Method ist das ursprüngliche GoF-Pattern und unterscheidet sich von der Simple Factory dadurch, dass die Erzeugung an eine abstrakte Methode in einer Basisklasse delegiert wird. Subklassen entscheiden, welches konkrete Produkt sie zurückgeben. Das ist besonders dann nützlich, wenn das gesamte Verhalten in der Basisklasse steht, nur die Erzeugung sich unterscheidet.
<?php
interface ReportFormatter
{
public function format(array $data): string;
}
abstract class ReportService
{
abstract protected function createFormatter(): ReportFormatter;
public function render(array $data): string
{
return $this->createFormatter()->format($data);
}
}
final class HtmlReportService extends ReportService
{
protected function createFormatter(): ReportFormatter
{
return new HtmlFormatter();
}
}
final class CsvReportService extends ReportService
{
protected function createFormatter(): ReportFormatter
{
return new CsvFormatter();
}
}
ReportService definiert den Ablauf in render(). Die Subklassen liefern nur den passenden Formatter. So wird das Open-Closed-Prinzip erfüllt: Neue Reportarten werden ergänzt, ohne die Basisklasse zu verändern.
Abstract Factory für Produktfamilien
Wenn mehrere Objekte zueinander passen müssen (etwa alle Bedienelemente eines UI-Themes), kommt die Abstract Factory ins Spiel. Sie erzeugt nicht ein einzelnes Produkt, sondern eine Familie von Produkten. Das verhindert, dass versehentlich Komponenten unterschiedlicher Themes vermischt werden.
<?php
interface Button { public function render(): string; }
interface Checkbox { public function render(): string; }
interface UiFactory
{
public function createButton(): Button;
public function createCheckbox(): Checkbox;
}
final class LightThemeFactory implements UiFactory
{
public function createButton(): Button { return new LightButton(); }
public function createCheckbox(): Checkbox { return new LightCheckbox(); }
}
final class DarkThemeFactory implements UiFactory
{
public function createButton(): Button { return new DarkButton(); }
public function createCheckbox(): Checkbox { return new DarkCheckbox(); }
}
Der Client erhält ein konkretes UiFactory-Objekt und ruft daraus alle passenden UI-Elemente ab. Light und Dark können niemals vermischt werden, weil eine Factory immer nur ihre eigene Familie liefert.
Anwendungsfall Datenbank-Adapter
Ein klassischer Praxisfall für das PHP Factory Pattern sind Datenbank-Adapter. Eine Anwendung soll mit MySQL, PostgreSQL oder SQLite arbeiten können, ohne dass der Service-Code etwas davon mitbekommt. Eine Factory mit Registry erlaubt es, neue Adapter zur Laufzeit hinzuzufügen.
<?php
interface DbAdapter
{
public function query(string $sql): array;
}
final class DbAdapterFactory
{
private array $adapters = [];
public function register(string $type, callable $factory): void
{
$this->adapters[$type] = $factory;
}
public function create(string $type, array $config): DbAdapter
{
if (!isset($this->adapters[$type])) {
throw new RuntimeException("Adapter $type nicht registriert");
}
return ($this->adapters[$type])($config);
}
}
$factory = new DbAdapterFactory();
$factory->register('mysql', fn($cfg) => new MysqlAdapter($cfg));
$factory->register('sqlite', fn($cfg) => new SqliteAdapter($cfg));
$db = $factory->create('mysql', ['host' => 'localhost']);
Die Registry-Variante ist besonders für modulare Systeme geeignet. Plugins oder Module registrieren ihre eigenen Adapter, ohne dass die Factory-Klasse selbst angefasst werden muss. So bleibt die Factory schlank und der Code wirklich erweiterbar.
flowchart TD
A[Client ruft Factory] --> B{Welcher Typ?}
B -->|file| C[FileLogger erzeugen]
B -->|console| D[ConsoleLogger erzeugen]
C --> E[Logger Interface]
D --> E
E --> F[Client nutzt Logger]
Test-Vorteile gegenüber direktem new
Der wichtigste Praxisvorteil des PHP Factory Pattern zeigt sich im Unit-Test. Wer mitten im Service-Code new ApiClient(...) aufruft, kann diese Abhängigkeit nicht durch ein Mock ersetzen. Eine Factory dagegen wird per Konstruktor injiziert und im Test durch eine Mock-Factory ausgetauscht, die kontrollierte Test-Doubles liefert.
<?php
final class OrderService
{
public function __construct(private LoggerFactory $loggerFactory) {}
public function placeOrder(int $orderId): void
{
$logger = $this->loggerFactory->create('file');
$logger->log("Bestellung $orderId erstellt");
}
}
/* Im Test wird einfach eine Mock-LoggerFactory injiziert,
die ein In-Memory-Logger zurueckgibt. So bleibt der Test
isoliert und schreibt nichts auf die Festplatte */
Mit der injizierten Factory wird die Erzeugungslogik austauschbar. Der Test kontrolliert, welche Implementierung der Service erhält. Das macht den Service-Code unabhängig von der konkreten Klasse und verbessert die Wartbarkeit langfristig.
Wann ist das PHP Factory Pattern sinnvoll?
Das PHP Factory Pattern ist kein Selbstzweck. Wer eine Factory für eine einzelne Klasse mit nur einer Implementierung baut, bekommt unnötige Komplexität. Eine gute Faustregel lautet: Factory ja, wenn mindestens zwei austauschbare Implementierungen existieren, oder wenn die Erzeugung selbst Logik enthält (etwa Validierung der Konfiguration, Aufbau verschachtelter Objekte oder Abhängigkeiten zwischen erzeugten Objekten).
Genauso wichtig ist, dass die Factory nicht statisch überall im Code aufgerufen wird. Statische Factories haben dieselben Test-Probleme wie ein Singleton, weil sie globale Abhängigkeiten verstecken. Besser ist es, die Factory wie jede andere Abhängigkeit per Konstruktor übergeben zu lassen oder über einen DI-Container auflösen zu lassen. Dann profitiert der Code wirklich von dem Pattern, und die Factory wird genau dort instanziiert, wo sie gebraucht wird.
In größeren Anwendungen ist es zudem sinnvoll, die Verantwortung der Factory klein zu halten. Eine Factory sollte ein Objekt erzeugen und es konfigurieren, mehr nicht. Logik, die zur Erzeugung gehört (Default-Werte ergänzen, Konfiguration validieren, Sub-Objekte initialisieren), ist erlaubt. Sobald die Factory aber Geschäftsregeln ausführt oder zu entscheiden beginnt, wann ein Objekt überhaupt erzeugt werden soll, gehört diese Entscheidung in einen Service. Eine schlanke Factory bleibt leicht testbar und ist im Code-Review schnell zu erfassen.
Ein letzter Punkt zur Wahl der richtigen Variante: Wer mit modernen PHP-Features wie Constructor Promotion und Named Arguments arbeitet, kommt oft schon mit einer einzigen statischen create()-Methode auf der eigenen Klasse aus. Das lässt sich dann elegant kombinieren mit einer Factory, die die create()-Methoden orchestriert. Erst wenn mehrere Klassen oder ganze Familien austauschbar sein müssen, lohnt sich der Schritt zu Factory Method oder Abstract Factory. So bleibt der Aufwand für kleine Codebasen klein, und das Pattern wird genau dort eingesetzt, wo es einen echten Mehrwert liefert.
Fazit zum PHP Factory Pattern
Das PHP Factory Pattern ist eines der nützlichsten Erzeugungsmuster im PHP-Alltag. Die Simple Factory deckt 80 % der Fälle ab, die Factory Method ist ideal, wenn nur ein einzelner Schritt eines größeren Ablaufs variiert, und die Abstract Factory schützt vor inkonsistenten Produktfamilien. Wer die Factory per Interface entkoppelt und sauber per Dependency Injection übergibt, gewinnt Testbarkeit und Erweiterbarkeit. Die wichtigste Regel bleibt: nicht jeder new-Aufruf braucht eine Factory; aber sobald mehr als eine Implementierung im Spiel ist, ist das PHP Factory Pattern fast immer die saubere Lösung.