GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Master   F
last analyzed

Complexity

Total Complexity 61

Size/Duplication

Total Lines 292
Duplicated Lines 0 %

Test Coverage

Coverage 76.25%

Importance

Changes 0
Metric Value
eloc 140
dl 0
loc 292
c 0
b 0
f 0
rs 3.52
ccs 106
cts 139
cp 0.7625
wmc 61

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 1
A allFinished() 0 8 3
F run() 0 75 24
A connect() 0 28 6
A gatherOutput() 0 11 4
A cumulativeExitCode() 0 8 3
A stringifyVerbosity() 0 15 6
A createConnectProcess() 0 12 3
B runTask() 0 53 8
A createProcess() 0 12 3

How to fix   Complexity   

Complex Class

Complex classes like Master often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Master, and based on these observations, apply Extract Interface, too.

1
<?php declare(strict_types=1);
2
/* (c) Anton Medvedev <[email protected]>
3
 *
4
 * For the full copyright and license information, please view the LICENSE
5
 * file that was distributed with this source code.
6
 */
7
8
namespace Deployer\Executor;
9
10
use Deployer\Component\Ssh\Client;
11
use Deployer\Configuration\Configuration;
12
use Deployer\Deployer;
13
use Deployer\Exception\Exception;
14
use Deployer\Host\Host;
15
use Deployer\Host\Localhost;
16
use Deployer\Selector\Selector;
17
use Deployer\Task\Task;
18
use Symfony\Component\Console\Input\InputInterface;
19
use Symfony\Component\Console\Output\OutputInterface;
20
use Symfony\Component\Process\Process;
21
22 1
const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
23
24
function spinner($message = '')
25
{
26 4
    $frame = FRAMES[(int)(microtime(true) * 10) % count(FRAMES)];
27 4
    return "  $frame $message\r";
28
}
29
30
class Master
31
{
32
    private $input;
33
    private $output;
34
    private $server;
35
    private $messenger;
36
    private $client;
37
    private $config;
38
39 12
    public function __construct(
40
        InputInterface $input,
41
        OutputInterface $output,
42
        Server $server,
43
        Messenger $messenger,
44
        Client $client,
45
        Configuration $config
46
    )
47
    {
48 12
        $this->input = $input;
49 12
        $this->output = $output;
50 12
        $this->server = $server;
51 12
        $this->messenger = $messenger;
52 12
        $this->client = $client;
53 12
        $this->config = $config;
54 12
    }
55
56
    /**
57
     * @param Task[] $tasks
58
     * @param Host[] $hosts
59
     * @param Planner|null $plan
60
     * @return int
61
     */
62 12
    public function run(array $tasks, array $hosts, $plan = null): int
63
    {
64 12
        $plan || $this->server->start();
65 12
        $plan || $this->connect($hosts);
66
67 12
        $globalLimit = (int)$this->input->getOption('limit') ?: count($hosts);
68
69 12
        foreach ($tasks as $task) {
70 12
            $plan || $this->messenger->startTask($task);
71
72 12
            $plannedHosts = $hosts;
73
74 12
            $limit = min($globalLimit, $task->getLimit() ?? $globalLimit);
75
76 12
            if ($task->isOnce()) {
77 3
                $plannedHosts = [];
78 3
                foreach ($hosts as $currentHost) {
79 3
                    if (Selector::apply($task->getSelector(), $currentHost)) {
80 3
                        $plannedHosts[] = $currentHost;
81 3
                        break;
82
                    }
83
                }
84
            }
85
86 12
            if ($task->isLocal()) {
87
                $plannedHosts = [new Localhost('localhost')];
88
            }
89
90 12
            if ($limit === 1 || count($plannedHosts) === 1) {
91 9
                foreach ($plannedHosts as $currentHost) {
92 9
                    if (!Selector::apply($task->getSelector(), $currentHost)) {
93
                        if ($plan) {
94
                            $plan->commit([], $task);
95
                        }
96
                        continue;
97
                    }
98
99 9
                    if ($plan) {
100
                        $plan->commit([$currentHost], $task);
101
                        continue;
102
                    }
103
104 9
                    $exitCode = $this->runTask($task, [$currentHost]);
105 9
                    if ($exitCode !== 0) {
106 2
                        return $exitCode;
107
                    }
108
                }
109
            } else {
110 4
                foreach (array_chunk($hosts, $limit) as $chunk) {
111 4
                    $selector = $task->getSelector();
112 4
                    $selectedHosts = [];
113 4
                    foreach ($chunk as $currentHost) {
114 4
                        if ($selector === null || Selector::apply($selector, $currentHost)) {
115 4
                            $selectedHosts[] = $currentHost;
116
                        }
117
                    }
118
119 4
                    if ($plan) {
120
                        $plan->commit($selectedHosts, $task);
121
                        continue;
122
                    }
123
124 4
                    $exitCode = $this->runTask($task, $selectedHosts);
125 4
                    if ($exitCode !== 0) {
126
                        return $exitCode;
127
                    }
128
                }
129
            }
130
131 12
            if (!$plan) {
132 12
                $this->messenger->endTask($task);
133
            }
134
        }
135
136 12
        return 0;
137
    }
138
139
    /**
140
     * @param Host[] $hosts
141
     */
142 12
    private function connect(array $hosts)
143
    {
144
        $callback = function (string $output) {
145
            $output = preg_replace('/\n$/', '', $output);
146
            if (strlen($output) !== 0) {
147
                $this->output->writeln($output);
148
            }
149 12
        };
150
151
        // Connect to each host sequentially, to prevent getting locked.
152 12
        foreach ($hosts as $host) {
153 12
            if ($host instanceof Localhost) {
154 12
                continue;
155
            }
156
            $process = $this->createConnectProcess($host);
157
            $process->start();
158
159
            while ($process->isRunning()) {
160
                $this->gatherOutput([$process], $callback);
161
                if ($this->output->isDecorated()) {
162
                    $this->output->write(spinner(str_pad("connect {$host->getTag()}", intval(getenv('COLUMNS')) - 1)));
163
                }
164
                usleep(1000);
165
            }
166
        }
167
168
        // Clear spinner.
169 12
        $this->output->write(str_repeat(' ', intval(getenv('COLUMNS')) - 1) . "\r");
170 12
    }
171
172
    /**
173
     * @param Task $task
174
     * @param Host[] $hosts
175
     * @return int
176
     */
177 12
    private function runTask(Task $task, array $hosts): int
178
    {
179 12
        if (getenv('DEPLOYER_LOCAL_WORKER') === 'true') {
180
            // This allows to code coverage all recipe,
181
            // as well as speedup tests by not spawning
182
            // lots of processes. Also there is a few tests
183
            // what runs with workers for tests subprocess
184
            // communications.
185 8
            foreach ($hosts as $host) {
186 8
                $worker = new Worker(Deployer::get());
187 8
                $exitCode = $worker->execute($task, $host);
188 8
                if ($exitCode !== 0) {
189 2
                    return $exitCode;
190
                }
191
            }
192 8
            return 0;
193
        }
194
195
        $callback = function (string $output) {
196 3
            $output = preg_replace('/\n$/', '', $output);
197 3
            if (strlen($output) !== 0) {
198 3
                $this->output->writeln($output);
199
            }
200 4
        };
201
202
        /** @var Process[] $processes */
203 4
        $processes = [];
204
205
        $this->server->addTimer(0, function () use(&$processes, $hosts, $task) {
206 4
            foreach ($hosts as $host) {
207 4
                $processes[] = $this->createProcess($host, $task);
208
            }
209
210 4
            foreach ($processes as $process) {
211 4
                $process->start();
212
            }
213 4
        });
214
215
        $this->server->addPeriodicTimer(0.03, function ($timer) use (&$processes, $callback) {
216 4
            $this->gatherOutput($processes, $callback);
217 4
            $this->output->write(spinner());
218 4
            if ($this->allFinished($processes)) {
219 4
                $this->server->stop();
220 4
                $this->server->cancelTimer($timer);
221
            }
222 4
        });
223
224 4
        $this->server->run();
225
226 4
        $this->output->write("    \r"); // clear spinner
227 4
        $this->gatherOutput($processes, $callback);
228
229 4
        return $this->cumulativeExitCode($processes);
230
    }
231
232 4
    protected function createProcess(Host $host, Task $task): Process
233
    {
234 4
        $dep = PHP_BINARY . ' ' . DEPLOYER_BIN;
0 ignored issues
show
Bug introduced by Anton Medvedev
The constant Deployer\Executor\DEPLOYER_BIN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
235 4
        $decorated = $this->output->isDecorated() ? '--decorated' : '';
236 4
        $verbosity = self::stringifyVerbosity($this->output->getVerbosity());
237 4
        $command = "$dep worker $task {$host->getAlias()} {$this->server->getPort()} {$this->input} $decorated $verbosity";
238
239 4
        if ($this->output->isDebug()) {
240 1
            $this->output->writeln("[{$host->getTag()}] $command");
241
        }
242
243 4
        return Process::fromShellCommandline($command);
244
    }
245
246
    protected function createConnectProcess(Host $host): Process
247
    {
248
        $dep = PHP_BINARY . ' ' . DEPLOYER_BIN;
0 ignored issues
show
Bug introduced by Anton Medvedev
The constant Deployer\Executor\DEPLOYER_BIN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
249
        $decorated = $this->output->isDecorated() ? '--decorated' : '';
250
        $verbosity = self::stringifyVerbosity($this->output->getVerbosity());
251
        $command = "$dep connect {$host->getAlias()} $decorated $verbosity";
252
253
        if ($this->output->isDebug()) {
254
            $this->output->writeln("[{$host->getTag()}] $command");
255
        }
256
257
        return Process::fromShellCommandline($command);
258
    }
259
260
    /**
261
     * @param Process[] $processes
262
     * @return bool
263
     */
264 4
    protected function allFinished(array $processes): bool
265
    {
266 4
        foreach ($processes as $process) {
267 4
            if (!$process->isTerminated()) {
268 4
                return false;
269
            }
270
        }
271 4
        return true;
272
    }
273
274
    /**
275
     * @param Process[] $processes
276
     * @param callable $callback
277
     */
278 4
    protected function gatherOutput(array $processes, callable $callback)
279
    {
280 4
        foreach ($processes as $process) {
281 4
            $output = $process->getIncrementalOutput();
282 4
            if (strlen($output) !== 0) {
283 3
                $callback($output);
284
            }
285
286 4
            $errorOutput = $process->getIncrementalErrorOutput();
287 4
            if (strlen($errorOutput) !== 0) {
288
                $callback($errorOutput);
289
            }
290
        }
291 4
    }
292
293
    /**
294
     * @param Process[] $processes
295
     * @return int
296
     */
297 4
    protected function cumulativeExitCode(array $processes): int
298
    {
299 4
        foreach ($processes as $process) {
300 4
            if ($process->getExitCode() > 0) {
301
                return $process->getExitCode();
0 ignored issues
show
Bug Best Practice introduced by Vitaliy Ryaboy
The expression return $process->getExitCode() could return the type null which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
302
            }
303
        }
304 4
        return 0;
305
    }
306
307 4
    private static function stringifyVerbosity(int $verbosity): string
308
    {
309 4
        switch ($verbosity) {
310
            case OutputInterface::VERBOSITY_QUIET:
311
                return '-q';
312
            case OutputInterface::VERBOSITY_NORMAL:
313 3
                return '';
314
            case OutputInterface::VERBOSITY_VERBOSE:
315
                return '-v';
316
            case OutputInterface::VERBOSITY_VERY_VERBOSE:
317
                return '-vv';
318
            case OutputInterface::VERBOSITY_DEBUG:
319 1
                return '-vvv';
320
            default:
321
                throw new Exception('Unknown verbosity level: ' . $verbosity);
322
        }
323
    }
324
}
325