Pipeline::run()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 2
dl 0
loc 6
ccs 5
cts 5
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace MichaelRubel\EnhancedPipeline;
6
7
use Closure;
8
use Illuminate\Container\Container as ContainerConcrete;
9
use Illuminate\Contracts\Container\Container;
10
use Illuminate\Contracts\Pipeline\Pipeline as PipelineContract;
11
use Illuminate\Support\Traits\Conditionable;
12
use MichaelRubel\EnhancedPipeline\Events\PipeExecutionFinished;
13
use MichaelRubel\EnhancedPipeline\Events\PipeExecutionStarted;
14
use MichaelRubel\EnhancedPipeline\Events\PipelineFinished;
15
use MichaelRubel\EnhancedPipeline\Events\PipelineStarted;
16
use MichaelRubel\EnhancedPipeline\Traits\HasDatabaseTransactions;
17
use MichaelRubel\EnhancedPipeline\Traits\HasEvents;
18
use RuntimeException;
19
use Throwable;
20
21
class Pipeline implements PipelineContract
22
{
23
    use Conditionable, HasDatabaseTransactions, HasEvents;
24
25
    /**
26
     * The container implementation.
27
     *
28
     * @var \Illuminate\Contracts\Container\Container|null
29
     */
30
    protected $container;
31
32
    /**
33
     * The object being passed through the pipeline.
34
     *
35
     * @var mixed
36
     */
37
    protected $passable;
38
39
    /**
40
     * The callback to be executed on failure pipeline.
41
     *
42
     * @var Closure|null
43
     */
44
    protected $onFailure;
45
46
    /**
47
     * The array of class pipes.
48
     *
49
     * @var array
50
     */
51
    protected $pipes = [];
52
53
    /**
54
     * The method to call on each pipe.
55
     *
56
     * @var string
57
     */
58
    protected $method = 'handle';
59
60
    /**
61
     * Create a new class instance.
62
     *
63
     * @return void
64
     */
65 32
    public function __construct(?Container $container = null)
66
    {
67 32
        $this->container = $container;
68
    }
69
70
    /**
71
     * Create a new class instance.
72
     */
73 11
    public static function make(?Container $container = null): Pipeline
74
    {
75 11
        if (! $container) {
76 11
            $container = ContainerConcrete::getInstance();
77
        }
78
79 11
        return $container->make(static::class);
80
    }
81
82
    /**
83
     * Set the object being sent through the pipeline.
84
     *
85
     * @param  mixed  $passable
86
     * @return $this
87
     */
88 31
    public function send($passable)
89
    {
90 31
        $this->passable = $passable;
91
92 31
        return $this;
93
    }
94
95
    /**
96
     * Set the array of pipes.
97
     *
98
     * @param  array|mixed  $pipes
99
     * @return $this
100
     */
101 28
    public function through($pipes)
102
    {
103 28
        $this->pipes = is_array($pipes) ? $pipes : func_get_args();
104
105 28
        return $this;
106
    }
107
108
    /**
109
     * Push additional pipes onto the pipeline.
110
     *
111
     * @param  array|mixed  $pipes
112
     * @return $this
113
     */
114 2
    public function pipe($pipes)
115
    {
116 2
        array_push($this->pipes, ...(is_array($pipes) ? $pipes : func_get_args()));
117
118 2
        return $this;
119
    }
120
121
    /**
122
     * Set the method to call on the pipes.
123
     *
124
     * @param  string  $method
125
     * @return $this
126
     */
127 3
    public function via($method)
128
    {
129 3
        $this->method = $method;
130
131 3
        return $this;
132
    }
133
134
    /**
135
     * Run the pipeline with a final destination callback.
136
     *
137
     * @return mixed
138
     */
139 31
    public function then(Closure $destination)
140
    {
141
        try {
142 31
            $this->fireEvent(PipelineStarted::class,
143 31
                $destination,
144 31
                $this->passable,
145 31
                $this->pipes(),
146 31
                $this->useTransaction,
147 31
            );
148
149 31
            $this->beginTransaction();
150
151 31
            $pipeline = array_reduce(
152 31
                array_reverse($this->pipes()),
153 31
                $this->carry(),
154 31
                $this->prepareDestination($destination)
155 31
            );
156
157 31
            $result = $pipeline($this->passable);
158
159 23
            $this->commitTransaction();
160
161 23
            $this->fireEvent(PipelineFinished::class,
162 23
                $destination,
163 23
                $this->passable,
164 23
                $this->pipes(),
165 23
                $this->useTransaction,
166 23
                $result,
167 23
            );
168
169 23
            return $result;
170 8
        } catch (Throwable $e) {
171 8
            $this->rollbackTransaction();
172
173 8
            if ($this->onFailure) {
174 4
                return ($this->onFailure)($this->passable, $e);
175
            }
176
177 4
            return $this->handleException($this->passable, $e);
178
        }
179
    }
180
181
    /**
182
     * Run the pipeline and return the result.
183
     *
184
     * @return mixed
185
     */
186 18
    public function thenReturn()
187
    {
188 18
        return $this->then(function ($passable) {
189 10
            return $passable;
190 18
        });
191
    }
192
193
    /**
194
     * Get the final piece of the Closure onion.
195
     *
196
     * @return \Closure
197
     */
198 31
    protected function prepareDestination(Closure $destination)
199
    {
200 31
        return function ($passable) use ($destination) {
201 19
            return $destination($passable);
202 31
        };
203
    }
204
205
    /**
206
     * Get a Closure that represents a slice of the application onion.
207
     *
208
     * @return \Closure
209
     */
210 31
    protected function carry()
211
    {
212 31
        return function ($stack, $pipe) {
213 29
            return function ($passable) use ($stack, $pipe) {
214 29
                $this->fireEvent(PipeExecutionStarted::class, $pipe, $passable);
215
216 29
                if (is_callable($pipe)) {
217
                    // If the pipe is a callable, then we will call it directly, but otherwise we
218
                    // will resolve the pipes out of the dependency container and call it with
219
                    // the appropriate method and arguments, returning the results back out.
220 11
                    $result = $pipe($passable, $stack);
221
222 8
                    $this->fireEvent(PipeExecutionFinished::class, $pipe, $passable);
223
224 8
                    return $result;
225 19
                } elseif (! is_object($pipe)) {
226 18
                    [$name, $parameters] = $this->parsePipeString($pipe);
227
228
                    // If the pipe is a string we will parse the string and resolve the class out
229
                    // of the dependency injection container. We can then build a callable and
230
                    // execute the pipe function giving in the parameters that are required.
231 18
                    $pipe = $this->getContainer()->make($name);
232
233 16
                    $parameters = array_merge([$passable, $stack], $parameters);
234
                } else {
235
                    // If the pipe is already an object we'll just make a callable and pass it to
236
                    // the pipe as-is. There is no need to do any extra parsing and formatting
237
                    // since the object we're given was already a fully instantiated object.
238 1
                    $parameters = [$passable, $stack];
239
                }
240
241 17
                $carry = method_exists($pipe, $this->method)
242 16
                                ? $pipe->{$this->method}(...$parameters)
243 1
                                : $pipe(...$parameters);
244
245 14
                $this->fireEvent(PipeExecutionFinished::class, $pipe, $passable);
246
247 14
                return $this->handleCarry($carry);
248 29
            };
249 31
        };
250
    }
251
252
    /**
253
     * Parse full pipe string to get name and parameters.
254
     *
255
     * @param  string  $pipe
256
     * @return array
257
     */
258 18
    protected function parsePipeString($pipe)
259
    {
260 18
        [$name, $parameters] = array_pad(explode(':', $pipe, 2), 2, []);
261
262 18
        if (is_string($parameters)) {
263 1
            $parameters = explode(',', $parameters);
264
        }
265
266 18
        return [$name, $parameters];
267
    }
268
269
    /**
270
     * Get the array of configured pipes.
271
     *
272
     * @return array
273
     */
274 31
    protected function pipes()
275
    {
276 31
        return $this->pipes;
277
    }
278
279
    /**
280
     * Get the container instance.
281
     *
282
     * @return \Illuminate\Contracts\Container\Container
283
     *
284
     * @throws \RuntimeException
285
     */
286 18
    protected function getContainer()
287
    {
288 18
        if (! $this->container) {
289 1
            throw new RuntimeException('A container instance has not been passed to the Pipeline.');
290
        }
291
292 17
        return $this->container;
293
    }
294
295
    /**
296
     * Set the container instance.
297
     *
298
     * @return $this
299
     */
300 1
    public function setContainer(Container $container)
301
    {
302 1
        $this->container = $container;
303
304 1
        return $this;
305
    }
306
307
    /**
308
     * Set callback to be executed on failure pipeline.
309
     *
310
     * @return $this
311
     */
312 4
    public function onFailure(Closure $callback)
313
    {
314 4
        $this->onFailure = $callback;
315
316 4
        return $this;
317
    }
318
319
    /**
320
     * Run a single pipe.
321
     */
322 4
    public function run(string $pipe, mixed $data = true): mixed
323
    {
324 4
        return $this
325 4
            ->send($data)
326 4
            ->through([$pipe])
327 4
            ->thenReturn();
328
    }
329
330
    /**
331
     * Handle the value returned from each pipe before passing it to the next.
332
     *
333
     * @param  mixed  $carry
334
     * @return mixed
335
     */
336 14
    protected function handleCarry($carry)
337
    {
338 14
        return $carry;
339
    }
340
341
    /**
342
     * Handle the given exception.
343
     *
344
     * @param  mixed  $passable
345
     * @return mixed
346
     *
347
     * @throws \Throwable
348
     */
349 4
    protected function handleException($passable, Throwable $e)
350
    {
351 4
        throw $e;
352
    }
353
}
354