From the archive

A Comprehensive Guide to PHP Object-Oriented Programming (OOP) Design Patterns with Code Example!

A reviewed archive of selected PHP OOP pattern examples, with corrected classifications, modern type declarations, and clear limits on incomplete source code.

Object-oriented programming gives PHP developers tools for organizing responsibilities and defining how objects collaborate. Design patterns add a shared vocabulary for recurring design problems, but a pattern name is useful only when the code actually represents that pattern.

Scope correction: The historical title is preserved for continuity, but the exported draft was not a complete guide to every PHP OOP feature or design pattern. It contained a broad catalog and selected examples. One example was mislabeled, two were incomplete, and the export ended immediately after the Command example. This reviewed archive preserves the useful material and states those limits instead of inventing the missing sections.

What Design Patterns Are

Design patterns describe reusable approaches to recurring software-design problems. They are not copy-and-paste production architectures, and using a pattern does not automatically make an application secure, scalable, or maintainable.

The original article grouped common patterns into three familiar categories:

  • Creational patterns address object creation. Examples include Singleton, Factory Method, Abstract Factory, Builder, Prototype, and Lazy Initialization.
  • Structural patterns address how classes and objects are composed. Examples include Adapter, Bridge, Composite, Decorator, Facade, Flyweight, and Proxy.
  • Behavioral patterns address communication and responsibility between objects. Examples include Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, and Visitor.

The source also listed Dependency Injection, MVC, Service Locator, Repository, Data Mapper, and Active Record. Those are useful architectural or enterprise patterns, but they are not part of the classic creational, structural, and behavioral catalog summarized above.

How These Examples Were Reviewed

The examples below use visibility declarations, constructors, interfaces, inheritance, typed properties, parameter types, and return types supported by current PHP branches. Each code block is intentionally isolated and omits namespaces and autoloading so the pattern remains visible. A real application should use project-appropriate namespaces, dependency management, tests, error handling, and security controls.

Syntax validation cannot establish production suitability or prove that a design is appropriate for a particular application.

Creational Examples

Singleton

Singleton restricts a class to one process-local instance and exposes a global access point. The archived example opened a database connection with credentials embedded in the class. That connection code was removed because credentials do not belong in source and a global database singleton is not a production architecture recommendation.

<?php

declare(strict_types=1);

final class Database
{
    private static ?self $instance = null;

    private function __construct()
    {
    }

    private function __clone()
    {
    }

    public function __wakeup(): void
    {
        throw new LogicException('A singleton cannot be unserialized.');
    }

    public static function getInstance(): self
    {
        return self::$instance ??= new self();
    }
}

$first = Database::getInstance();
$second = Database::getInstance();

var_dump($first === $second);

This demonstrates the mechanics of one in-process instance. It does not provide a database connection, shared state across processes, or a substitute for dependency injection.

Simple Factory, Not Factory Method

The export labeled the next example “Factory Method,” but its static creator and type switch implement a Simple Factory. A Factory Method pattern delegates creation through an overridable method, which this code does not do.

<?php

declare(strict_types=1);

abstract class Product
{
    abstract public function getType(): string;
}

final class ConcreteProductA extends Product
{
    public function getType(): string
    {
        return 'Type A';
    }
}

final class ConcreteProductB extends Product
{
    public function getType(): string
    {
        return 'Type B';
    }
}

final class ProductFactory
{
    public static function create(string $type): Product
    {
        return match ($type) {
            'A' => new ConcreteProductA(),
            'B' => new ConcreteProductB(),
            default => throw new InvalidArgumentException('Invalid product type.'),
        };
    }
}

try {
    $product = ProductFactory::create('A');
    echo $product->getType();
} catch (InvalidArgumentException $exception) {
    echo $exception->getMessage();
}

The exception makes an unsupported type explicit. A production caller should decide whether to catch it locally or allow a higher application boundary to handle it.

Abstract Factory Fragment

Abstract Factory defines an interface for creating related products without tying the client to their concrete classes. The archived block included the factories and application but omitted every button and checkbox class, so this fragment is syntax-valid but cannot create the interface family on its own.

<?php

declare(strict_types=1);

interface GUIFactory
{
    public function createButton(): object;

    public function createCheckbox(): object;
}

final class WinFactory implements GUIFactory
{
    public function createButton(): object
    {
        return new WinButton();
    }

    public function createCheckbox(): object
    {
        return new WinCheckbox();
    }
}

final class MacFactory implements GUIFactory
{
    public function createButton(): object
    {
        return new MacButton();
    }

    public function createCheckbox(): object
    {
        return new MacCheckbox();
    }
}

final class Application
{
    public function __construct(private GUIFactory $factory)
    {
    }

    public function createUI(): array
    {
        return [
            $this->factory->createButton(),
            $this->factory->createCheckbox(),
        ];
    }
}

WinButton, WinCheckbox, MacButton, and MacCheckbox were not present in the export. They have not been invented here, so the example cannot be exercised independently.

Structural Examples

Adapter

Adapter translates one interface into another interface expected by a client. The reviewed version makes that target contract explicit instead of inheriting from an otherwise empty “new system” class.

<?php

declare(strict_types=1);

final class OldSystem
{
    public function oldRequest(): string
    {
        return 'Old system request';
    }
}

interface ModernRequester
{
    public function newRequest(): string;
}

final class Adapter implements ModernRequester
{
    public function __construct(private OldSystem $oldSystem)
    {
    }

    public function newRequest(): string
    {
        return $this->oldSystem->oldRequest();
    }
}

$adapter = new Adapter(new OldSystem());
echo $adapter->newRequest();

The adapter wraps the legacy object and presents the interface the client expects without modifying OldSystem.

Bridge

Bridge separates an abstraction from its implementation so both can vary independently. Here, Page is the abstraction and Renderer is the implementation contract.

<?php

declare(strict_types=1);

interface Renderer
{
    public function render(string $text): string;
}

final class HtmlRenderer implements Renderer
{
    public function render(string $text): string
    {
        $safeText = htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');

        return "<p>{$safeText}</p>";
    }
}

final class PlainTextRenderer implements Renderer
{
    public function render(string $text): string
    {
        return $text;
    }
}

abstract class Page
{
    public function __construct(protected Renderer $renderer)
    {
    }

    abstract public function view(): string;
}

final class SimplePage extends Page
{
    public function view(): string
    {
        return $this->renderer->render('Simple Page Content');
    }
}

$page = new SimplePage(new HtmlRenderer());
echo $page->view();

Escaping the sample text avoids presenting raw interpolation as a safe HTML-rendering architecture. A production renderer would need context-aware output handling and tests.

Composite

Composite represents individual objects and object collections through one interface. The exported code ended with a dangling $composite expression before showing how the constructed objects were used. That unrecoverable line was removed; the complete class definitions that preceded it are preserved in reviewed form.

<?php

declare(strict_types=1);

interface Component
{
    public function operation(): string;
}

final class Leaf implements Component
{
    public function operation(): string
    {
        return 'Leaf';
    }
}

final class Composite implements Component
{
    /** @var list<Component> */
    private array $children = [];

    public function add(Component $component): void
    {
        $this->children[] = $component;
    }

    public function operation(): string
    {
        $results = array_map(
            static fn (Component $child): string => $child->operation(),
            $this->children,
        );

        return 'Composite(' . implode(', ', $results) . ')';
    }
}

The interface and recursive composition represent the pattern, but the source’s missing usage example and expected output cannot be restored from the export.

Behavioral Examples

Strategy

Strategy defines interchangeable implementations behind one contract. These payment classes only print demonstration text; they do not connect to a payment processor and must not be treated as checkout code.

<?php

declare(strict_types=1);

interface PaymentStrategy
{
    public function pay(int $amountInCents): void;
}

final class CreditCardPayment implements PaymentStrategy
{
    public function pay(int $amountInCents): void
    {
        echo "Demonstration credit-card payment: {$amountInCents} cents";
    }
}

final class PayPalPayment implements PaymentStrategy
{
    public function pay(int $amountInCents): void
    {
        echo "Demonstration PayPal payment: {$amountInCents} cents";
    }
}

final class Order
{
    public function __construct(private PaymentStrategy $paymentMethod)
    {
    }

    public function checkout(int $amountInCents): void
    {
        $this->paymentMethod->pay($amountInCents);
    }
}

$order = new Order(new PayPalPayment());
$order->checkout(10000);

Real payment handling requires an approved provider, authenticated requests, validated amounts, idempotency, secure secrets, and reliable error handling. None of that architecture existed in the source example.

Observer

Observer defines a one-to-many relationship in which a subject notifies registered observers when its state changes.

<?php

declare(strict_types=1);

interface Observer
{
    public function update(int $state): void;
}

final class Subject
{
    /** @var list<Observer> */
    private array $observers = [];

    private int $state = 0;

    public function attach(Observer $observer): void
    {
        $this->observers[] = $observer;
    }

    public function setState(int $state): void
    {
        $this->state = $state;
        $this->notifyObservers();
    }

    private function notifyObservers(): void
    {
        foreach ($this->observers as $observer) {
            $observer->update($this->state);
        }
    }
}

final class ConcreteObserver implements Observer
{
    public function update(int $state): void
    {
        echo "Observer state: {$state}";
    }
}

$subject = new Subject();
$subject->attach(new ConcreteObserver());
$subject->setState(1);

The observer receives the state as part of the notification instead of reaching back into the subject. Larger systems also need decisions about detaching observers, notification order, failures, and object lifetimes.

Command

Command turns a request into an object. The invoker depends on the Command interface rather than knowing how a light is controlled.

<?php

declare(strict_types=1);

interface Command
{
    public function execute(): void;
}

final class Light
{
    public function on(): void
    {
        echo 'Light is ON';
    }

    public function off(): void
    {
        echo 'Light is OFF';
    }
}

final class LightOnCommand implements Command
{
    public function __construct(private Light $light)
    {
    }

    public function execute(): void
    {
        $this->light->on();
    }
}

final class LightOffCommand implements Command
{
    public function __construct(private Light $light)
    {
    }

    public function execute(): void
    {
        $this->light->off();
    }
}

final class RemoteControl
{
    private ?Command $command = null;

    public function setCommand(Command $command): void
    {
        $this->command = $command;
    }

    public function pressButton(): void
    {
        if ($this->command === null) {
            throw new LogicException('No command has been assigned.');
        }

        $this->command->execute();
    }
}

$light = new Light();
$remote = new RemoteControl();
$remote->setCommand(new LightOnCommand($light));
$remote->pressButton();

The guard prevents the invoker from calling an uninitialized command. Undo history, queues, persistence, retries, and failure recovery were not part of the archived example.

Archive Completeness Note

The WordPress export ends immediately after the original Command code block. It contains no recoverable conclusion and no code examples for the many other patterns listed near the beginning. Those missing sections have not been reconstructed.