Factory Method Pattern

Factory method allows subclassing to implement different ways to create objects. It depends on abstractions, not concrete classes.


Purpose

The good point over the SimpleFactory is you can subclass it to implement different ways to create objects.

For simple cases, this abstract class could be just an interface.

This pattern is a "real" Design Pattern because it achieves the Dependency Inversion principle a.k.a the "D" in SOLID principles.

It means the FactoryMethod class depends on abstractions, not concrete classes. This is the real trick compared to SimpleFactory or StaticFactory

UML


Code

Logger


namespace DesignPatterns\Creational\FactoryMethod;

interface Logger
{
    public function log(string $message);
}

LoggerFactory


namespace DesignPatterns\Creational\FactoryMethod;

interface LoggerFactory
{
    public function createLogger(): Logger;
}

FileLogger


namespace DesignPatterns\Creational\FactoryMethod;

class FileLogger implements Logger
{
    public function __construct(private string $filePath)
    {
    }

    public function log(string $message)
    {
        file_put_contents($this->filePath, $message . PHP_EOL, FILE_APPEND);
    }
}

StdOutLogger


namespace DesignPatterns\Creational\FactoryMethod;

class StdoutLogger implements Logger
{
    public function log(string $message)
    {
        echo $message;
    }
}

FileLoggerFactory


namespace DesignPatterns\Creational\FactoryMethod;

class FileLoggerFactory implements LoggerFactory
{
    public function __construct(private string $filePath)
    {
    }

    public function createLogger(): Logger
    {
        return new FileLogger($this->filePath);
    }
}

StdOutLoggerFactory


namespace DesignPatterns\Creational\FactoryMethod;

class StdoutLoggerFactory implements LoggerFactory
{
    public function createLogger(): Logger
    {
        return new StdoutLogger();
    }
}

Tests


public function testCanCreateStdoutLogging()
{
    $loggerFactory = new StdoutLoggerFactory();
    $logger = $loggerFactory->createLogger();

    $this->assertInstanceOf(StdoutLogger::class, $logger);
}

public function testCanCreateFileLogging()
{
    $loggerFactory = new FileLoggerFactory(sys_get_temp_dir());
    $logger = $loggerFactory->createLogger();

    $this->assertInstanceOf(FileLogger::class, $logger);
}