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

ข้อวิจารณ์หลักของ factory คือการเพิ่ม indirection ซึ่งอาจทำให้รู้สึกเยิ่นเย้อเกินไปสำหรับออบเจกต์ที่เรียบง่าย การทำ Over-engineering กับ service ง่ายๆ อาจทำให้ codebase บวมขึ้นโดยไม่เกิดประโยชน์ที่แท้จริง หัวใจสำคัญคือการประเมินความซับซ้อนในการสร้าง: หากออบเจกต์เป็นเพียง plain data holder ที่ไม่มี dependency การใช้ new โดยตรงก็อาจเป็นเรื่องที่ยอมรับได้ นอกจากนี้ factory ยังอาจกลายเป็นที่รวมของ logic ต่างๆ ที่ไม่เกี่ยวข้องกัน การทำให้ factory มุ่งเน้นไปที่การสร้างออบเจกต์เพียงอย่างเดียว และส่งต่อการ configuration ให้กับ service provider ที่รับผิดชอบโดยเฉพาะ จะช่วยรักษาความชัดเจนของโค้ดไว้ได้

สิ่งที่ควรระวังต่อไป

  • Service container bindings – container ของ Laravel สามารถ resolve factory ได้โดยอัตโนมัติ หากคุณทำการ bind interface เข้ากับ factory class ใน service provider
  • Auto-discovery – แพ็กเกจบางตัวอาจมีการเปิดใช้งาน factory ของตัวเอง ดังนั้นควรระวังเรื่อง naming collisions
  • Testing strategies – เมื่อทำการ unit testing controllers ให้แทนที่ factory ด้วย mock ที่คืนค่าเป็น stubbed processor เพื่อให้มั่นใจว่าการทดสอบยังคงรวดเร็วและ isolated

บทสรุป

การแทนที่การเรียกใช้ new ที่กระจัดกระจายด้วย factory ที่วางไว้อย่างเหมาะสม จะช่วยรวมศูนย์การสร้างออบเจกต์ ลด coupling และทำให้โค้ดที่คุณเขียนสอดคล้องกับปรัชญาการออกแบบของ Laravel สำหรับทีมที่คาดการณ์ถึงการเติบโตในอนาคต ไม่ว่าจะเป็นผู้ให้บริการชำระเงินรายใหม่, storage back-ends หรือคอมโพเนนต์รูปแบบ plug-in ใดๆ การใช้ Factory Method pattern ถือเป็นการลงทุนที่ต้นทุนต่ำแต่ให้ผลตอบแทนที่คุ้มค่าในด้านความยืดหยุ่นและความมั่นใจ ตัวคุณเองในอนาคต รวมถึงใครก็ตามที่ต้องมารับช่วงดูแลโค้ดต่อ จะต้องขอบคุณคุณอย่างแน่นอน