Factory Pattern
debt(d7/e5/b5/t5)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints note automated=no and the code_pattern is 'new SomeComplexClass(...) scattered across codebase' — PHPStan (the only listed tool) can flag type issues but won't identify missing factory abstractions or factories returning concrete types instead of interfaces. Recognising misuse requires deliberate code review to spot scattered construction or coupling to concrete classes.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix says 'extract object creation logic into a factory' — this is not a one-line swap. Common mistakes like factories returning concrete types instead of interfaces, or scattered new X / new Y calls across the codebase, require identifying all call sites, introducing an interface, creating a factory class, and updating callers. This spans multiple files but is typically contained within one component rather than being a full cross-cutting architectural rework.
Closest to 'persistent productivity tax' (b5). The applies_to covers web, cli, and queue-worker contexts, meaning the factory abstraction (or its absence) affects all work streams. A well-applied factory reduces burden; but common mistakes like god-object factories with dozens of create methods, or factories coupled to concrete types, impose an ongoing tax on every future maintainer who must understand and navigate the factory layer. The pattern itself has moderate reach.
Closest to 'notable trap (a documented gotcha most devs eventually learn)' (t5). The misconception field explicitly states that Factory Method and Abstract Factory are commonly conflated — developers assume they are 'just variations' when they solve fundamentally different scoping problems. Additionally, common mistakes show developers often return concrete types instead of interfaces, defeating the pattern's purpose. These are documented gotchas that most developers learn through experience, not catastrophic but meaningfully deceptive.
Also Known As
TL;DR
Explanation
Factory patterns (Factory Method, Abstract Factory, Static Factory) abstract object creation logic from consuming code. Instead of new ConcreteClass(), call a factory that decides which concrete class to instantiate based on parameters or configuration. This follows Dependency Inversion (depend on the interface returned, not the concrete class), makes construction logic testable, and makes swapping implementations easy. In PHP, static factory methods on value objects (Money::fromFloat(9.99, 'GBP')) are a lightweight pattern that centralises validation and construction.
Common Misconception
Why It Matters
Common Mistakes
- Factories that return concrete types instead of interfaces — callers are still coupled to the implementation.
- Static factory methods that cannot be overridden in subclasses — use instance factories for flexibility.
- Over-using factories for simple objects that could just use new — factories are for creation that involves logic.
- Factory classes with dozens of create methods — a sign the factory has grown into a god object.
Avoid When
- You only ever create one type of object and the creation logic is trivial — a factory adds indirection with no benefit.
- The factory itself becomes a god object that knows about every concrete class — defeats the purpose of abstraction.
- Simple value objects with no dependencies — new Point(1, 2) is clearer than PointFactory::create(1, 2).
When To Use
- The concrete class to instantiate is determined at runtime based on configuration or input.
- Object creation involves complex setup, dependency resolution, or conditional branching.
- You want to enforce that objects are always created in a valid state with required dependencies.
- Testing — factories make it easy to swap real implementations for test doubles.
Code Examples
// Object creation scattered across codebase — hard to change:
$mailer = new SmtpMailer('smtp.example.com', 587, 'user', 'pass');
// Same constructor call in 12 places — change SMTP provider = 12 edits
// Factory — centralised:
class MailerFactory {
public function create(): MailerInterface {
return new SmtpMailer(config('mail.host'), config('mail.port'));
}
}
interface Logger {
public function log(string $msg): void;
}
class LoggerFactory {
public static function create(string $driver): Logger {
return match($driver) {
'file' => new FileLogger(),
'database' => new DatabaseLogger(),
'null' => new NullLogger(),
default => throw new \InvalidArgumentException("Unknown driver: $driver"),
};
}
}
$logger = LoggerFactory::create($_ENV['LOG_DRIVER'] ?? 'file');