Skip to content

Latest commit

 

History

History
452 lines (352 loc) · 13.1 KB

File metadata and controls

452 lines (352 loc) · 13.1 KB

🎯 Strategy Pattern: First-Match Implementation

A comprehensive PHP tutorial demonstrating the Strategy Pattern with a "first-match" approach for data transformation operations.

PHP Version License Tests Code Style

📚 Table of Contents

🧠 What is the Strategy Pattern?

The Strategy Pattern is a behavioral design pattern that defines a family of algorithms, encapsulates each one, and makes them interchangeable. It lets the algorithm vary independently from clients that use it.

Key Components

┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│     Context     │───▶│    Strategy      │◀───│ ConcreteStrategy│
│ (Transformer)   │    │   (Interface)    │    │ (JsonTransformer│
└─────────────────┘    └──────────────────┘    └─────────────────┘
                                 ▲               ┌─────────────────┐
                                 │               │ ConcreteStrategy│
                                 └───────────────│ (XmlTransformer │
                                                 └─────────────────┘
                                                 ┌─────────────────┐
                                                 │ ConcreteStrategy│
                                                 │ (CsvTransformer │
                                                 └─────────────────┘

💪 Why is it Powerful?

🔄 Runtime Algorithm Selection

Switch between different algorithms at runtime without changing the client code:

$transformer = new TransformerProvider($strategies);

// Same interface, different behaviors
$result = $transformer->transform($data, Type::JSON);  // Uses JsonTransformer
$result = $transformer->transform($data, Type::XML);   // Uses XmlTransformer
$result = $transformer->transform($data, Type::CSV);   // Uses CsvTransformer

🧩 Easy Extension

Add new transformation formats without modifying existing code:

// Add new strategy without touching existing code
class PdfTransformer implements TransformerInterface {
    public function supports(Type $type): bool {
        return $type === Type::PDF;
    }
    
    public function transform(array $data): string {
        // PDF transformation logic
    }
}

🔒 SOLID Principles Compliance

  • Single Responsibility: Each strategy handles one transformation format
  • Open/Closed: Open for extension, closed for modification
  • Dependency Inversion: Depends on abstractions, not concretions

🧪 Testability

Easy to unit test each strategy independently:

public function testJsonTransformer(): void
{
    $transformer = new JsonTransformer();
    $result = $transformer->transform(['name' => 'John']);
    
    $this->assertJson($result);
}

✅ When to Use It

Perfect Scenarios

📊 Multiple Data Formats

When you need to support multiple output formats:

// Transform user data in different formats
$userData = ['id' => 1, 'name' => 'John', 'email' => 'john@example.com'];

$transformer->transform($userData, Type::JSON);  // For APIs
$transformer->transform($userData, Type::XML);   // For legacy systems
$transformer->transform($userData, Type::CSV);   // For spreadsheets

🔐 Payment Processing

Different payment gateways with same interface:

$processor = new PaymentProcessor([
    new StripePayment(),
    new PayPalPayment(),
    new SquarePayment()
]);

$processor->process($amount, PaymentType::CREDIT_CARD);

📧 Notification Systems

Multiple notification channels:

$notifier = new NotificationService([
    new EmailNotification(),
    new SmsNotification(),
    new PushNotification()
]);

$notifier->send($message, NotificationType::EMAIL);

🗂️ File Storage

Different storage backends:

$storage = new FileStorage([
    new LocalStorage(),
    new S3Storage(),
    new GoogleCloudStorage()
]);

$storage->store($file, StorageType::CLOUD);

❌ When NOT to Use It

Avoid in These Scenarios

🚫 Single Algorithm

Don't use Strategy Pattern if you only have one way to do something:

// ❌ Overkill - just use a simple method
class Calculator {
    public function add(int $a, int $b): int {
        return $a + $b;  // Only one way to add numbers
    }
}

🚫 Simple Conditional Logic

For simple if/else scenarios:

// ❌ Strategy Pattern is overkill here
public function getDiscount(string $customerType): float {
    return match($customerType) {
        'premium' => 0.20,
        'regular' => 0.10,
        default => 0.0
    };
}

🚫 Algorithms Rarely Change

When the algorithm is stable and unlikely to change:

// ❌ String length calculation doesn't need strategies
public function getStringLength(string $text): int {
    return strlen($text);  // This won't change
}

🚫 Performance-Critical Code

When every nanosecond matters:

// ❌ Strategy overhead might be too much for tight loops
for ($i = 0; $i < 1000000; $i++) {
    $result = $strategy->calculate($data[$i]);  // Overhead adds up
}

🏗️ Project Overview

This project demonstrates a "first-match" Strategy Pattern implementation for data transformation operations. The system automatically selects the first strategy that supports the requested transformation type.

Key Features

  • 🎯 First-match selection algorithm
  • 🔄 Runtime strategy switching
  • 📦 Easy strategy extension
  • 🧪 Comprehensive test coverage
  • 📝 Detailed documentation
  • 🐳 Docker development environment

🏛️ Architecture

Core Components

// Strategy Interface
interface TransformerInterface {
    public function transform(array $data): string;
    public function supports(Type $type): bool;
}

// Context (Strategy Manager)
class TransformerProvider implements TransformerProviderInterface {
    public function transform(array $data, Type $type): string {
        foreach ($this->strategies as $strategy) {
            if ($strategy->supports($type)) {
                return $strategy->transform($data);  // First match wins!
            }
        }
        throw new InvalidArgumentException("No strategy found for type: {$type->value}");
    }
}

// Concrete Strategies
class JsonTransformer implements TransformerInterface { /* ... */ }
class XmlTransformer implements TransformerInterface { /* ... */ }
class CsvTransformer implements TransformerInterface { /* ... */ }

Strategy Selection Flow

1. Client requests transformation with Type::JSON
2. TransformerProvider iterates through strategies
3. First strategy that supports JSON is selected
4. Selected strategy performs the transformation
5. Result is returned to client

🚀 Quick Start

Prerequisites

  • PHP 8.1+
  • Docker & Docker Compose
  • Make

Installation

# Clone the repository
git clone https://github.com/nullodyssey/first-match-strategy-pattern.git
cd first-match-strategy-pattern

# Start the development environment
make start

# Install dependencies
make composer c="install"

# Run tests to verify everything works
make test

📖 Usage Examples

Basic Usage

use NullOdyssey\FirstMatchStrategy\TransformerProvider;
use NullOdyssey\FirstMatchStrategy\Strategy\{JsonTransformer, XmlTransformer, CsvTransformer};
use NullOdyssey\FirstMatchStrategy\Type;

// Create transformer with strategies
$transformer = new TransformerProvider([
    new JsonTransformer(),
    new XmlTransformer(),
    new CsvTransformer()
]);

// Sample data
$userData = [
    'id' => 123,
    'name' => 'John Doe',
    'email' => 'john@example.com',
    'active' => true
];

// Transform in different formats
$jsonResult = $transformer->transform($userData, Type::JSON);
// Output: {"id":123,"name":"John Doe","email":"john@example.com","active":true}

$xmlResult = $transformer->transform($userData, Type::XML);
// Output: <root><id>123</id><name>John Doe</name>...</root>

$csvResult = $transformer->transform($userData, Type::CSV);
// Output: id,name,email,active\n123,"John Doe","john@example.com",1

Using the Facade

use NullOdyssey\FirstMatchStrategy\DataTransformer;
use NullOdyssey\FirstMatchStrategy\Type;

$transformerService = new DataTransformer();
$data = ['name' => 'Jane Smith', 'role' => 'Developer'];

// Simple one-liner transformations
$transformerService($data, Type::JSON);  // Triggers JSON transformation
$transformerService($data, Type::XML);   // Triggers XML transformation
$transformerService($data, Type::CSV);   // Triggers CSV transformation

Adding Custom Strategies

use NullOdyssey\FirstMatchStrategy\TransformerInterface;
use NullOdyssey\FirstMatchStrategy\Type;

// Extend the Type enum first
enum Type: string {
    case JSON = 'json';
    case XML = 'xml';
    case CSV = 'csv';
    case YAML = 'yaml';  // New type
}

// Create custom strategy
class YamlTransformer implements TransformerInterface {
    public function transform(array $data): string {
        return yaml_emit($data);
    }
    
    public function supports(Type $type): bool {
        return $type === Type::YAML;
    }
}

// Use with existing transformer
$transformer = new TransformerProvider([
    new JsonTransformer(),
    new XmlTransformer(),
    new CsvTransformer(),
    new YamlTransformer()  // Add your custom strategy
]);

$result = $transformer->transform($data, Type::YAML);

🧪 Running Tests

# Run all tests
make test

# Run specific test file
make sh
./vendor/bin/phpunit tests/Unit/TransformerProviderTest.php

# Run with coverage
make sh
./vendor/bin/phpunit --coverage-html coverage

📊 Real-World Applications

E-commerce Platform

// Order transformation in multiple formats for different systems
$orderTransformer = new TransformerProvider([
    new JsonTransformer(),     // For REST APIs
    new XmlTransformer(),      // For SOAP services
    new CsvTransformer(),      // For accounting software
    new PdfTransformer()       // For customer invoices
]);

$order = ['id' => 'ORD-123', 'total' => 99.99, 'items' => [...]];
$receipt = $orderTransformer->transform($order, Type::PDF);

Analytics Dashboard

// Report generation in multiple formats
$reportTransformer = new TransformerProvider([
    new ExcelTransformer(),    // For business users
    new CsvTransformer(),      // For data analysis
    new JsonTransformer(),     // For API consumption
    new ChartTransformer()     // For visualizations
]);

$analyticsData = ['revenue' => 10000, 'users' => 500, 'conversion' => 2.5];
$report = $reportTransformer->transform($analyticsData, Type::EXCEL);

🛠️ Development Commands

# Docker & Environment
make build          # Build Docker images
make start          # Build and start containers
make down           # Stop containers
make sh             # Connect to PHP container

# Dependencies
make composer       # Run composer commands
make vendor         # Install production dependencies

# Code Quality
make test           # Run PHPUnit tests
make phpstan        # Run static analysis
make phpcs          # Run code style fixer
make quality        # Run both phpstan and phpcs

🎯 Learning Objectives

After exploring this project, you'll understand:

  • Strategy Pattern fundamentals
  • When and why to use it
  • First-match selection algorithm
  • SOLID principles in practice
  • PHP 8.1+ features (enums, readonly classes)
  • Test-driven development
  • Design pattern trade-offs

🤝 Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

👨‍💻 Author

nullodyssey


Happy coding! 🚀