Factory Method — delegating instantiation to subclasses
Concept
The Factory Method pattern defines an interface for creating an object but lets subclasses decide which class to instantiate. Rather than calling new ConcreteClass() directly, you call a factory method that returns an object conforming to an interface. The creation logic is encapsulated, and callers depend only on the abstraction.
The problem it solves is tight coupling to concrete types at construction time. When a class directly instantiates its collaborators with new, it is permanently coupled to that implementation. Adding a second payment provider means modifying every place that creates payment processors. The Factory Method moves that decision to a single location — the factory — and isolates the rest of the code from it.
UML structure: A Creator abstract class declares the factory method createProduct(): ProductInterface. Concrete creator subclasses (StripeCreator, PayPalCreator) override it and return concrete products (StripeGateway, PayPalGateway). The Creator may contain template methods that call createProduct() internally, so the algorithm that uses the product is defined once in the parent, and only the creation varies in the child.
When to use: when a class cannot anticipate which class it must create; when you want subclasses to specify the objects they create; when you want to localize the knowledge of which class gets created. When NOT to use: when you only ever have one concrete product — then a plain factory function or direct instantiation is simpler. Do not create a factory hierarchy for a single class just because "factories are good practice."
Laravel uses Factory Method internally in Illuminate\Database\Connectors\ConnectionFactory. The make() method inspects the driver config key and delegates to createConnection(), which concrete connector subclasses (MySqlConnector, PostgresConnector, SQLiteConnector) implement. This is classic Factory Method: the framework defines the interface and the algorithm (make → resolve connector → create PDO → wrap in Connection), while each driver subclass controls the instantiation.
| Factory Method | Simple Factory (not a GoF pattern) |
|---|---|
| Abstract creator class with subclasses | Single class with a switch statement |
| Extensible — add a subclass, no modification | Modification required to add new types |
| Follows Open/Closed principle | Violates Open/Closed principle |
| More structure, better for frameworks | Fine for small, stable sets of types |
Code Example
<?php
declare(strict_types=1);
// ── Interfaces ─────────────────────────────────────────────────────────────
interface PaymentGatewayInterface
{
public function charge(int $amountCents, string $currency, string $token): PaymentResult;
public function refund(string $transactionId, int $amountCents): RefundResult;
}
// ── Concrete Products ───────────────────────────────────────────────────────
final class StripeGateway implements PaymentGatewayInterface
{
public function __construct(private readonly string $secretKey) {}
public function charge(int $amountCents, string $currency, string $token): PaymentResult
{
// Real Stripe SDK call would go here
return new PaymentResult(id: 'ch_' . uniqid(), status: 'succeeded');
}
public function refund(string $transactionId, int $amountCents): RefundResult
{
return new RefundResult(id: 're_' . uniqid(), status: 'succeeded');
}
}
final class PayPalGateway implements PaymentGatewayInterface
{
public function __construct(
private readonly string $clientId,
private readonly string $clientSecret,
) {}
public function charge(int $amountCents, string $currency, string $token): PaymentResult
{
return new PaymentResult(id: 'PAY-' . uniqid(), status: 'approved');
}
public function refund(string $transactionId, int $amountCents): RefundResult
{
return new RefundResult(id: 'REF-' . uniqid(), status: 'completed');
}
}
// ── Abstract Creator ────────────────────────────────────────────────────────
abstract class PaymentProcessor
{
// Factory Method — subclasses decide which gateway to create
abstract protected function createGateway(): PaymentGatewayInterface;
// Template method that uses the factory method
final public function processOrderPayment(Order $order, string $token): PaymentResult
{
$gateway = $this->createGateway();
$result = $gateway->charge(
amountCents: $order->totalCents(),
currency: $order->currency(),
token: $token,
);
if ($result->succeeded()) {
$order->markAsPaid($result->transactionId());
}
return $result;
}
final public function refundOrder(Order $order, string $transactionId): RefundResult
{
return $this->createGateway()->refund($transactionId, $order->totalCents());
}
}
// ── Concrete Creators ───────────────────────────────────────────────────────
final class StripePaymentProcessor extends PaymentProcessor
{
protected function createGateway(): PaymentGatewayInterface
{
return new StripeGateway(secretKey: config('services.stripe.secret'));
}
}
final class PayPalPaymentProcessor extends PaymentProcessor
{
protected function createGateway(): PaymentGatewayInterface
{
return new PayPalGateway(
clientId: config('services.paypal.client_id'),
clientSecret: config('services.paypal.client_secret'),
);
}
}
// ── Laravel Service Provider wiring ────────────────────────────────────────
// In AppServiceProvider::register():
$this->app->bind(PaymentProcessor::class, function (): PaymentProcessor {
return match (config('payment.driver')) {
'stripe' => new StripePaymentProcessor(),
'paypal' => new PayPalPaymentProcessor(),
default => throw new \InvalidArgumentException('Unknown payment driver'),
};
});
// ── Usage in an application service ────────────────────────────────────────
final class CheckoutService
{
public function __construct(
private readonly PaymentProcessor $processor, // inject the creator
) {}
public function checkout(Order $order, string $paymentToken): void
{
$result = $this->processor->processOrderPayment($order, $paymentToken);
if (! $result->succeeded()) {
throw new PaymentFailedException($result->errorMessage());
}
}
}Interview Q&A
Q: What is the difference between Factory Method and Simple Factory, and when would you choose each?
A Simple Factory is a single class with a static or instance method that contains a switch/if chain to decide what to instantiate — it is not a GoF pattern, just a helper function. Factory Method is a class hierarchy where the decision is made by subclassing: you add a new creator subclass rather than modifying an existing switch. Choose Simple Factory when the set of types is small and stable and you don't need extensibility via subclassing. Choose Factory Method when you are building a framework or library where third parties need to plug in new types without touching your code — they extend the creator, they don't modify it.
Q: How does Laravel use the Factory Method pattern internally?
Illuminate\Database\Connectors\ConnectionFactory is the clearest example. Its make() method orchestrates the full connection setup, but delegates the actual connector creation to createConnector(array $config), which returns a ConnectorInterface specific to the driver (MySqlConnector, PostgresConnector, SQLiteConnector, SqlServerConnector). The createConnection() method similarly delegates to driver-specific connection classes. The framework defines the algorithm; concrete drivers provide the product. The same pattern appears in Illuminate\Cache\CacheManager (store creation) and Illuminate\Queue\QueueManager (queue connection creation).
Q: Can the Factory Method violate the Open/Closed Principle?
Only if it is implemented poorly. When the abstract creator is in a library and you extend it in your application code, adding a new product type requires no modification to the library — you add a new subclass pair (creator + product). This is exactly Open/Closed: open for extension (subclass), closed for modification (no changes to existing classes). The violation occurs when developers implement Factory Method as a switch statement inside the factory method itself (if ($type === 'A') return new A()), which is actually a Simple Factory. Every new type requires opening and modifying the creator class, breaking OCP.