Skip to content

Factory Method

The Factory Method Pattern is a creational design pattern that defines an interface for creating an object but allows subclasses to decide which concrete class to instantiate.

Instead of instantiating objects directly, the creation logic is delegated to factories, making the system easier to extend without modifying existing code.


Structure

Role Example Responsibility
Product Parser Defines the interface for objects created by the factory
Concrete Product CSVParser, JSONParser, XMLParser Implements the product interface
Creator ParserFactory Declares the factory method
Concrete Creator CSVParserFactory, JSONParserFactory, XMLParserFactory Implements the factory method to create a specific product

Steps

  1. Create a Product interface
  2. Implement Concrete Products
  3. Create a Creator interface with a factory method
  4. Implement Concrete Creators that return specific products
classDiagram
    class Parser {
        <<interface>>
        +parse(data)
    }

    class ParserFactory {
        <<interface>>
        +createParser()
    }

    class CSVParserFactory {
        +createParser()
    }

    class JSONParserFactory {
        +createParser()
    }

    class XMLParserFactory {
        +createParser()
    }

    class CSVParser {
        +parse(data)
    }

    class JSONParser {
        +parse(data)
    }

    class XMLParser {
        +parse(data)
    }

    CSVParserFactory ..|> ParserFactory
    JSONParserFactory ..|> ParserFactory
    XMLParserFactory ..|> ParserFactory

    CSVParser ..|> Parser
    JSONParser ..|> Parser
    XMLParser ..|> Parser

    CSVParserFactory ..> CSVParser : creates
    JSONParserFactory ..> JSONParser : creates
    XMLParserFactory ..> XMLParser : creates

The client interacts only with the factory interface (ParserFactory) and product interface (Parser), while concrete factories (CSVParserFactory) decide which concrete product (CSVParser) to instantiate.


Example: Data Parsers

Each concrete factory decides which specific Parser implementation to create, while the client code remains decoupled from the concrete classes.


Product Interface

Parser.php
<?php

declare(strict_types=1);

namespace DesignPattern\Creational\FactoryMethod\Parser;

interface Parser
{
    /**
     * @param string $data
     * @return array<mixed>
     */
    public function parse(string $data): array;
}

Creator Interface

ParserFactory.php
<?php

declare(strict_types=1);

namespace DesignPattern\Creational\FactoryMethod\Parser\ParserFactory;

use DesignPattern\Creational\FactoryMethod\Parser\Parser;

interface ParserFactory
{
    /**
     * @return Parser
     */
    public function createParser(): Parser;
}

Concrete Creators

CSVParserFactory.php
    <?php

    declare(strict_types=1);

    namespace DesignPattern\Creational\FactoryMethod\Parser\ParserFactory;

    use DesignPattern\Creational\FactoryMethod\Parser\CSVParser;
    use DesignPattern\Creational\FactoryMethod\Parser\Parser;

    class CSVParserFactory implements ParserFactory
    {
        /**
         * @return Parser
         */
        public function createParser(): Parser
        {
            return new CSVParser();
        }
    }
JSONParserFactory.php
    <?php

    declare(strict_types=1);

    namespace DesignPattern\Creational\FactoryMethod\Parser\ParserFactory;

    use DesignPattern\Creational\FactoryMethod\Parser\JSONParser;
    use DesignPattern\Creational\FactoryMethod\Parser\Parser;

    class JSONParserFactory implements ParserFactory
    {
        /**
         * @return Parser
         */
        public function createParser(): Parser
        {
            return new JSONParser();
        }
    }
XMLParserFactory.php
    <?php

    declare(strict_types=1);

    namespace DesignPattern\Creational\FactoryMethod\Parser\ParserFactory;

    use DesignPattern\Creational\FactoryMethod\Parser\XMLParser;
    use DesignPattern\Creational\FactoryMethod\Parser\Parser;

    class XMLParserFactory implements ParserFactory
    {
        /**
         * @return Parser
         */
        public function createParser(): Parser
        {
            return new XMLParser();
        }
    }

Concrete Products

XMLParser.php
    <?php

    declare(strict_types=1);

    namespace DesignPattern\Creational\FactoryMethod\Parser;

    class XMLParser implements Parser
    {
        /**
         * @param string $data
         * @return array<int, array<string, string>>
         * @throws \JsonException
         * @throws \UnexpectedValueException
         */
        public function parse(string $data): array
        {
            $xml = simplexml_load_string($data);

            if ($xml === false) {
                throw new \UnexpectedValueException("Unable to parse XML content");
            }

            $result = [];
            foreach ($xml->children() as $child) {
                $fields = [];
                foreach ($child as $key => $value) {
                    $fields[$key] = (string) $value;
                }
                $result[] = $fields;
            }

            return $result;
        }
    }
JSONParser.php
    <?php

    declare(strict_types=1);

    namespace DesignPattern\Creational\FactoryMethod\Parser;

    use JsonException;

    class JSONParser implements Parser
    {
        /**
         * @return array<mixed>
         * @throws JsonException
         * @throws \UnexpectedValueException
         */
        public function parse(string $data): array
        {
            $result = json_decode($data, true, 512, JSON_THROW_ON_ERROR);
            if (!is_array($result)) {
                throw new \UnexpectedValueException('Expected JSON to decode to array.');
            }
            return $result;
        }
    }
CSVParser.php
    <?php

    declare(strict_types=1);

    namespace DesignPattern\Creational\FactoryMethod\Parser;

    class CSVParser implements Parser
    {
        public function parse(string $data): array
        {
            $lines = explode(PHP_EOL, $data);
            $result = [];
            foreach ($lines as $line) {
                if (trim($line) !== '') {
                    // empty escape: required by PHPStan and avoids PHP 8.4 deprecation
                    $result[] = str_getcsv($line, ',', '"', '');
                }
            }
            return $result;
        }
    }

Tests

ParserTest.php
<?php

declare(strict_types=1);

namespace DesignPattern\Creational\FactoryMethod\Parser;

interface Parser
{
    /**
     * @param string $data
     * @return array<mixed>
     */
    public function parse(string $data): array;
}