The new operator in production code is a warning sign. A class that directly instantiates other classes is tightly coupled to them – hard to test, hard to extend. Factory Method delegates the responsibility for object creation to dedicated classes. In Magento 2 factories are everywhere – and generated automatically.
The problem with the new keyword
<?php
class OrderProcessor
{
public function process(array $data): void
{
// Tight coupling - cannot replace EmailNotifier in a test
$notifier = new EmailNotifier();
$logger = new FileLogger('/var/log/orders.log');
$notifier->send($data['email'], 'Order received');
$logger->log('info', 'Order processed');
}
}
Simple Factory – the entry point
<?php
interface NotifierInterface
{
public function send(string $recipient, string $message): void;
}
class EmailNotifier implements NotifierInterface { /* ... */ }
class SmsNotifier implements NotifierInterface { /* ... */ }
class SlackNotifier implements NotifierInterface { /* ... */ }
// Simple Factory - centralises creation logic
class NotifierFactory
{
public function create(string $type): NotifierInterface
{
return match ($type) {
'email' => new EmailNotifier(),
'sms' => new SmsNotifier(),
'slack' => new SlackNotifier(),
default => throw new \InvalidArgumentException("Unknown notifier: {$type}"),
};
}
}
$factory = new NotifierFactory();
$notifier = $factory->create('email');
$notifier->send('jan@example.com', 'Order received');
Factory Method – the GoF pattern
<?php
abstract class OrderHandler
{
// Factory method - subclasses implement this
abstract protected function createNotifier(): NotifierInterface;
// Business logic uses the notifier without knowing its type
public function handle(array $orderData): void
{
$notifier = $this->createNotifier();
$notifier->send($orderData['contact'], 'Order #' . $orderData['id'] . ' received');
}
}
class EmailOrderHandler extends OrderHandler
{
protected function createNotifier(): NotifierInterface
{
return new EmailNotifier();
}
}
class SmsOrderHandler extends OrderHandler
{
protected function createNotifier(): NotifierInterface
{
return new SmsNotifier();
}
}
$handler = new EmailOrderHandler();
$handler->handle(['id' => 123, 'contact' => 'jan@example.com']);
Factory with DI – testable code
<?php
class NotifierFactory
{
public function __construct(private array $notifiers = []) {}
public function create(string $type): NotifierInterface
{
if (!isset($this->notifiers[$type])) {
throw new \InvalidArgumentException("Notifier '{$type}' not registered");
}
return $this->notifiers[$type];
}
}
class OrderProcessor
{
public function __construct(private NotifierFactory $notifierFactory) {}
public function process(array $orderData): void
{
$type = $orderData['notification_type'] ?? 'email';
$notifier = $this->notifierFactory->create($type);
$notifier->send($orderData['contact'], 'Order received');
}
}
<!-- Register notifiers via di.xml arguments -->
<type name="Vendor\Module\Model\NotifierFactory">
<arguments>
<argument name="notifiers" xsi:type="array">
<item name="email" xsi:type="object">Vendor\Module\Model\EmailNotifier</item>
<item name="sms" xsi:type="object">Vendor\Module\Model\SmsNotifier</item>
<item name="slack" xsi:type="object">Vendor\Module\Model\SlackNotifier</item>
</argument>
</arguments>
</type>
Adding a new notifier means a new class and one line in di.xml – zero changes to existing code.
Auto-generated factories in Magento 2
<?php
use Magento\Catalog\Model\ProductFactory;
class MyService
{
public function __construct(
private ProductFactory $productFactory
) {}
public function createProduct(array $data): \Magento\Catalog\Model\Product
{
// create() always returns a new instance
return $this->productFactory->create(['data' => $data]);
}
}
Magento\Catalog\Model\ProductFactory does not exist in the Magento repository. It is generated by setup:di:compile into the generated/ directory based on the Product class. This is one of the most widely-used mechanisms in the Magento 2 ecosystem.
Summary
Factory Method separates object creation logic from business logic. Code becomes easier to test – inject a factory mock instead of real implementations. In Magento 2 auto-generated factories are the standard way to create new model instances, eliminating direct new in production code.
