Skip to content

Commit

Permalink
Strategy pattern added.
Browse files Browse the repository at this point in the history
  • Loading branch information
cmatosbc committed Dec 24, 2024
1 parent 0db3c13 commit 25281a5
Show file tree
Hide file tree
Showing 8 changed files with 692 additions and 1 deletion.
177 changes: 176 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Modern PHP Design Patterns

[![PHP Lint](https://github.com/cmatosbc/desired-patterns/actions/workflows/lint.yml/badge.svg)](https://github.com/cmatosbc/desired-patterns/actions/workflows/lint.yml) [![PHPUnit Tests](https://github.com/cmatosbc/desired-patterns/actions/workflows/phpunit.yml/badge.svg)](https://github.com/cmatosbc/desired-patterns/actions/workflows/phpunit.yml) [![PHP Composer](https://github.com/cmatosbc/desired-patterns/actions/workflows/composer.yml/badge.svg)](https://github.com/cmatosbc/desired-patterns/actions/workflows/composer.yml) ![Code Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/cmatosbc/664fd72a90f996481f161d1d3a2f7285/raw/coverage.json)
[![PHP Lint](https://github.com/cmatosbc/desired-patterns/actions/workflows/lint.yml/badge.svg)](https://github.com/cmatosbc/desired-patterns/actions/workflows/lint.yml) [![PHPUnit Tests](https://github.com/cmatosbc/desired-patterns/actions/workflows/phpunit.yml/badge.svg)](https://github.com/cmatosbc/desired-patterns/actions/workflows/phpunit.yml) [![PHP Composer](https://github.com/cmatosbc/desired-patterns/actions/workflows/composer.yml/badge.svg)](https://github.com/cmatosbc/desired-patterns/actions/workflows/composer.yml) ![Code Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/cmatosbc/664fd72a90f996481f161d1d3a2f7285/rawgit/coverage.json)

A collection of modern PHP design patterns implemented using PHP 8.2+ features. Sexier than older implementations and more readable than ever.

Expand Down Expand Up @@ -267,6 +267,181 @@ if ($canAccessContent->isSatisfiedBy($user)) {
}
```

## 8. Strategy Pattern

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

### Real-World Examples

1. **Payment Processing**
- Different payment methods (Credit Card, PayPal, Cryptocurrency)
- Each payment method has its own validation and processing logic
- System can switch between payment strategies based on user selection

2. **Data Export**
- Multiple export formats (CSV, JSON, XML, PDF)
- Each format has specific formatting requirements
- Choose export strategy based on user preference or file type

3. **Shipping Calculation**
- Various shipping providers (FedEx, UPS, DHL)
- Each provider has unique rate calculation algorithms
- Select provider based on destination, weight, or cost

### Complete Example

```php

namespace Examples\Strategy\Payment;

use DesiredPatterns\Strategy\AbstractStrategy;
use DesiredPatterns\Traits\ConfigurableStrategyTrait;

/**
* Credit Card Payment Strategy
*/
class CreditCardStrategy extends AbstractStrategy
{
use ConfigurableStrategyTrait;

protected array $requiredOptions = ['api_key'];

public function supports(array $data): bool
{
return isset($data['payment_method'])
&& $data['payment_method'] === 'credit_card';
}

public function validate(array $data): bool
{
return isset($data['card_number'])
&& isset($data['expiry'])
&& isset($data['cvv']);
}

public function execute(array $data): array
{
// Process credit card payment
return [
'status' => 'success',
'transaction_id' => uniqid('cc_'),
'method' => 'credit_card',
'amount' => $data['amount']
];
}
}

/**
* PayPal Payment Strategy
*/
class PayPalStrategy extends AbstractStrategy
{
use ConfigurableStrategyTrait;

protected array $requiredOptions = ['client_id', 'client_secret'];

public function supports(array $data): bool
{
return isset($data['payment_method'])
&& $data['payment_method'] === 'paypal';
}

public function validate(array $data): bool
{
return isset($data['paypal_email'])
&& isset($data['amount']);
}

public function execute(array $data): array
{
// Process PayPal payment
return [
'status' => 'success',
'transaction_id' => uniqid('pp_'),
'method' => 'paypal',
'amount' => $data['amount']
];
}
}

/**
* Cryptocurrency Payment Strategy
*/
class CryptoStrategy extends AbstractStrategy
{
use ConfigurableStrategyTrait;

protected array $requiredOptions = ['wallet_address'];

public function supports(array $data): bool
{
return isset($data['payment_method'])
&& $data['payment_method'] === 'crypto';
}

public function validate(array $data): bool
{
return isset($data['crypto_address'])
&& isset($data['crypto_currency']);
}

public function execute(array $data): array
{
// Process crypto payment
return [
'status' => 'success',
'transaction_id' => uniqid('crypto_'),
'method' => 'crypto',
'amount' => $data['amount'],
'currency' => $data['crypto_currency']
];
}
}

// Usage Example
$context = new StrategyContext();

// Configure payment strategies
$context->addStrategy(
new CreditCardStrategy(),
['api_key' => 'sk_test_123']
)
->addStrategy(
new PayPalStrategy(),
[
'client_id' => 'client_123',
'client_secret' => 'secret_456'
]
)
->addStrategy(
new CryptoStrategy(),
['wallet_address' => '0x123...']
);

// Process a credit card payment
$ccPayment = $context->executeStrategy([
'payment_method' => 'credit_card',
'amount' => 99.99,
'card_number' => '4242424242424242',
'expiry' => '12/25',
'cvv' => '123'
]);

// Process a PayPal payment
$ppPayment = $context->executeStrategy([
'payment_method' => 'paypal',
'amount' => 149.99,
'paypal_email' => '[email protected]'
]);

// Process a crypto payment
$cryptoPayment = $context->executeStrategy([
'payment_method' => 'crypto',
'amount' => 199.99,
'crypto_address' => '0x456...',
'crypto_currency' => 'ETH'
]);

## Testing

Run the test suite using PHPUnit :
Expand Down
60 changes: 60 additions & 0 deletions examples/Strategy/Sorting/QuickSortStrategy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace DesiredPatterns\Examples\Strategy\Sorting;

use DesiredPatterns\Strategy\AbstractStrategy;
use DesiredPatterns\Traits\ConfigurableStrategyTrait;

class QuickSortStrategy extends AbstractStrategy
{
use ConfigurableStrategyTrait;

protected array $requiredOptions = ['sort_key'];

public function supports(array $data): bool
{
return count($data) > 1000; // Use QuickSort for larger datasets
}

public function validate(array $data): bool
{
if (empty($data)) {
return false;
}

$sortKey = $this->getOption('sort_key');
return isset($data[0][$sortKey]);
}

public function execute(array $data): array
{
$sortKey = $this->getOption('sort_key');
return $this->quickSort($data, $sortKey);
}

private function quickSort(array $data, string $sortKey): array
{
if (count($data) <= 1) {
return $data;
}

$pivot = $data[array_key_first($data)];
$left = $right = [];

for ($i = 1; $i < count($data); $i++) {
if ($data[$i][$sortKey] < $pivot[$sortKey]) {
$left[] = $data[$i];
} else {
$right[] = $data[$i];
}
}

return array_merge(
$this->quickSort($left, $sortKey),
[$pivot],
$this->quickSort($right, $sortKey)
);
}
}
30 changes: 30 additions & 0 deletions examples/Strategy/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

require_once __DIR__ . '/../../vendor/autoload.php';

use DesiredPatterns\Strategy\StrategyContext;
use DesiredPatterns\Examples\Strategy\Sorting\QuickSortStrategy;

// Sample data
$data = [
['id' => 3, 'name' => 'John'],
['id' => 1, 'name' => 'Jane'],
['id' => 2, 'name' => 'Bob'],
];

// Create context with strategy
$context = new StrategyContext();
$context->addStrategy(
new QuickSortStrategy(),
['sort_key' => 'id']
);

// Execute strategy
try {
$sortedData = $context->executeStrategy($data);
print_r($sortedData);
} catch (RuntimeException $e) {
echo "Error: " . $e->getMessage() . "\n";
}
46 changes: 46 additions & 0 deletions src/Contracts/StrategyInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace DesiredPatterns\Contracts;

/**
* Interface StrategyInterface
*
* Defines a family of algorithms that can be used interchangeably.
* Each concrete strategy implements a specific algorithm while maintaining
* the same interface, allowing them to be swapped at runtime.
*/
interface StrategyInterface
{
/**
* Execute the strategy's algorithm with the given data
*
* @param array $data The input data to process
* @return mixed The result of the strategy execution
*/
public function execute(array $data): mixed;

/**
* Determine if this strategy can handle the given data
*
* @param array $data The data to check
* @return bool True if this strategy can handle the data, false otherwise
*/
public function supports(array $data): bool;

/**
* Configure the strategy with optional parameters
*
* @param array $options Configuration options for the strategy
*/
public function configure(array $options = []): void;

/**
* Validate the input data before execution
*
* @param array $data The data to validate
* @return bool True if the data is valid, false otherwise
*/
public function validate(array $data): bool;
}
61 changes: 61 additions & 0 deletions src/Strategy/AbstractStrategy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace DesiredPatterns\Strategy;

use DesiredPatterns\Contracts\StrategyInterface;

/**
* Abstract class AbstractStrategy
*
* Provides a base implementation for strategies with common functionality.
* Concrete strategies should extend this class and implement the abstract methods.
*/
abstract class AbstractStrategy implements StrategyInterface
{
/**
* Configuration options for the strategy
*
* @var array<string, mixed>
*/
protected array $options = [];

/**
* Configure the strategy with optional parameters
*
* @param array $options Configuration options for the strategy
*/
public function configure(array $options = []): void
{
$this->options = array_merge($this->options, $options);
}

/**
* Default validation implementation that always returns true
* Override this method in concrete strategies to implement specific validation logic
*
* @param array $data The data to validate
* @return bool True if the data is valid, false otherwise
*/
public function validate(array $data): bool
{
return true;
}

/**
* Determine if this strategy can handle the given data
*
* @param array $data The data to check
* @return bool True if this strategy can handle the data, false otherwise
*/
abstract public function supports(array $data): bool;

/**
* Execute the strategy's algorithm with the given data
*
* @param array $data The input data to process
* @return mixed The result of the strategy execution
*/
abstract public function execute(array $data): mixed;
}
Loading

0 comments on commit 25281a5

Please sign in to comment.