-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
692 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
|
||
|
@@ -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 : | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
Oops, something went wrong.