Structural Design Patterns in PHP

As experienced developers, organizing code efficiently is crucial. After getting familiar with Creational Design Patterns we can now dive into Structural design patterns which offer valuable tools for this purpose. In this article, we’ll explore five essential structural design patterns within the context of a straightforward e-commerce application.

1. Adapter Pattern

The Adapter Pattern helps integrate incompatible classes. In our e-commerce app, imagine uniting a new payment system with an old one:

class NewPayment {
    public function process(): void {
        // New payment logic
    }
}

class OldPayment {
    public function makePayment(): void {
        // Old payment logic
    }
}

class PaymentAdapter {
    private OldPayment $oldPayment;

    public function __construct(OldPayment $oldPayment) {
        $this->oldPayment = $oldPayment;
    }

    public function process(): void {
        $this->oldPayment->makePayment();
    }
}

With the PaymentAdapter, we bridge the gap between old and new payment systems.

2. Bridge Pattern

The Bridge Pattern separates object abstraction from implementation. In our e-commerce app, think of product categories with different rendering options:

// Interface with example implementations
interface CategoryRenderer {
    public function render(): string;
}

class ListRenderer implements CategoryRenderer {
    public function render(): string {
        return "Rendering category as a <li></li>.";
    }
}

class GridRenderer implements CategoryRenderer {
    public function render(): string {
        return "Rendering category as a <table></table>.";
    }
}

// Abstract class with concrete classes
abstract class Category {
    protected CategoryRenderer $renderer;

    public function __construct(CategoryRenderer $renderer) {
      	// The Bridge between interface and the abstraction
        $this->renderer = $renderer;
    }

    abstract public function display(): string;
}

class ElectronicsCategory extends Category {
    public function display(): string {
        return "<h1>Electronics</h1>: " . $this->renderer->render();
    }
}

class ClothingCategory extends Category {
    public function display(): string {
        return "<h2>Clothing</h2>: " . $this->renderer->render();
    }
}

// Now you can dynamically generate pages
$listRenderer = new ListRenderer();
$electronicsList = new ElectronicsCategory($listRenderer);
echo $electronicsList->display();

With the Bridge Pattern, we can switch between ListRenderer and GridRenderer easily.

3. Composite Pattern

The Composite Pattern helps manage objects and their compositions uniformly. In our e-commerce app, think of product categories with subcategories:

interface Category {
    public function render(): string;
}

class SubCategory implements Category {
    public function render(): string {
        return "Subcategory rendering.";
    }
}

class ProductCategory implements Category {
    private array $categories = [];

    public function addCategory(Category $category): void {
        $this->categories[] = $category;
    }

    public function render(): string {
        $output = "Product category rendering and subcategories:";
        foreach ($this->categories as $category) {
            $output .= "\n" . $category->render();
        }
        return $output;
    }
}

The Composite Pattern simplifies managing complex category hierarchies.

4. Decorator Pattern

The Decorator Pattern adds behavior to objects without altering their code. In our e-commerce app, consider adding discounts to products:

interface Product {
    public function getPrice(): float;
}

class ConcreteProduct implements Product {
    private float $price;

    public function __construct(float $price) {
        $this->price = $price;
    }

    public function getPrice(): float {
        return $this->price;
    }
}

// adding an option to be discounted for a Product
class DiscountedProduct implements Product {
    private Product $product;
    private float $discount;

    public function __construct(Product $product, float $discount) {
        $this->product = $product;
        $this->discount = $discount;
    }

    public function getPrice(): float {
        return $this->product->getPrice() * (1 - $this->discount);
    }
}

With the Decorator Pattern, we can easily add discounts to any product. You can think about descorator as about wrappers to objects, which add additional capabilities

5. Proxy Pattern

The Proxy Pattern provides a surrogate for another object. In our e-commerce app, think of lazy loading product images:

interface ProductImage {
    public function display(): void;
}

class RealImage implements ProductImage {
    private string $filename;

    public function __construct(string $filename) {
        $this->filename = $filename;
    }

    public function display(): void {
        echo "Displaying image: $this->filename" . PHP_EOL;
    }
}

class ProxyImage implements ProductImage {
    private ?RealImage $realImage;
    private string $filename;

    public function __construct(string $filename) {
        $this->filename = $filename;
    }

    public function display(): void {
        if ($this->realImage === null) {
            $this->realImage = new RealImage($this->filename);
        }
        $this->realImage->display();
    }
}

The Proxy Pattern helps control access to expensive objects like images.

6. Facade Pattern

The Facade Pattern simplifies interactions with a complex subsystem. In our e-commerce app, imagine a CheckoutFacade streamlining the checkout process:

class InventorySystem {
    public function checkAvailability(int $productId): bool {
        return true;
    }
}

class PaymentGateway {
    public function processPayment(float $amount): bool {
        return true;
    }
}

class ShippingService {
    public function shipProduct(int $productId): bool {
        return true;
    }
}

class CheckoutFacade {
    public function completeOrder(int $productId, float $amount): bool {
        $inventory = new InventorySystem();
        $payment = new PaymentGateway();
        $shipping = new ShippingService();

        return $inventory->checkAvailability($productId) && $payment->processPayment($amount) && $shipping->shipProduct($productId);
    }
}

$checkout = new CheckoutFacade();
echo $checkout->completeOrder(123, 99.99) ? "Order completed!" : "Order failed.";

The CheckoutFacade simplifies the checkout process in our e-commerce system. You can think of a Facade as a wrapper to a long procedure.

7. Flyweight Pattern

The Flyweight Pattern reduces memory usage by sharing common data. In our e-commerce app, think of shared product attributes:

class ProductAttributes {
    private array $attributes;

    public function __construct(array $attributes) {
        $this->attributes = $attributes;
    }

    public function getAttribute(string $name): string {
        return $this->attributes[$name] ?? ''; // by making the getter dynamic, we avoid using RAM on attributes that are empty
    }
}

class ProductFactory {
    private array $products = [];

    public function getProduct(string $name, array $attributes): ProductAttributes {
        if (!isset($this->products[$name])) {
            $this->products[$name] = new ProductAttributes($attributes);
        }
        return $this->products[$name];
    }
}

$factory = new ProductFactory();
$shirt = $factory->getProduct('Shirt', ['color' => 'Red', 'size' => 'Large']);
$jacket = $factory->getProduct('Shirt', ['color' => 'Blue', 'size' => 'Medium']);
$tie = $factory->getProduct('Tie', ['color' => 'Yellow']);

echo $product1->getAttribute('color'); // Red
echo $product2->getAttribute('size'); // Medium
echo $tie->getAttributr('size'); // returns empty string AND does not use the memory in oposite to the classical $tie->size approach

The Flyweight Pattern optimizes memory usage by sharing common product attributes.

In conclusion, structural design patterns in PHP offer elegant solutions for organizing and extending code. By mastering these patterns, we can simplify complex e-commerce systems, making them more manageable and efficient. The next portion of the design patterns will be the Behavioral Design Patterns.

Leave a Comment