Factory Method

As an application grows, code that decides which class to instantiate tends to spread everywhere — a sprinkling of if/else or switch statements, each picking a concrete class based on some type flag. Every time a new type is added, every one of those scattered blocks has to be found and updated. The Factory Method pattern fixes this by giving object creation a single, well-defined home: an interface (or method) whose whole job is deciding which concrete class to build, so the rest of the code never has to know or care.

The Problem: Scattered if/else Creation Logic

Imagine an app that needs to send notifications by email or SMS, chosen by a $type string coming from user preferences. A natural first attempt puts the decision right where the notification is sent:

// Bad: every call site that sends a notification repeats this logic
function sendUserAlert(string $type, string $message) {
    if ($type === 'email') {
        $notification = new EmailNotification();
    } elseif ($type === 'sms') {
        $notification = new SmsNotification();
    } else {
        throw new InvalidArgumentException("Unknown type: $type");
    }
    $notification->send($message);
}

This same if/elseif block gets copy-pasted into every place the app sends a notification. Add a push notification type later, and every single copy needs to be tracked down and updated — a maintenance trap, and a direct violation of the Open/Closed Principle (code should be open for extension but closed for modification).

The Factory Method Version

Instead, define a shared Notification interface, a concrete class per channel, and a single NotificationFactory whose only job is deciding which one to build.

<?php

interface Notification {
    public function send(string $message): void;
}

class EmailNotification implements Notification {
    public function send(string $message): void {
        mail('user@example.com', 'Alert', $message);
    }
}

class SmsNotification implements Notification {
    public function send(string $message): void {
        // Call out to an SMS gateway API here
        SmsGateway::send('+1555000123', $message);
    }
}

class NotificationFactory {
    public static function create(string $type): Notification {
        return match ($type) {
            'email' => new EmailNotification(),
            'sms'   => new SmsNotification(),
            default => throw new InvalidArgumentException("Unknown type: $type"),
        };
    }
}

// Client code no longer knows or cares about concrete classes
function sendUserAlert(string $type, string $message) {
    $notification = NotificationFactory::create($type);
    $notification->send($message);
}
Why this scales better: to add a PushNotification channel, you write a new class implementing Notification and add one line inside NotificationFactory::create(). Every call site that already calls sendUserAlert() needs zero changes — they were only ever coupled to the Notification interface, not to any concrete class. That's the Open/Closed Principle in practice: the system is open to new notification types, closed to modifying existing, working client code.

The "Method" in Factory Method

In the strict GoF definition, Factory Method is usually a method that subclasses override — an abstract Creator class declares a createProduct() method, and each concrete subclass overrides it to return a different product. The static-factory style shown above is a common, pragmatic PHP variant of the same idea: a single place decides which concrete class to instantiate, keeping that decision out of client code either way.

Where You'll See This in Real Frameworks

Laravel's Illuminate\Notifications and queue "driver" systems work this way — you ask for a mail driver or a queue connection by name, and a factory-style method resolves the correct concrete class. Doctrine's DriverManager similarly picks a concrete database driver class based on a connection's configured driver name, keeping calling code free of vendor-specific new calls.

Up next: Abstract Factory takes this same idea and scales it up — from choosing one product to choosing a whole matching family of them.