Interceptor::handleStart()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
declare(strict_types=1);
3
namespace Modulate\Artisan\Interceptor;
4
5
use Modulate\Artisan\Interceptor\Contracts\ArtisanHandler;
6
use Modulate\Artisan\Interceptor\Contracts\Interceptor as InterceptorContract;
7
use Modulate\Artisan\Interceptor\Contracts\HandlerStack as HandlerStackContract;
8
use Modulate\Artisan\Interceptor\Contracts\Handler as HandlerContract;
9
use Modulate\Artisan\Interceptor\Contracts\OptionHandler as OptionHandlerContract;
10
use Modulate\Artisan\Interceptor\Enums\StackType;
11
use Modulate\Artisan\Interceptor\Handlers\CallbackHandler;
12
use Modulate\Artisan\Interceptor\Handlers\CallbackOptionHandler;
13
use Symfony\Component\Console\Application;
14
15
use Illuminate\Support\Arr;
16
use Modulate\Artisan\Interceptor\Handlers\ArtisanCallbackHandler;
17
use Symfony\Component\Console\Input\InputOption;
18
use Illuminate\Support\Traits\Macroable;
19
20
/**
21
 * The interceptor implements the core logic of the package and is responsible for interacting with
22
 * both the registered callbacks and the artisan instance
23
 */
24
class Interceptor implements InterceptorContract
25
{
26
    use Macroable;
0 ignored issues
show
Bug introduced by
The trait Illuminate\Support\Traits\Macroable requires the property $name which is not provided by Modulate\Artisan\Interceptor\Interceptor.
Loading history...
27
28
    /**
29
     * @var InputOption[]
30
     */
31
    protected array $options = [];
32
33
    /**
34
     * @var HandlerStackContract
35
     */
36
    protected HandlerStackContract $start;
37
38
    /**
39
     * @var HandlerStackContract
40
     */
41
    protected HandlerStackContract $before;
42
43
    /**
44
     * @var HandlerStackContract
45
     */
46
    protected HandlerStackContract $after;
47
48
    /**
49
     * @var Application
50
     */
51
    protected $app;
52
53
    /**
54
     * @var bool
55
     */
56
    protected bool $started = false;
57
58
59
    /**
60
     * @param HandlerStackContract $start
61
     * @param HandlerStackContract $before
62
     * @param HandlerStackContract $after
63
     */
64 8
    public function __construct(HandlerStackContract $start, HandlerStackContract $before, HandlerStackContract $after)
65
    {
66 8
        $this->start  = $start;
67 8
        $this->before = $before;
68 8
        $this->after  = $after;
69
    }
70
71
    /**
72
     * Add a handler to the interceptor
73
     *
74
     * @param HandlerContract $handler
75
     * @param string|null $option
76
     * @return InterceptorContract
77
     */
78 6
    public function addHandler(HandlerContract|ArtisanHandler $handler, StackType $stack = StackType::before, string $option = null): InterceptorContract
79
    {
80 6
        if ($handler instanceof OptionHandlerContract) {
81 1
            $handler->setOption($option);
0 ignored issues
show
Bug introduced by
It seems like $option can also be of type null; however, parameter $option of Modulate\Artisan\Interce...ionHandler::setOption() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

81
            $handler->setOption(/** @scrutinizer ignore-type */ $option);
Loading history...
82
        }
83 6
        if ($stack) {
0 ignored issues
show
introduced by
$stack is of type Modulate\Artisan\Interceptor\Enums\StackType, thus it always evaluated to true.
Loading history...
84 6
            $this->{$stack->value}->push($handler);
85
        }
86
87 6
        return $this;
88
    }
89
90
    /**
91
     * Add a handler or callable to run on artisan start
92
     *
93
     * @param callable|ArtisanHandler $callable
94
     * @return InterceptorContract
95
     */
96 2
    public function start(callable|ArtisanHandler $callable): InterceptorContract
97
    {
98 2
        $handler = $callable;
99 2
        if (is_callable($callable)) {
100 2
            $handler = new ArtisanCallbackHandler($callable);
0 ignored issues
show
Bug introduced by
It seems like $callable can also be of type Modulate\Artisan\Interce...ontracts\ArtisanHandler; however, parameter $callable of Modulate\Artisan\Interce...kHandler::__construct() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

100
            $handler = new ArtisanCallbackHandler(/** @scrutinizer ignore-type */ $callable);
Loading history...
101
        }
102
103 2
        $this->addHandler($handler, StackType::start);
0 ignored issues
show
Bug introduced by
It seems like $handler can also be of type callable; however, parameter $handler of Modulate\Artisan\Interce...terceptor::addHandler() does only seem to accept Modulate\Artisan\Interce...eptor\Contracts\Handler, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

103
        $this->addHandler(/** @scrutinizer ignore-type */ $handler, StackType::start);
Loading history...
104
105 2
        return $this;
106
    }
107
108
    /**
109
     * Add a handler or callable to run on before command starts
110
     *
111
     * @param callable|HandlerContract $callable
112
     * @param string|null $option
113
     * @return InterceptorContract
114
     */
115 5
    public function before(callable|HandlerContract $callable, string $option = null): InterceptorContract
116
    {
117 5
        $handler = $callable;
118 5
        if (is_callable($callable)) {
119 5
            if ($option) {
120 1
                $handler = new CallbackOptionHandler(
121 1
                    $option,
122 1
                    $callable
0 ignored issues
show
Bug introduced by
It seems like $callable can also be of type Modulate\Artisan\Interceptor\Contracts\Handler; however, parameter $callable of Modulate\Artisan\Interce...nHandler::__construct() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

122
                    /** @scrutinizer ignore-type */ $callable
Loading history...
123 1
                );
124
            } else {
125 4
                $handler = new CallbackHandler($callable);
0 ignored issues
show
Bug introduced by
It seems like $callable can also be of type Modulate\Artisan\Interceptor\Contracts\Handler; however, parameter $callable of Modulate\Artisan\Interce...kHandler::__construct() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

125
                $handler = new CallbackHandler(/** @scrutinizer ignore-type */ $callable);
Loading history...
126
            }
127
        }
128
129 5
        $this->addHandler($handler, StackType::before, $option);
0 ignored issues
show
Bug introduced by
It seems like $handler can also be of type callable; however, parameter $handler of Modulate\Artisan\Interce...terceptor::addHandler() does only seem to accept Modulate\Artisan\Interce...eptor\Contracts\Handler, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

129
        $this->addHandler(/** @scrutinizer ignore-type */ $handler, StackType::before, $option);
Loading history...
130
131 5
        return $this;
132
    }
133
134
    /**
135
     * Add a handler or callable to run on after command finishes
136
     *
137
     * @param callable|HandlerContract $callable
138
     * @param string $option
139
     * @return InterceptorContract
140
     */
141 1
    public function after(callable|HandlerContract $callable, string $option = null): InterceptorContract
142
    {
143 1
        $handler = $callable;
144 1
        if (is_callable($callable)) {
145 1
            if ($option) {
146
                $handler = new CallbackOptionHandler(
147
                    $option,
148
                    $callable
0 ignored issues
show
Bug introduced by
It seems like $callable can also be of type Modulate\Artisan\Interceptor\Contracts\Handler; however, parameter $callable of Modulate\Artisan\Interce...nHandler::__construct() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

148
                    /** @scrutinizer ignore-type */ $callable
Loading history...
149
                );
150
            } else {
151 1
                $handler = new CallbackHandler($callable);
0 ignored issues
show
Bug introduced by
It seems like $callable can also be of type Modulate\Artisan\Interceptor\Contracts\Handler; however, parameter $callable of Modulate\Artisan\Interce...kHandler::__construct() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

151
                $handler = new CallbackHandler(/** @scrutinizer ignore-type */ $callable);
Loading history...
152
            }
153
        }
154
155 1
        $this->addHandler($handler, StackType::after, $option);
0 ignored issues
show
Bug introduced by
It seems like $handler can also be of type callable; however, parameter $handler of Modulate\Artisan\Interce...terceptor::addHandler() does only seem to accept Modulate\Artisan\Interce...eptor\Contracts\Handler, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

155
        $this->addHandler(/** @scrutinizer ignore-type */ $handler, StackType::after, $option);
Loading history...
156
157 1
        return $this;
158
    }
159
160
    /**
161
     * Adds an option to the application
162
     * @param InputOption $option The input option to add
163
     *
164
     * @return Interceptor
165
     */
166 4
    public function addOption(InputOption $option): Interceptor
167
    {
168
        // If the app has not yet started store a reference to the option to
169
        // add to the options array for adding after artisan has started,
170
        // otherwise add it directly to the app
171 4
        if ($this->started === false) {
172 4
            $this->options[] = $option;
173
        } else {
174
            $this->app->getDefinition()->addOption($option);
175
        }
176
177 4
        return $this;
178
    }
179
180
    /**
181
     * Add options to the interceptor
182
     *
183
     * @param InputOption[] $options
184
     * @return Interceptor
185
     */
186 1
    public function addOptions(...$options): Interceptor
187
    {
188
        // Ensure we always have a single array of options
189
        // regardless of if an array or list of options
190
        // was passed through the method
191 1
        $options = Arr::wrap($options[0]);
192
193 1
        foreach ($options as $option) {
194 1
            $this->addOption($option);
195
        }
196
197 1
        return $this;
198
    }
199
200
    /**
201
     * @internal
202
     * Called during the artisan boot process to inject the application into the handler
203
     * @param Application $app The symfony console application
204
     *
205
     * @return Interceptor
206
     */
207 8
    public function starting(Application $app): Interceptor
208
    {
209 8
        $this->app = $app;
210 8
        foreach ($this->options as $option) {
211
212 4
            $this->app->getDefinition()->addOption($option);
213
        }
214
215 8
        $this->options = [];
216
217 8
        $this->started = true;
218
219 8
        $this->handleStart($app);
220
221 8
        return $this;
222
    }
223
224
    /**
225
     * Check to see if artisan has been started
226
     */
227 2
    public function isStarted(): bool
228
    {
229 2
        return $this->started;
230
    }
231
232
    /**
233
     * @internal This method is only exposed for use in unit tests. Do not rely on it for your application and instead register a start handler
234
     *
235
     * @return Application
236
     */
237 2
    public function getArtisan(): Application
238
    {
239 2
        if (!$this->isStarted()) {
240
            throw new \RuntimeException('Artisan cannot be returned before the interceptor has started');
241
        }
242
243 2
        return $this->app;
244
    }
245
246
    /**
247
     * Handle a stack
248
     *
249
     * @param InterceptedCommand|Application $intercepted
250
     * @param StackType $stackType
251
     * @return void
252
     */
253 8
    protected function handle(
254
        InterceptedCommand|Application $intercepted,
255
        StackType $stackType,
256
    ): void {
257 8
        $stack = $stackType->value;
258
        // Move the stack pointer back to the start
259 8
        $this->$stack->reset();
260
261 8
        if (method_exists($intercepted, 'setArtisan')) {
262 6
            $intercepted->setArtisan($this->app);
263
        }
264
265
266 8
        if (!$this->$stack->empty()) {
267
            do {
268 6
                $current = $this->$stack->current();
269
                // Skip over any handlers where the check returns false
270 6
                if (!$current->check($intercepted)) {
271 1
                    continue;
272
                }
273 6
                $current->handle($intercepted);
274 6
            } while ($this->$stack->next());
275
        }
276
    }
277
278
    /**
279
     * Handles artisan starting stack
280
     *
281
     * @param Application $artisan
282
     * @return void
283
     */
284 8
    public function handleStart(Application $artisan): void
285
    {
286 8
        $this->handle($artisan, StackType::start);
287
    }
288
289
    /**
290
     * The handleBefore method is called once the CommandStarting event
291
     *
292
     * @param InterceptedCommand $intercepted
293
     * @return void
294
     */
295 6
    public function handleBefore(InterceptedCommand $intercepted): void
296
    {
297 6
        $this->handle($intercepted, StackType::before);
298
    }
299
300
    /**
301
     * The handleAfter method is called once the CommandFinished event
302
     *
303
     * @param InterceptedCommand $intercepted
304
     * @return void
305
     */
306 6
    public function handleAfter(InterceptedCommand $intercepted): void
307
    {
308 6
        $this->handle($intercepted, StackType::after);
309
    }
310
}
311