Operator new w kodzie produkcyjnym to sygnał ostrzegawczy. Klasa tworząca instancje innych klas bezpośrednio jest z nimi silnie sprzężona – trudno ją testować, trudno rozszerzać. Factory Method to wzorzec, który deleguje odpowiedzialność za tworzenie obiektów do dedykowanych klas. W Magento 2 fabryki są wszechobecne – i generowane automatycznie.
Problem ze słowem kluczowym new
<?php
class OrderProcessor
{
public function process(array $data): void
{
// Silne sprzężenie - nie możesz podmienić EmailNotifier w teście
$notifier = new EmailNotifier();
$logger = new FileLogger('/var/log/orders.log');
// ... logika procesowania
$notifier->send($data['email'], 'Zamówienie przyjęte');
$logger->log('info', 'Order processed');
}
}
Testowanie tej klasy wymaga realnego systemu plików i serwera SMTP. Factory Method pozwala oddzielić logikę tworzenia od logiki biznesowej.
Simple Factory – punkt wejścia
Zanim przejdziemy do pełnego Factory Method, warto zobaczyć prostszą wersję – Simple Factory. To jeszcze nie wzorzec GoF, ale często wystarczy:
<?php
interface NotifierInterface
{
public function send(string $recipient, string $message): void;
}
class EmailNotifier implements NotifierInterface
{
public function send(string $recipient, string $message): void
{
// wysyłka emaila
}
}
class SmsNotifier implements NotifierInterface
{
public function send(string $recipient, string $message): void
{
// wysyłka SMS
}
}
class SlackNotifier implements NotifierInterface
{
public function send(string $recipient, string $message): void
{
// powiadomienie Slack
}
}
// Simple Factory - centralizuje logikę tworzenia
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}"),
};
}
}
// Użycie
$factory = new NotifierFactory();
$notifier = $factory->create('email');
$notifier->send('jan@example.com', 'Zamówienie przyjęte');
Factory Method – wzorzec GoF
Właściwy Factory Method przenosi decyzję o tworzeniu obiektu do klas potomnych. Klasa bazowa definiuje interfejs fabryki, podklasy decydują co tworzyć:
<?php
// Abstrakcyjna klasa z factory method
abstract class OrderHandler
{
// Factory method - podklasy implementują
abstract protected function createNotifier(): NotifierInterface;
// Logika biznesowa używa notifiera bez wiedzy o jego typie
public function handle(array $orderData): void
{
$notifier = $this->createNotifier();
// ... procesowanie zamówienia
$notifier->send($orderData['contact'], 'Zamówienie #' . $orderData['id'] . ' przyjęte');
}
}
// Konkretne implementacje decydują jaki notifier tworzyć
class EmailOrderHandler extends OrderHandler
{
protected function createNotifier(): NotifierInterface
{
return new EmailNotifier();
}
}
class SmsOrderHandler extends OrderHandler
{
protected function createNotifier(): NotifierInterface
{
return new SmsNotifier();
}
}
// Użycie
$handler = new EmailOrderHandler();
$handler->handle(['id' => 123, 'contact' => 'jan@example.com']);
Factory z DI – testowalny kod
W praktyce fabryki wstrzykuje się przez DI, co umożliwia podmianę w testach:
<?php
interface NotifierFactoryInterface
{
public function create(string $type): NotifierInterface;
}
class NotifierFactory implements NotifierFactoryInterface
{
// Rejestr dostępnych notifierów wstrzykiwany przez DI
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 NotifierFactoryInterface $notifierFactory
) {}
public function process(array $orderData): void
{
$type = $orderData['notification_type'] ?? 'email';
$notifier = $this->notifierFactory->create($type);
$notifier->send($orderData['contact'], 'Zamówienie przyjęte');
}
}
Konfiguracja fabryki przez di.xml – rejestr notifierów przez argumenty:
<?xml version="1.0"?>
<config>
<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>
</config>
Dodanie nowego notifiera to teraz tylko nowa klasa i jedna linia w di.xml – zero zmian w istniejącym kodzie.
Auto-generowane fabryki w Magento 2
Magento 2 automatycznie generuje klasę Factory dla każdego modelu jeśli poprosisz o nią w konstruktorze z sufiksem Factory:
<?php
use Magento\Catalog\Model\ProductFactory;
class MyService
{
public function __construct(
private ProductFactory $productFactory
) {}
public function createProduct(array $data): \Magento\Catalog\Model\Product
{
// create() zawsze zwraca nową instancję
return $this->productFactory->create(['data' => $data]);
}
}
Plik Magento\Catalog\Model\ProductFactory nie istnieje w repozytorium Magento. Jest generowany przez setup:di:compile do katalogu generated/ na podstawie klasy Product. To jeden z najczęściej używanych mechanizmów w ekosystemie Magento 2.
Podsumowanie
Factory Method oddziela logikę tworzenia obiektów od logiki biznesowej. Kod staje się łatwiejszy do testowania – możesz wstrzyknąć mock fabryki zamiast realnych implementacji. W Magento 2 auto-generowane fabryki są wszechobecne i stanowią standardowy sposób tworzenia nowych instancji modeli, eliminując bezpośrednie użycie new w kodzie produkcyjnym.
