Abstract Factory Pattern
debt(d7/e5/b5/t5)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints indicate automated detection is 'no', and while phpstan is listed, it cannot reliably detect when you should be using Abstract Factory or when you're mixing incompatible product families. The code pattern (new ...Gateway && new ...Request from different providers) requires manual review or custom static analysis rules. Misuse — mixing objects from different families — compiles fine and often only manifests at runtime, though typically during testing rather than silently in production, so d7 rather than d9.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix says to 'introduce an Abstract Factory and inject it instead of instantiating objects directly,' which involves creating a factory interface, concrete factory implementations, updating all call sites that directly instantiate related objects, and wiring the factory through dependency injection. This typically touches multiple files within a component or module. It's not quite cross-cutting (e7) since it's usually localized to one subsystem's creation logic, but it's clearly more than a simple parameterized fix.
Closest to 'persistent productivity tax' (b5). Abstract Factory applies across web, cli, and queue-worker contexts and is a creational design pattern that, once adopted, shapes how all product families are created and extended. Adding a new product type requires updating every concrete factory. It doesn't quite define the system's shape (b9/b7), but it imposes a persistent tax: every new product variant or family requires coordinated changes across factory interfaces and implementations, and developers must understand the pattern to work with the affected code.
Closest to 'notable trap (a documented gotcha most devs eventually learn)' (t5). The misconception field explicitly states that developers confuse Abstract Factory with Factory Method, believing them interchangeable. This is a well-documented gotcha in the patterns community. Factory Method creates a single product; Abstract Factory creates families of related products. Competent developers who know Factory Method will often reach for it when Abstract Factory is needed (or vice versa), and common_mistakes confirm overuse when only one product varies. It's not quite t7 because the distinction is well-covered in pattern literature, but it genuinely trips up intermediate developers.
Also Known As
TL;DR
Explanation
The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. Each concrete factory produces a consistent set of products (e.g., StripeGateway, StripeRequest, StripeResponse). This ensures that objects designed to work together are never mixed with incompatible ones. It is commonly used when systems must support multiple interchangeable configurations (e.g., payment providers, UI themes, database drivers). Unlike Factory Method, which creates a single product, Abstract Factory guarantees consistency across a full product family.
Common Misconception
Why It Matters
Common Mistakes
- Using Abstract Factory when only one product varies — adds unnecessary complexity.
- Creating factories that return unrelated objects — defeats the purpose of product families.
- Exposing concrete factories directly instead of injecting abstractions.
- Mixing factory-created objects with manually instantiated ones.
Avoid When
- Only one product type varies — use Factory Method instead.
- Products are independent and do not need to be used together.
- System has a single fixed implementation with no variation.
- The abstraction adds more complexity than the variation justifies.
When To Use
- Multiple related objects must always be used together.
- System must support interchangeable product families (e.g., providers, themes).
- You want to enforce compatibility between created objects.
- Configuration should swap entire implementations, not individual classes.
Code Examples
// ❌ Mixing incompatible product families
$gateway = new StripeGateway();
$request = new BraintreePaymentRequest(); // runtime mismatch
interface PaymentFactory {
public function createGateway(): GatewayInterface;
public function createRequest(Money $amount): RequestInterface;
public function createResponse(array $data): ResponseInterface;
}
final class StripeFactory implements PaymentFactory {
public function createGateway(): GatewayInterface {
return new StripeGateway();
}
public function createRequest(Money $amount): RequestInterface {
return new StripeRequest($amount);
}
public function createResponse(array $data): ResponseInterface {
return new StripeResponse($data);
}
}
// Usage
function processPayment(PaymentFactory $factory, Money $amount) {
$gateway = $factory->createGateway();
$request = $factory->createRequest($amount);
$rawResponse = $gateway->send($request);
return $factory->createResponse($rawResponse);
}