Skip to content

Commit

Permalink
State Pattern added.
Browse files Browse the repository at this point in the history
  • Loading branch information
cmatosbc committed Dec 25, 2024
1 parent 6068ebb commit 15e4c5c
Show file tree
Hide file tree
Showing 13 changed files with 913 additions and 1 deletion.
116 changes: 115 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ composer require cmatosbc/desired-patterns

## Patterns Implemented

Quick Links:
- [1. Singleton Pattern](#1-singleton-pattern)
- [2. Multiton Pattern](#2-multiton-pattern)
- [3. Command Pattern](#3-command-pattern)
- [4. Chain of Responsibility Pattern](#4-chain-of-responsibility-pattern)
- [5. Registry Pattern](#5-registry-pattern)
- [6. Service Locator Pattern](#6-service-locator-pattern)
- [7. Specification Pattern](#7-specification-pattern)
- [8. Strategy Pattern](#8-strategy-pattern)
- [9. State Pattern](#9-state-pattern)

### 1. Singleton Pattern
The Singleton pattern ensures a class has only one instance and provides a global point of access to it. Our implementation uses a trait to make it reusable.

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

## 8. Strategy Pattern
### 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.

Expand Down Expand Up @@ -443,6 +454,109 @@ $cryptoPayment = $context->executeStrategy([
]);
```

### 9. State Pattern
The State pattern allows an object to alter its behavior when its internal state changes. The object will appear to change its class. Our implementation provides a flexible and type-safe way to handle state transitions with context validation.

```php
use DesiredPatterns\State\StateMachineTrait;
use DesiredPatterns\State\AbstractState;

// Define your states
class PendingState extends AbstractState
{
public function getName(): string
{
return 'pending';
}

protected array $allowedTransitions = ['processing', 'cancelled'];

protected array $validationRules = [
'order_id' => 'required',
'amount' => 'type:double'
];

public function handle(array $context): array
{
return [
'status' => 'pending',
'message' => 'Order is being validated',
'order_id' => $context['order_id']
];
}
}

// Create your state machine
class Order
{
use StateMachineTrait;

public function __construct(string $orderId)
{
// Initialize states
$this->addState(new PendingState(), true)
->addState(new ProcessingState())
->addState(new ShippedState());

// Set initial context
$this->updateContext([
'order_id' => $orderId,
'created_at' => date('Y-m-d H:i:s')
]);
}

public function process(array $paymentDetails): array
{
$this->transitionTo('processing', $paymentDetails);
return $this->getCurrentState()->handle($this->getContext());
}
}

// Usage
$order = new Order('ORD-123');

try {
$result = $order->process([
'payment_id' => 'PAY-456',
'amount' => 99.99
]);
echo $result['message']; // "Payment verified, preparing shipment"
} catch (StateException $e) {
echo "Error: " . $e->getMessage();
}
```

#### Real-World Example: Order Processing System

The State pattern is perfect for managing complex workflows like order processing. Each state encapsulates its own rules and behaviors:

1. **States**:
- `PendingState`: Initial state, validates order details
- `ProcessingState`: Handles payment verification
- `ShippedState`: Manages shipping details
- `DeliveredState`: Handles delivery confirmation
- `CancelledState`: Manages order cancellation

2. **Features**:
- Context validation per state
- Type-safe state transitions
- State history tracking
- Fluent interface for state machine setup

3. **Benefits**:
- Clean separation of concerns
- Easy to add new states
- Type-safe state transitions
- Automatic context validation
- Comprehensive state history

4. **Use Cases**:
- Order Processing Systems
- Document Workflow Management
- Game State Management
- Payment Processing
- Task Management Systems

## Testing

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

declare(strict_types=1);

namespace Examples\State\Order;

use DesiredPatterns\State\StateMachineTrait;
use Examples\State\Order\States\{
PendingState,
ProcessingState,
ShippedState,
DeliveredState,
CancelledState
};

class Order
{
use StateMachineTrait;

private string $orderId;

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

// Initialize all possible states
$this->addState(new PendingState(), true) // Initial state
->addState(new ProcessingState())
->addState(new ShippedState())
->addState(new DeliveredState())
->addState(new CancelledState());

// Set initial context
$this->updateContext([
'order_id' => $orderId,
'created_at' => date('Y-m-d H:i:s')
]);
}

public function getOrderId(): string
{
return $this->orderId;
}

public function process(array $paymentDetails): array
{
$this->transitionTo('processing', $paymentDetails);
return $this->getCurrentState()->handle($this->getContext());
}

public function ship(array $shippingDetails): array
{
$this->transitionTo('shipped', $shippingDetails);
return $this->getCurrentState()->handle($this->getContext());
}

public function deliver(array $deliveryDetails): array
{
$this->transitionTo('delivered', $deliveryDetails);
return $this->getCurrentState()->handle($this->getContext());
}

public function cancel(string $reason): array
{
$this->transitionTo('cancelled', ['cancellation_reason' => $reason]);
return $this->getCurrentState()->handle($this->getContext());
}
}
34 changes: 34 additions & 0 deletions examples/State/Order/States/CancelledState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Examples\State\Order\States;

use DesiredPatterns\State\AbstractState;

class CancelledState extends AbstractState
{
public function getName(): string
{
return 'cancelled';
}

protected array $allowedTransitions = []; // Final state

protected array $validationRules = [
'order_id' => 'required',
'cancellation_reason' => 'required'
];

public function handle(array $context): array
{
// Process refund and cleanup
return [
'status' => 'cancelled',
'message' => 'Order has been cancelled',
'order_id' => $context['order_id'],
'reason' => $context['cancellation_reason'],
'timestamp' => date('Y-m-d H:i:s')
];
}
}
36 changes: 36 additions & 0 deletions examples/State/Order/States/DeliveredState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Examples\State\Order\States;

use DesiredPatterns\State\AbstractState;

class DeliveredState extends AbstractState
{
public function getName(): string
{
return 'delivered';
}

protected array $allowedTransitions = []; // Final state

protected array $validationRules = [
'order_id' => 'required',
'delivery_date' => 'required',
'signature' => 'required'
];

public function handle(array $context): array
{
// Complete the order and trigger post-delivery actions
return [
'status' => 'delivered',
'message' => 'Order has been delivered',
'order_id' => $context['order_id'],
'delivery_date' => $context['delivery_date'],
'signature' => $context['signature'],
'timestamp' => date('Y-m-d H:i:s')
];
}
}
34 changes: 34 additions & 0 deletions examples/State/Order/States/PendingState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Examples\State\Order\States;

use DesiredPatterns\State\AbstractState;

class PendingState extends AbstractState
{
public function getName(): string
{
return 'pending';
}

protected array $allowedTransitions = ['processing', 'cancelled'];

protected array $validationRules = [
'order_id' => 'required',
'total_amount' => 'type:double',
'items' => 'type:array'
];

public function handle(array $context): array
{
// Validate order and check inventory
return [
'status' => 'pending',
'message' => 'Order is being validated',
'order_id' => $context['order_id'],
'timestamp' => date('Y-m-d H:i:s')
];
}
}
35 changes: 35 additions & 0 deletions examples/State/Order/States/ProcessingState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace Examples\State\Order\States;

use DesiredPatterns\State\AbstractState;

class ProcessingState extends AbstractState
{
public function getName(): string
{
return 'processing';
}

protected array $allowedTransitions = ['shipped', 'cancelled'];

protected array $validationRules = [
'order_id' => 'required',
'payment_id' => 'required',
'payment_status' => 'required'
];

public function handle(array $context): array
{
// Process payment and prepare for shipping
return [
'status' => 'processing',
'message' => 'Payment verified, preparing shipment',
'order_id' => $context['order_id'],
'payment_id' => $context['payment_id'],
'timestamp' => date('Y-m-d H:i:s')
];
}
}
36 changes: 36 additions & 0 deletions examples/State/Order/States/ShippedState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Examples\State\Order\States;

use DesiredPatterns\State\AbstractState;

class ShippedState extends AbstractState
{
public function getName(): string
{
return 'shipped';
}

protected array $allowedTransitions = ['delivered'];

protected array $validationRules = [
'order_id' => 'required',
'tracking_number' => 'required',
'shipping_address' => 'required'
];

public function handle(array $context): array
{
// Generate shipping label and notify courier
return [
'status' => 'shipped',
'message' => 'Order has been shipped',
'order_id' => $context['order_id'],
'tracking_number' => $context['tracking_number'],
'shipping_address' => $context['shipping_address'],
'timestamp' => date('Y-m-d H:i:s')
];
}
}
Loading

0 comments on commit 15e4c5c

Please sign in to comment.