Completed
Push — master ( d234f3...0a4926 )
by Jan Philipp
47s queued 12s
created

Application::run()   A

Complexity

Conditions 2
Paths 5

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.7333
c 0
b 0
f 0
cc 2
nc 5
nop 1
1
<?php declare(strict_types=1);
2
3
namespace Shopware\Psh\Application;
4
5
use InvalidArgumentException;
6
use Khill\Duration\Duration;
7
use League\CLImate\CLImate;
8
use Shopware\Psh\Config\Config;
9
use Shopware\Psh\Config\RequiredValue;
10
use Shopware\Psh\Listing\Script;
11
use Shopware\Psh\Listing\ScriptFinder;
12
use Shopware\Psh\Listing\ScriptNotFoundException;
13
use Shopware\Psh\ScriptRuntime\Execution\ExecutionErrorException;
14
use function array_key_exists;
15
use function array_map;
16
use function array_merge;
17
use function count;
18
use function explode;
19
use function implode;
20
use function mb_strlen;
21
use function sprintf;
22
23
/**
24
 * Main application entry point. moves the requested data around and outputs user information.
25
 */
26
class Application
27
{
28
    const MIN_PADDING_SIZE = 30;
29
30
    /**
31
     * @var CLImate
32
     */
33
    public $cliMate;
34
35
    /**
36
     * @var string
37
     */
38
    private $rootDirectory;
39
40
    /**
41
     * @var ApplicationFactory
42
     */
43
    private $applicationFactory;
44
45
    /**
46
     * @var Duration
47
     */
48
    private $duration;
49
50
    public function __construct(string $rootDirectory)
51
    {
52
        $this->rootDirectory = $rootDirectory;
53
        $this->applicationFactory = new ApplicationFactory();
54
        $this->cliMate = new CLImate();
55
        $this->duration = new Duration();
56
    }
57
58
    /**
59
     * Main entry point to execute the application.
60
     *
61
     * @return int exit code
62
     */
63
    public function run(array $inputArgs): int
64
    {
65
        try {
66
            $config = $this->prepare($inputArgs);
67
68
            $scriptFinder = $this->applicationFactory->createScriptFinder($config);
69
70
            $this->executeScript($inputArgs, $scriptFinder, $config);
71
72
            $this->showListing($scriptFinder->getAllVisibleScripts());
73
74
            throw ExitSignal::success();
75
        } catch (ExitSignal $signal) {
76
            return $signal->signal();
77
        }
78
    }
79
80
    /**
81
     * @param Script[] $scripts
82
     */
83
    public function showListing(array $scripts): void
84
    {
85
        $this->cliMate->green()->bold('Available commands:')->br();
86
87
        if (!count($scripts)) {
88
            $this->cliMate->yellow()->bold('-> Currently no scripts available');
89
        }
90
91
        $paddingSize = $this->getPaddingSize($scripts);
92
        $padding = $this->cliMate->padding($paddingSize)->char(' ');
93
94
        $scriptEnvironment = false;
95
96
        foreach ($scripts as $script) {
97
            if ($script->getEnvironment() !== $scriptEnvironment) {
98
                $scriptEnvironment = $script->getEnvironment();
99
                $this->cliMate->green()->br()->bold(($scriptEnvironment ?? 'default') . ':');
100
            }
101
102
            $padding
103
                ->label('<bold> - ' . $script->getName() . '</bold>')
104
                ->result('<dim>' . $script->getDescription() . '</dim>');
105
        }
106
107
        $this->cliMate->green()->bold("\n" . count($scripts) . " script(s) available\n");
108
    }
109
110
    private function extractScriptNames(array $inputArgs): array
111
    {
112
        if (!isset($inputArgs[1])) {
113
            return [];
114
        }
115
116
        return explode(',', $inputArgs[1]);
117
    }
118
119
    private function execute(Script $script, Config $config, ScriptFinder $scriptFinder): void
120
    {
121
        $commands = $this->applicationFactory
122
            ->createCommands($script, $scriptFinder);
123
124
        $logger = new ClimateLogger($this->cliMate, $this->duration);
125
        $executor = $this->applicationFactory
126
            ->createProcessExecutor($script, $config, $logger, $this->rootDirectory);
127
128
        try {
129
            $executor->execute($script, $commands);
130
        } catch (ExecutionErrorException $e) {
131
            $this->notifyError("\nExecution aborted, a subcommand failed!\n");
132
133
            throw ExitSignal::error();
134
        }
135
136
        $this->notifySuccess("All commands successfully executed!\n");
137
    }
138
139
    /**
140
     * @param $string
141
     */
142
    private function notifySuccess(string $string): void
143
    {
144
        $this->cliMate->bold()->green($string);
145
    }
146
147
    /**
148
     * @param $string
149
     */
150
    public function notifyError(string $string): void
151
    {
152
        $this->cliMate->bold()->red($string);
153
    }
154
155
    private function getPaddingSize(array $scripts): int
156
    {
157
        $maxScriptNameLength = 0;
158
        foreach ($scripts as $script) {
159
            if (mb_strlen($script->getName()) > $maxScriptNameLength) {
160
                $maxScriptNameLength = mb_strlen($script->getName());
161
            }
162
        }
163
164
        return $maxScriptNameLength + self::MIN_PADDING_SIZE;
165
    }
166
167
    private function showAutocompleteListing(Config $config): void
168
    {
169
        $scriptFinder = $this->applicationFactory
170
            ->createScriptFinder($config);
171
172
        $scripts = $scriptFinder->getAllVisibleScripts();
173
174
        $commands = array_map(function (Script $script) {
175
            return $script->getName();
176
        }, $scripts);
177
178
        $this->cliMate->out(implode(' ', $commands));
179
    }
180
181
    private function showScriptNotFoundListing(ScriptNotFoundException $ex, array $scriptNames, ScriptFinder $scriptFinder): void
182
    {
183
        $this->notifyError("Script with name {$ex->getScriptName()} not found\n");
184
185
        $scripts = [];
186
        foreach ($scriptNames as $scriptName) {
187
            $newScripts = $scriptFinder->findScriptsByPartialName($scriptName);
188
            $scripts = array_merge($scripts, $newScripts);
189
        }
190
191
        if (count($scripts) > 0) {
192
            $this->cliMate->yellow()->bold('Have you been looking for this?');
193
            $this->showListing($scripts);
194
        }
195
    }
196
197
    private function printHead(Config $config, ApplicationConfigLogger $logger): void
198
    {
199
        $this->cliMate->green()->bold()->out("\n###################");
200
201
        if ($config->getHeader()) {
202
            $this->cliMate->out("\n" . $config->getHeader());
203
        }
204
205
        $logger->printOut($this->cliMate);
206
    }
207
208
    private function validateConfig(Config $config, ?string $environment = null): void
209
    {
210
        $allPlaceholders = $config->getAllPlaceholders($environment);
211
212
        $missing = [];
213
        foreach ($config->getRequiredVariables($environment) as $requiredVariable) {
214
            if (!array_key_exists($requiredVariable->getName(), $allPlaceholders)) {
215
                $missing[] = $requiredVariable;
216
                $this->printMissingRequiredVariable($requiredVariable);
217
            }
218
        }
219
220
        if (count($missing)) {
221
            $this->cliMate->error("\n<bold>Please define the missing value(s) first</bold>\n");
222
            throw ExitSignal::error();
223
        }
224
    }
225
226
    private function printMissingRequiredVariable(RequiredValue $requiredVariable): void
227
    {
228
        if ($requiredVariable->hasDescription()) {
229
            $this->cliMate->error(sprintf(
230
                "\t - <bold>Missing required const or var named <underline>%s</underline></bold> <dim>(%s)</dim>",
231
                $requiredVariable->getName(),
232
                $requiredVariable->getDescription()
233
            ));
234
        } else {
235
            $this->cliMate->error(sprintf(
236
                "\t - <bold>Missing required const or var named <underline>%s</underline></bold>",
237
                $requiredVariable->getName()
238
            ));
239
        }
240
    }
241
242
    private function prepare(array $inputArgs): Config
243
    {
244
        $configLogger = new ApplicationConfigLogger($this->rootDirectory, $this->cliMate);
0 ignored issues
show
Unused Code introduced by
The call to ApplicationConfigLogger::__construct() has too many arguments starting with $this->cliMate.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
245
246
        try {
247
            $config = $this->applicationFactory
248
                ->createConfig($configLogger, $this->rootDirectory, $inputArgs);
249
        } catch (InvalidParameterException | InvalidArgumentException $e) {
250
            $this->notifyError("\n" . $e->getMessage() . "\n");
251
252
            throw ExitSignal::error();
253
        }
254
255
        if (count($inputArgs) > 1 && $inputArgs[1] === 'bash_autocompletion_dump') {
256
            $this->showAutocompleteListing($config);
257
258
            throw ExitSignal::success();
259
        }
260
261
        $this->printHead($config, $configLogger);
262
        $this->validateConfig($config);
263
264
        return $config;
265
    }
266
267
    private function executeScript(array $inputArgs, ScriptFinder $scriptFinder, Config $config): void
268
    {
269
        $scriptNames = $this->extractScriptNames($inputArgs);
270
271
        if (!count($scriptNames)) {
272
            return;
273
        }
274
275
        try {
276
            foreach ($scriptNames as $scriptName) {
277
                $this->execute($scriptFinder->findScriptByName($scriptName), $config, $scriptFinder);
278
            }
279
        } catch (ScriptNotFoundException $e) {
280
            $this->showScriptNotFoundListing($e, $scriptNames, $scriptFinder);
281
282
            throw ExitSignal::error();
283
        }
284
285
        throw ExitSignal::success();
286
    }
287
}
288