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 – ConnectionFactory decides whether to build a MySQL or PostgreSQL connection.
  • Queues – QueueManager creates drivers for Redis, SQS, or other back-ends.
  • Filesystem – FilesystemManager produces local or S3 disk instances.
  • Mail – MailManager resolves 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 new call with no extra setup.
  • Only one implementation will ever be used, making the extra abstraction unnecessary overhead.

Quick walk-through: refactoring a payment controller

  1. Define an interface – PaymentProcessorInterface with a process(array $data) method.
  2. Implement concrete classes – StripePaymentProcessor, PayPalPaymentProcessor, each fulfilling the interface.
  3. Create a factory – PaymentProcessorFactory with a make(string $driver): PaymentProcessorInterface method. Inside, use a switch or a map to return the appropriate class, injecting any required services from Laravel’s service container.
  4. Inject the factory – In the controller’s constructor, type-hint PaymentProcessorFactory. Laravel will resolve it automatically.
  5. 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

Głównym zarzutem wobec fabryk jest wprowadzanie dodatkowej pośredniości, co może wydawać się zbyt rozwlekłe w przypadku trywialnych obiektów. Nadmierne komplikowanie (over-engineering) prostego serwisu może niepotrzebnie powiększyć bazę kodu bez realnych korzyści. Kluczem jest ocena złożoności procesu tworzenia: jeśli obiekt jest zwykłym nośnikiem danych bez żadnych zależności, bezpośrednie użycie new może być dopuszczalne. Ponadto fabryki mogą stać się „śmietnikiem” dla niepowiązanej logiki. Skupienie fabryk na tworzeniu obiektów i delegowanie konfiguracji do dedykowanych dostawców usług (service providers) pozwala zachować przejrzystość.

Na co zwrócić uwagę w następnej kolejności

  • Wiązania kontenera usług (Service container bindings) – kontener Laravel może automatycznie rozwiązywać fabryki, jeśli powiążesz interfejs z klasą fabryki w dostawcy usług (service provider).
  • Auto-discovery – niektóre pakiety udostępniają własne fabryki; należy uważać na kolizje nazw.
  • Strategie testowania – podczas testów jednostkowych kontrolerów zastąp fabrykę obiektem typu mock, który zwraca stubowany procesor, co zapewni szybkość i izolację testów.

Podsumowanie

Zastąpienie rozproszonych wywołań new dobrze umiejscowioną fabryką centralizuje proces tworzenia, zmniejsza sprzężenie (coupling) i sprawia, że własny kod jest zgodny z filozofią projektową Laravel. Dla zespołów przewidujących rozwój – nowych dostawców płatności, systemów przechowywania danych czy jakichkolwiek komponentów typu plug-in – wzorzec Factory Method jest niskokosztową inwestycją, która zwraca się w postaci elastyczności i pewności działania. Twoje przyszłe „ja” oraz każdy, kto przejmie ten kod, podziękuje Ci za to.