Passed
Push — main ( d65f74...8a9455 )
by Michael
03:18
created

Pipeline::rollbackTransaction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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