Factory Method in PHP: When Refactoring Leads to a Pattern

Factory Method is one of those patterns that many PHP developers encounter early, but fully understand much later. It is often introduced as a “creational pattern” with diagrams and inheritance hierarchies, yet in real projects it rarely starts that way.

In practice, Factory Method is almost never a design decision made upfront. It is a destination reached through refactoring—usually when simple object creation starts to interfere with otherwise stable business logic.

This article explains how Factory Method emerges naturally, what problems it actually solves, and where developers frequently misuse it.

Factory Method is often described as the “younger sibling” of Abstract Factory.

Here you can find article about Abstract factory: https://codecraftdiary.com/2025/12/07/abstract-factory-pattern-php/


Every PHP project begins with direct instantiation:

$exporter = new CsvExporter();
$exporter->export($data);
PHP

This is clean, readable, and perfectly correct.
No abstraction is needed because there is no variability yet.

The mistake many developers make is assuming that patterns should exist before problems do. In reality, the absence of patterns here is a sign of healthy code.


As requirements grow, object creation often becomes conditional:

if ($format === 'csv') {
    $exporter = new CsvExporter();
} elseif ($format === 'xlsx') {
    $exporter = new XlsxExporter();
} else {
    throw new RuntimeException('Unsupported format');
}

$exporter->export($data);
PHP

This code still works, but several problems appear:

  • Business logic is mixed with creation logic
  • Adding a new exporter requires modifying this method
  • The method now has multiple reasons to change

However, this situation still does not justify Factory Method.

At this stage, the problem is not patterns—it is responsibility.


The right response is not introducing a GoF pattern, but performing a simple refactoring:

protected function createExporter(string $format): Exporter
{
    if ($format === 'csv') {
        return new CsvExporter();
    }

    if ($format === 'xlsx') {
        return new XlsxExporter();
    }

    throw new RuntimeException('Unsupported format');
}
PHP

This step is critical. It separates what the code does from what it creates.

At this point, many developers stop—and often they should. If object creation remains stable and parameter-driven, a Strategy, a map, or even a simple switch is usually enough.

Factory Method only becomes relevant if creation itself must vary by context.


Before going any further, ask:

Will different variants of this class need to create different implementations, while the overall algorithm stays the same?

If the answer is no, Factory Method is the wrong tool.

If the answer is yes, Factory Method is likely the simplest correct solution.


Consider a scenario where you now have multiple export services, each following the same workflow:

  1. Validate data
  2. Create exporter
  3. Export
  4. Log result

The workflow is identical. Only the exporter differs.

This is where Factory Method naturally appears.


abstract class ExportService
{
    public function export(array $data): void
    {
        $this->validate($data);

        $exporter = $this->createExporter();
        $exporter->export($data);

        $this->log();
    }

    abstract protected function createExporter(): Exporter;

    protected function validate(array $data): void
    {
        // shared validation logic
    }

    protected function log(): void
    {
        // shared logging logic
    }
}
PHP

Notice what happened:

  • The algorithm is fixed
  • The creation step is deferred
  • Subclasses decide what to instantiate

This is Factory Method—not because the pattern was chosen, but because the structure demanded it.


class CsvExportService extends ExportService
{
    protected function createExporter(): Exporter
    {
        return new CsvExporter();
    }
}

class XlsxExportService extends ExportService
{
    protected function createExporter(): Exporter
    {
        return new XlsxExporter();
    }
}
PHP

Each subclass controls creation, while the base class controls behavior.

This separation is the essence of Factory Method.


1. Stable Core Logic

The export process is written once. It does not care which exporter exists, only that one exists.

2. Open for Extension

New exporters require new subclasses, not changes to existing logic.

3. Improved Testability

You can override createExporter() in tests and inject test doubles without touching production code.

class FakeExporter implements Exporter
{
    public bool $called = false;

    public function export(array $data): void
    {
        $this->called = true;
    }
}
PHP

“Factory Method replaces conditionals”

It does not.
If the choice depends on runtime data, Factory Method is usually the wrong abstraction.

“Factory Method is just a factory class”

Factory Method is about inheritance and variation, not about standalone factory objects.

“It should be public”

In almost all cases, the factory method should be protected. Making it public leaks internal construction details.


SituationBetter Choice
Parameter-based choiceStrategy / Map
One implementationNo pattern
Multiple related objectsAbstract Factory
Same workflow, different creationFactory Method

Factory Method sits in the middle: more flexible than direct instantiation, simpler than full factory hierarchies.


Avoid Factory Method if:

  • There is only one concrete implementation
  • Object creation depends purely on input values
  • You need to assemble complex object graphs
  • A simple data-driven approach is enough

Patterns add structure—but also weight.


Factory Method is not something you “apply”.
It is something your code grows into.

If you extract object creation, notice that subclasses need to customize it, and want to keep the main algorithm untouched—you are already there.

Used this way, Factory Method becomes one of the most practical and low-risk patterns in everyday PHP development.

Not because it is clever—but because it respects how software actually evolves.

Leave a Reply

Your email address will not be published. Required fields are marked *