-
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
6 changed files
with
733 additions
and
1 deletion.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
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 |
---|---|---|
|
@@ -27,6 +27,7 @@ Quick Links: | |
- [7. Specification Pattern](#7-specification-pattern) | ||
- [8. Strategy Pattern](#8-strategy-pattern) | ||
- [9. State Pattern](#9-state-pattern) | ||
- [10. Pipeline Pattern](#10-pipeline-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. | ||
|
@@ -557,6 +558,164 @@ The State pattern is perfect for managing complex workflows like order processin | |
- Payment Processing | ||
- Task Management Systems | ||
|
||
### 10. Pipeline Pattern | ||
|
||
The Pipeline pattern allows you to process data through a series of operations, where each operation takes input from the previous operation and produces output for the next one. This pattern is particularly useful for data transformation, validation, and processing workflows. | ||
|
||
#### Features | ||
- Fluent interface for operation chaining | ||
- Built-in error handling | ||
- Input validation | ||
- Type-safe operations with PHP 8.2+ generics | ||
- Side effect management | ||
- Conditional processing | ||
- Operation composition | ||
|
||
#### Basic Usage | ||
|
||
```php | ||
use DesiredPatterns\Pipeline\Pipeline; | ||
|
||
// Basic pipeline | ||
$result = Pipeline::of(5) | ||
->pipe(fn($x) => $x * 2) // 10 | ||
->pipe(fn($x) => $x + 1) // 11 | ||
->get(); // Returns: 11 | ||
|
||
// Pipeline with error handling | ||
$result = Pipeline::of($value) | ||
->try( | ||
fn($x) => processData($x), | ||
fn(\Throwable $e) => handleError($e) | ||
) | ||
->get(); | ||
|
||
// Pipeline with validation | ||
$result = Pipeline::of($data) | ||
->when( | ||
fn($x) => $x > 0, | ||
fn($x) => sqrt($x) | ||
) | ||
->get(); | ||
``` | ||
|
||
#### Advanced Usage with PipelineBuilder | ||
|
||
The PipelineBuilder provides a more structured way to create complex pipelines with validation and error handling: | ||
|
||
```php | ||
use DesiredPatterns\Pipeline\PipelineBuilder; | ||
|
||
$builder = new PipelineBuilder(); | ||
$result = $builder | ||
->withValidation(fn($x) => $x > 0, 'Value must be positive') | ||
->withValidation(fn($x) => $x < 100, 'Value must be less than 100') | ||
->withErrorHandling(fn(\Throwable $e) => handleValidationError($e)) | ||
->add(fn($x) => $x * 2) | ||
->add(fn($x) => "Result: $x") | ||
->build(50) | ||
->get(); | ||
``` | ||
|
||
#### Real-World Example: Data Processing Pipeline | ||
|
||
Here's a real-world example of using the Pipeline pattern for processing user data: | ||
|
||
```php | ||
class UserDataProcessor | ||
{ | ||
private PipelineBuilder $pipeline; | ||
|
||
public function __construct() | ||
{ | ||
$this->pipeline = new PipelineBuilder(); | ||
$this->pipeline | ||
->withValidation( | ||
fn($data) => isset($data['email']), | ||
'Email is required' | ||
) | ||
->withValidation( | ||
fn($data) => filter_var($data['email'], FILTER_VALIDATE_EMAIL), | ||
'Invalid email format' | ||
) | ||
->withErrorHandling(fn(\Throwable $e) => [ | ||
'success' => false, | ||
'error' => $e->getMessage() | ||
]) | ||
->add(function($data) { | ||
// Normalize email | ||
$data['email'] = strtolower($data['email']); | ||
return $data; | ||
}) | ||
->add(function($data) { | ||
// Hash password if present | ||
if (isset($data['password'])) { | ||
$data['password'] = password_hash( | ||
$data['password'], | ||
PASSWORD_DEFAULT | ||
); | ||
} | ||
return $data; | ||
}) | ||
->add(function($data) { | ||
// Add metadata | ||
$data['created_at'] = new DateTime(); | ||
$data['status'] = 'active'; | ||
return $data; | ||
}); | ||
} | ||
|
||
public function process(array $userData): array | ||
{ | ||
return $this->pipeline | ||
->build($userData) | ||
->get(); | ||
} | ||
} | ||
|
||
// Usage | ||
$processor = new UserDataProcessor(); | ||
|
||
// Successful case | ||
$result = $processor->process([ | ||
'email' => '[email protected]', | ||
'password' => 'secret123' | ||
]); | ||
// Returns: [ | ||
// 'email' => '[email protected]', | ||
// 'password' => '$2y$10$...', | ||
// 'created_at' => DateTime, | ||
// 'status' => 'active' | ||
// ] | ||
|
||
// Error case | ||
$result = $processor->process([ | ||
'email' => 'invalid-email' | ||
]); | ||
// Returns: [ | ||
// 'success' => false, | ||
// 'error' => 'Invalid email format' | ||
// ] | ||
``` | ||
|
||
#### Benefits | ||
1. **Separation of Concerns**: Each operation in the pipeline has a single responsibility. | ||
2. **Maintainability**: Easy to add, remove, or modify processing steps without affecting other parts. | ||
3. **Reusability**: Pipeline operations can be reused across different contexts. | ||
4. **Error Handling**: Built-in error handling makes it easy to manage failures. | ||
5. **Validation**: Input validation can be added at any point in the pipeline. | ||
6. **Type Safety**: PHP 8.2+ generics provide type safety throughout the pipeline. | ||
7. **Testability**: Each operation can be tested in isolation. | ||
|
||
#### Use Cases | ||
- Data transformation and normalization | ||
- Form validation and processing | ||
- API request/response handling | ||
- Image processing workflows | ||
- ETL (Extract, Transform, Load) operations | ||
- Document processing pipelines | ||
- Multi-step validation processes | ||
|
||
## 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,131 @@ | ||
<?php | ||
|
||
namespace DesiredPatterns\Pipeline; | ||
|
||
/** | ||
* Pipeline pattern implementation for processing data through a series of operations. | ||
* | ||
* @template T | ||
*/ | ||
class Pipeline | ||
{ | ||
/** | ||
* @var T | ||
*/ | ||
private mixed $value; | ||
|
||
/** | ||
* @param T $value | ||
*/ | ||
private function __construct(mixed $value) | ||
{ | ||
$this->value = $value; | ||
} | ||
|
||
/** | ||
* Create a new pipeline with an initial value. | ||
* | ||
* @template U | ||
* @param U $value | ||
* @return self<U> | ||
*/ | ||
public static function of(mixed $value): self | ||
{ | ||
return new self($value); | ||
} | ||
|
||
/** | ||
* Apply a transformation function to the current value. | ||
* | ||
* @template U | ||
* @param callable(T): U $fn | ||
* @return self<U> | ||
*/ | ||
public function pipe(callable $fn): self | ||
{ | ||
return new self($fn($this->value)); | ||
} | ||
|
||
/** | ||
* Get the final value from the pipeline. | ||
* | ||
* @return T | ||
*/ | ||
public function get(): mixed | ||
{ | ||
return $this->value; | ||
} | ||
|
||
/** | ||
* Map multiple values through the pipeline. | ||
* | ||
* @template U | ||
* @param array<T> $values | ||
* @param callable(T): U $fn | ||
* @return array<U> | ||
*/ | ||
public function map(array $values, callable $fn): array | ||
{ | ||
return array_map($fn, $values); | ||
} | ||
|
||
/** | ||
* Chain multiple operations in sequence. | ||
* | ||
* @param callable[] $operations | ||
* @return self | ||
*/ | ||
public function through(array $operations): self | ||
{ | ||
return array_reduce( | ||
$operations, | ||
fn($carry, $operation) => $carry->pipe($operation), | ||
$this | ||
); | ||
} | ||
|
||
/** | ||
* Apply a side effect without modifying the value. | ||
* | ||
* @param callable(T): void $fn | ||
* @return self<T> | ||
*/ | ||
public function tap(callable $fn): self | ||
{ | ||
$fn($this->value); | ||
return $this; | ||
} | ||
|
||
/** | ||
* Conditionally apply a transformation. | ||
* | ||
* @template U | ||
* @param callable(T): bool $predicate | ||
* @param callable(T): U $fn | ||
* @return self<T|U> | ||
*/ | ||
public function when(callable $predicate, callable $fn): self | ||
{ | ||
if ($predicate($this->value)) { | ||
return $this->pipe($fn); | ||
} | ||
return $this; | ||
} | ||
|
||
/** | ||
* Handle errors in the pipeline. | ||
* | ||
* @template U | ||
* @param callable(T): U $fn | ||
* @param callable(\Throwable): U $errorHandler | ||
* @return self<U> | ||
*/ | ||
public function try(callable $fn, callable $errorHandler): self | ||
{ | ||
try { | ||
return $this->pipe($fn); | ||
} catch (\Throwable $e) { | ||
return new self($errorHandler($e)); | ||
} | ||
} | ||
} |
Oops, something went wrong.