|
1 |
| -## Introduction |
| 1 | +# Welcome to TheWebSolver Pipeline |
2 | 2 |
|
3 |
| -Pipeline follows the Chain of Responsibility Design Pattern to handle the given subject/request using pipes. |
| 3 | +For usage details, visit [GitHub Wiki][Wiki]. |
4 | 4 |
|
5 |
| -## Installation (via Composer) |
6 |
| - |
7 |
| -Install library using composer command: |
8 |
| -```sh |
9 |
| -$ composer require thewebsolver/pipeline |
10 |
| -``` |
11 |
| - |
12 |
| -## Usage |
13 |
| - |
14 |
| -The main responsibility of the Pipeline class is to pass given subject to all registered pipes and return back the transformed value. The pipeline chain can be initialized following builder pattern as follows: |
15 |
| -- Initialize the pipeline (`new Pipeline()`). |
16 |
| -- Pass subject to the pipeline using `Pipeline::send()` method. |
17 |
| -- Provide a single handler using `Pipeline::pipe()` method or if multiple pipes (_which obviously should be the case, otherwise what is the intent of using Pipeline anyway_), use `Pipeline::through()` method. |
18 |
| - > NOTE: `Pipeline::through()` is the main method to provide pipes. If pipes are provided using `Pipeline::pipe()` method and then required pipes are provided using `Pipeline::through()` method, pipes provided using `Pipeline::pipe()` will be deferred. Meaning subject will pass through pipes provided using `Pipeline::through()` and then the transformed subject is pass through other pipes provided using `Pipeline::pipe()`. |
19 |
| - |
20 |
| - > In summary, `Pipeline::pipe()` method's intent is to append additional pipes to the pipeline after required pipes are provided using `Pipeline::through()` method. |
21 |
| - |
22 |
| - > Subject is passed through pipes in the same order as they are provided. |
23 |
| - |
24 |
| -- Finally, get the transformed subject back using `Pipeline::thenReturn()` method. If subject needs to be transformed one last time before receiving it, `Pipeline::then()` can be used. |
25 |
| - |
26 |
| -There are two methods that may be required depending on how subject is being handled and how pipes behave. |
27 |
| -- `Pipeline::use()` method provides additional data to each pipe. |
28 |
| -- `Pipeline::sealWith()` method provides prevention mechanism form script interruption if any pipe throws an exception. |
29 |
| - |
30 |
| -### Basic Usage |
31 |
| -```php |
32 |
| -use TheWebSolver\Codegarage\Lib\Pipeline; |
33 |
| - |
34 |
| -// Pipe as a PipeInterface. |
35 |
| -class MakeTextUpperCase implements PipeInterface { |
36 |
| - public function handle(string $text, \Closure $next): string { |
37 |
| - return strtoupper($text); |
38 |
| - } |
39 |
| -} |
40 |
| - |
41 |
| -// Pipe as a lambda function. |
42 |
| -$trimCharacters = static fn(string $text, \Closure $next): string => $next( trim( $text ) ); |
43 |
| - |
44 |
| -// $finalText will be -> 'WHITESPACE AND MIXED CASED STRING'; |
45 |
| -$finalText = (new Pipeline()) |
46 |
| - ->send(subject: ' Whitespace and Mixed cased String\ ') |
47 |
| - ->through(pipes: array( MakeTextUpperCase::class, $trimCharacters ) ) |
48 |
| - ->thenReturn(); |
49 |
| -``` |
50 |
| - |
51 |
| - |
52 |
| -### Advanced Usage |
53 |
| -```php |
54 |
| -use Closure; |
55 |
| -use Throwable; |
56 |
| -use TheWebSolver\Codegarage\Lib\Pipeline; |
57 |
| - |
58 |
| -$pipeline = new Pipeline(); |
59 |
| - |
60 |
| -$pipeline->use(is_string(...), strtoupper(...)) |
61 |
| - ->send(subject: ' convert this to all caps ') |
62 |
| - ->sealWith(fallback: static fn(\Throwable $e): string => $e->getMessage()) |
63 |
| - ->through(pipes: [ |
64 |
| - // $isString is the first value passed to "Pipeline::use()". |
65 |
| - static function(mixed $subject, Closure $next, Closure $isString): string { |
66 |
| - return $next(!$isString($subject) ? '' : $subject); |
67 |
| - }, |
68 |
| - |
69 |
| - // $uppercase is the second value passed to "Pipeline::use()". |
70 |
| - static function(mixed $subject, Closure $next, Closure $isString, Closure $uppercase): string { |
71 |
| - return $next($uppercase($subject)); |
72 |
| - }, |
73 |
| - |
74 |
| - // We'll convert our subject into an array. |
75 |
| - static fn(mixed $subject, Closure $next): array => $next(array($subject)), |
76 |
| - |
77 |
| - // Final check if our subject remains same type. |
78 |
| - static function(mixed $subject, Closure $next, Closure $isString): array { |
79 |
| - return $isString($subject) |
80 |
| - ? $next($subject) |
81 |
| - : throw new \TypeError('Subject transformed into an array'); |
82 |
| - } |
83 |
| - ]) |
84 |
| - // Subject never reaches to this pipe. |
85 |
| - ->pipe( static fn(mixed $subject, Closure $next): array |
86 |
| - => $next(is_array($subject) ? array(...$subject, 'suffix') : array())); |
87 |
| - |
88 |
| -// Last pipe throws exception, so we'll get exception message instead of transformed subject. |
89 |
| -$transformed = $pipeline->then( |
90 |
| - static fn(mixed $subject) => array('prefix', ...(is_array($subject) ? $subject : array())) |
91 |
| -); |
92 |
| -``` |
93 |
| - |
94 |
| -## PSR-7 & PSR-15 Bridge |
95 |
| -The `TheWebSolver\Codegarage\Lib\PipelineBridge` seamlessly handles middlewares coming to the Request Handler, piping through the pipeline and getting the response back. |
96 |
| - |
97 |
| -```php |
98 |
| -use Closure; |
99 |
| -use Psr\Http\Message\ResponseInterface; |
100 |
| -use Psr\Http\Server\MiddlewareInterface; |
101 |
| -use Psr\Http\Server\RequestHandlerInterface; |
102 |
| -use Psr\Http\Message\ServerRequestInterface; |
103 |
| -use TheWebSolver\Codegarage\Lib\PipelineBridge; |
104 |
| - |
105 |
| -class RequestHandler implements RequestHandlerInterface { |
106 |
| - /** @var array<Closure|MiddlewareInterface> */ |
107 |
| - protected array $middlewares; |
108 |
| - |
109 |
| - public function addMiddleware(Closure|MiddlewareInterface $middleware) { |
110 |
| - $this->middlewares[] = PipelineBridge::middlewareToPipe($middleware); |
111 |
| - } |
112 |
| - |
113 |
| - public function handle( ServerRequestInterface $request ): ResponseInterface { |
114 |
| - // Concrete that implements "ResponseInterface". |
115 |
| - $response = new Response(); |
116 |
| - |
117 |
| - // Middleware that uses previous response as required. |
118 |
| - $middleware = function( |
119 |
| - ServerRequestInterface $request, |
120 |
| - RequestHandlerInterface $handler |
121 |
| - ) { |
122 |
| - $previousResponse = $request->getAttribute(PipelineBridge::MIDDLEWARE_RESPONSE); |
123 |
| - |
124 |
| - return '/app/route/' !== $request->getRequestTarget() |
125 |
| - ? new Response(status: 404) |
126 |
| - : $previousResponse->withStatus(code: 200); |
127 |
| - }; |
128 |
| - |
129 |
| - $middlewares = array( |
130 |
| - ...$this->middlewares, |
131 |
| - PipelineBridge::middlewareToPipe($middleware) |
132 |
| - ); |
133 |
| - |
134 |
| - return (new Pipeline()) |
135 |
| - ->use($request, $this) // Passed to "MiddlewareInterface::process()" method. |
136 |
| - ->send(subject: $response) |
137 |
| - ->through(pipes: $middlewares) |
138 |
| - ->thenReturn(); |
139 |
| - } |
140 |
| -} |
141 |
| -``` |
| 5 | +[Wiki]: https://github.com/TheWebSolver/pipeline/wiki |
0 commit comments