Developers building Laravel applications are being urged to ditch the new keyword inside their controllers and switch to the Factory Method pattern. By moving object creation into dedicated factories, code becomes easier to extend, test, and maintain—benefits that matter as projects grow beyond a handful of endpoints.
Why the new keyword is a hidden liability
When a controller contains a line such as
$processor = new StripePaymentProcessor($config);
the controller is now tightly coupled to the StripePaymentProcessor class. Every place that needs a payment processor repeats that line, scattering the construction logic across the codebase. If the processor later requires a logger, a cache manager, or a different configuration format, each of those spots must be updated. The result is fragile code that resists change and is hard to mock in unit tests.
The Factory Method pattern steps in
The Factory Method pattern replaces direct instantiation with a method that returns an instance of a given interface. The controller depends on the interface, while a factory class knows which concrete class to build and how to assemble its dependencies. All creation logic lives in one place, so adding a new dependency or swapping implementations touches only the factory.
Core advantages
- Loose coupling – Controllers work against abstractions, not concrete classes.
- Centralized construction – Changing the build process requires editing a single file.
- Testability – Factories can be stubbed or replaced with mocks, allowing isolated controller tests.
- Open/Closed Principle – New features (e.g., a new payment gateway) can be added without touching existing controller code.
A coffee-shop analogy
Imagine a barista who must grind beans, steam milk, and pour water manually for every order, using a long series of if-else statements. If the latte recipe changes, every barista must relearn the steps. A coffee machine that receives an order type and handles the preparation internally solves the problem: the barista simply tells the machine what’s needed, and the machine encapsulates all the steps. Updating the recipe now means adjusting the machine, not every barista.
Laravel already leans on factories
Laravel’s own core components illustrate the pattern in action:
- Database –
ConnectionFactorydecides whether to build a MySQL or PostgreSQL connection. - Queues –
QueueManagercreates drivers for Redis, SQS, or other back-ends. - Filesystem –
FilesystemManagerproduces local or S3 disk instances. - Mail –
MailManagerresolves SMTP, Mailgun, or other mail drivers.
If the framework trusts factories for these critical services, custom code should follow suit.
When to introduce a factory
Use a factory when:
- Object creation involves multiple configuration steps or external services.
- Several interchangeable implementations exist (different payment gateways, storage providers, etc.).
- The list of possible implementations is expected to grow.
Avoid a factory when:
- Construction is a single, immutable
newcall with no extra setup. - Only one implementation will ever be used, making the extra abstraction unnecessary overhead.
Quick walk-through: refactoring a payment controller
- Define an interface –
PaymentProcessorInterfacewith aprocess(array $data)method. - Implement concrete classes –
StripePaymentProcessor,PayPalPaymentProcessor, each fulfilling the interface. - Create a factory –
PaymentProcessorFactorywith amake(string $driver): PaymentProcessorInterfacemethod. Inside, use aswitchor a map to return the appropriate class, injecting any required services from Laravel’s service container. - Inject the factory – In the controller’s constructor, type-hint
PaymentProcessorFactory. Laravel will resolve it automatically. - Use the factory –
$processor = $this->processorFactory->make('stripe'); $processor->process($request->all());
Now the controller never mentions new or a concrete processor. Adding a new gateway means creating a class and extending the factory’s map—no changes to the controller.
Potential downsides and how to mitigate them
Die Hauptkritik an Factories ist die zusätzliche Indirektion, die bei trivialen Objekten umständlich wirken kann. Das Over-Engineering eines einfachen Services kann die Codebasis aufblähen, ohne einen echten Nutzen zu bringen. Der Schlüssel liegt in der Bewertung der Konstruktionskomplexität: Wenn ein Objekt lediglich ein einfacher Datenträger ohne Abhängigkeiten ist, kann ein direktes new akzeptabel sein. Zudem können Factories zu einem Sammelbecken für nicht zusammengehörige Logik werden. Wenn man Factories auf die Objekterzeugung konzentriert und die Konfiguration an dedizierte Service Provider delegiert, bleibt die Übersichtlichkeit gewahrt.
Worauf Sie als Nächstes achten sollten
- Service-Container-Bindings – Der Container von Laravel kann Factories automatisch auflösen, wenn Sie das Interface in einem Service Provider an die Factory-Klasse binden.
- Auto-Discovery – Einige Pakete stellen eigene Factories bereit; achten Sie auf Namenskollisionen.
- Teststrategien – Ersetzen Sie beim Unit-Testing von Controllern die Factory durch einen Mock, der einen gestubbten Processor zurückgibt, um sicherzustellen, dass die Tests schnell und isoliert bleiben.
Fazit
Das Ersetzen verstreuter new-Aufrufe durch eine gut platzierte Factory zentralisiert die Konstruktion, reduziert die Kopplung und bringt den eigenen Code mit der Designphilosophie von Laravel in Einklang. Für Teams, die Wachstum erwarten – neue Zahlungsanbieter, Storage-Backends oder Plug-in-ähnliche Komponenten –, ist das Factory Method Pattern eine kostengünstige Investition, die sich durch Flexibilität und Sicherheit auszahlt. Ihr zukünftiges Ich und jeder, der den Code übernimmt, wird es Ihnen danken.
