Factory Method Design Pattern

Factory Method design pattern is a creational pattern which defines an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

Let’s see an example of payment system where we can pay with different types of payment methods. We will create a payment service and delegate its instantiation logic to Factories. Our project structure will look like this.

├── composer.json
├── composer.lock
├── src
│   ├── Factories
│   │   └── Payment
│   │       ├── PaymentFactoryInterface.php
│   │       ├── PaypalFactory.php
│   │       └── StripeFactory.php
│   └── Services
│       └── Payment
│           ├── PaymentInterface.php
│           ├── Paypal.php
│           └── Stripe.php
├── tests
│   └── PaymentTest.php
└── vendor

Create PaymentInterface first.

<?php declare(strict_types=1);

namespace App\Services\Payment;

interface PaymentInterface
{
    public function pay(): string;
}
    
?>

Paypal service will implement the PaymentInterface

<?php declare(strict_types=1);

namespace App\Services\Payment;
use App\Services\Payment\PaymentInterface;

class Paypal implements PaymentInterface
{
    public function pay(): string
    {
        // process payment via paypal
        return 'SUCCESS';
    }
}
?>

Similarly Sripe service will implement PaymentInterface.

<?php declare(strict_types=1);

namespace App\Services\Payment;
use App\Services\Payment\PaymentInterface;

class Stripe implements PaymentInterface
{
    public function pay(): string
    {
        // process payment via stripe
        return "SUCCESS";
    }
}
?>

Any other payment service in future if you need should also implement this PaymentInterface. Now, we will create factories.

PaymentFactoryInterface.php

<?php declare(strict_types=1);

namespace App\Factories\Payment;

use App\Services\Payment\PaymentInterface;

interface PaymentFactoryInterface
{
    public function getInstance(): PaymentInterface;
}

?>

PaypalFactory.php

<?php declare(strict_types=1);

namespace App\Factories\Payment;

use App\Services\Payment\Paypal;
use App\Services\Payment\PaymentInterface;
use App\Factories\Payment\PaymentFactoryInterface;


class PaypalFactory implements PaymentFactoryInterface
{
    public function getInstance(): PaymentInterface
    {
        return new Paypal();
    }
}

?>

StripeFactory.php

<?php declare(strict_types=1);

namespace App\Factories\Payment;

use App\Services\Payment\Stripe;
use App\Services\Payment\PaymentInterface;
use App\Factories\Payment\PaymentFactoryInterface;

class StripeFactory implements PaymentFactoryInterface
{
    public function getInstance(): PaymentInterface
    {
        return new Stripe();
    }
}

?>

Test case:

<?php declare(strict_types=1);
namespace App\Tests;

require_once './vendor/autoload.php'; 
use App\Factories\Payment\PaypalFactory;
use App\Factories\Payment\StripeFactory;
use App\Services\Payment\Paypal;
use App\Services\Payment\Stripe;
use PHPUnit\Framework\TestCase;

class PaymentTest extends TestCase
{
    public function testCanCreatePaypalInstance()
    {
        $paymentFactory = new PaypalFactory();
        $payment = $paymentFactory->getInstance();

        $this->assertInstanceOf(Paypal::class, $payment);
    }

    public function testCanCreateStripeInstance()
    {
        $paymentFactory = new StripeFactory();
        $payment = $paymentFactory->getInstance();

        $this->assertInstanceOf(Stripe::class, $payment);
    }

    public function testCanPayWithPaypal()
    {
        $paymentFactory = new PaypalFactory();
        $payment = $paymentFactory->getInstance();
        $response = $payment->pay();
        $this->assertEquals($response, 'SUCCESS');
    }

    public function testCanPayWithStripe()
    {
        $paymentFactory = new StripeFactory();
        $payment = $paymentFactory->getInstance();
        $response = $payment->pay();
        $this->assertEquals($response, 'SUCCESS');
    }
}

Execute tests

$./vendor/bin/phpunit tests
PHPUnit 7.5.20 by Sebastian Bergmann and contributors.

....                                                                4 / 4 (100%)

Time: 33 ms, Memory: 4.00 MB

OK (4 tests, 4 assertions)
Written on March 26, 2021