Passed
Push — master ( 1cab99...6bc53d )
by Francesc
01:56
created

ConsoleManager   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 303
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 303
rs 8.8
c 0
b 0
f 0
wmc 36

14 Methods

Rating   Name   Duplication   Size   Complexity  
A getAvailableCommands() 0 10 3
A showHelp() 0 7 1
A getUserMethods() 0 8 1
A showAvailableCommands() 0 9 2
A optionNotAvailable() 0 3 1
A getAvailableOptions() 0 14 2
A run() 0 12 2
B execute() 0 60 8
C __construct() 0 27 8
A getDescription() 0 3 1
A getAllFcqns() 0 9 2
A getFileNames() 0 10 2
A getFullNameSpace() 0 11 2
A getClassName() 0 6 1
1
<?php
2
/**
3
 * This file is part of FSConsoleTools
4
 * Copyright (C) 2018 Francesc Pineda Segarra <[email protected]>
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU Lesser General Public License as
8
 * published by the Free Software Foundation, either version 3 of the
9
 * License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public License
17
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
 */
19
20
namespace FacturaScriptsUtils\Console;
21
22
use Symfony\Component\Finder\Finder;
23
24
/**
25
 * This class is a start point for php-cli commands.
26
 *
27
 * @author Francesc Pineda Segarra <[email protected]>
28
 */
29
class ConsoleManager extends ConsoleAbstract
30
{
31
    /**
32
     * ConsoleManager constructor.
33
     *
34
     * @param int   $argc
35
     * @param array $argv
36
     */
37
    public function __construct(int $argc, array $argv)
38
    {
39
        $this->argv = $argv;
40
41
        // Check that at least there are 2 params (console & command)
42
        if ($argc >= 2) {
43
            // Check if first param is an option or a command
44
            switch ($this->argv[1]) {
45
                case '-l':
46
                case '--list':
47
                    $this->showAvailableCommands();
48
                    break;
49
50
                case '-h':
51
                case '--help':
52
                    break;
53
54
                case 0 === \strpos($this->argv[1], '-'):
55
                case 0 === \strpos($this->argv[1], '--'):
56
                    $this->optionNotAvailable($this->argv[0], $this->argv[1]);
57
                    break;
58
                default:
59
                    $this->run();
60
            }
61
        }
62
63
        $this->showHelp();
64
    }
65
66
    /**
67
     * Start point to run the command.
68
     *
69
     * @param array $params
70
     *
71
     * @return int
72
     */
73
    public function run(...$params): int
74
    {
75
        $cmd = $this->argv[1];
76
77
        if (class_exists(__NAMESPACE__ . '\Command\\' . $cmd)) {
78
            exit($this->execute());
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return integer. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
79
        }
80
81
        echo \PHP_EOL . 'ERROR: Command "' . $cmd . '" not found.' . \PHP_EOL . \PHP_EOL;
82
83
        $this->showHelp();
84
        return -1;
85
    }
86
87
    /**
88
     * Return description about this class.
89
     *
90
     * @return string
91
     */
92
    public function getDescription(): string
93
    {
94
        return 'The Console Manager';
95
    }
96
97
    /**
98
     * Print help information to the user.
99
     */
100
    public function showHelp()
101
    {
102
        echo 'Use as: php vendor/bin/console [COMMAND] [OPTIONS]' . \PHP_EOL;
103
        echo 'Available options:' . \PHP_EOL;
104
        echo '   -h, --help        Show this help.' . \PHP_EOL;
105
        echo '   -l, --list        Show a list of available commands.' . \PHP_EOL;
106
        echo \PHP_EOL;
107
    }
108
109
    /**
110
     * Returns an associative array of available methods for the user.
111
     * Add more options if you want to add support for custom methods.
112
     *      [
113
     *          '-h'        => 'showHelp',
114
     *          '--help'    => 'showHelp',
115
     *          '-l'        => 'showAvailableCommands',
116
     *          '--list'    => 'showAvailableCommands',
117
     *      ]
118
     *
119
     * @return array
120
     */
121
    public function getUserMethods(): array
122
    {
123
        // Adding extra method
124
        $methods = parent::getUserMethods();
125
        $methods['-l'] = 'showAvailableCommands';
126
        $methods['--list'] = 'showAvailableCommands';
127
128
        return $methods;
129
    }
130
131
    /**
132
     * Exec the command with the given options
133
     *
134
     * @return int
135
     */
136
    public function execute(): int
137
    {
138
        $status = -1;
139
        $params = $this->argv;
140
        \array_shift($params); // Extract console
141
        // command class
142
        $cmd = \array_shift($params); // Extract command
143
        // $params contains adicional parameters if are received
144
145
        switch ($cmd) {
146
            case '-h':
147
            case '--help':
148
                $this->getAvailableOptions($cmd);
149
                break;
150
            default:
151
                $className = __NAMESPACE__ . '\Command\\' . $cmd;
152
                $methods = \call_user_func([new $className(), 'getUserMethods']);
153
                // Forced in ConsoleAbstract, but we don't want to show it to users
154
                $methods['run'] = 'run';
155
156
                // If not alias, we want to directly run
157
                $alias = $params[0] ?? 'run';
158
                // If not method match, show how it works
159
                $method = $methods[$alias[0]] ?? 'showHelp';
160
161
                if (\array_key_exists($alias, $methods)) {
162
                    // Check if method is in class or parent class
163
                    if (\in_array($method, \get_class_methods($className), false) ||
164
                        \in_array($method, \get_class_methods(\get_parent_class($className)), false)
165
                    ) {
166
                        $status = (int) \call_user_func_array([new $className(), 'run'], $params);
167
                        break;
168
                    }
169
                    // Can be deleted, but starting with this can be helpful
170
                    if (\FS_DEBUG) {
171
                        $msg = '#######################################################################################'
172
                            . \PHP_EOL . '# ERROR: "' . $method . '" not defined in "' . $className . '"' . \PHP_EOL
173
                            . '#    Maybe you have a misspelling on the method name or is a missing declaration?'
174
                            . \PHP_EOL
175
                            . '#######################################################################################'
176
                            . \PHP_EOL;
177
                        echo $msg;
178
                    }
179
                    break;
180
                }
181
182
                // Can be deleted, but starting with this can be helpful
183
                if (\FS_DEBUG) {
184
                    $msg = '#######################################################################################'
185
                        . \PHP_EOL . '# ERROR: "' . $alias . '" not in "getUserMethods" for "' . $className . '"'
186
                        . \PHP_EOL . '#    Maybe you are missing to put it in to getUserMethods?' . \PHP_EOL
187
                        . '#######################################################################################'
188
                        . \PHP_EOL;
189
                    echo $msg;
190
                }
191
192
                $this->optionNotAvailable($cmd, $alias);
193
                $this->getAvailableOptions($cmd);
194
        }
195
        return $status;
196
    }
197
198
    /**
199
     * Returns a list of available methods for this command.
200
     *
201
     * @param string $cmd
202
     */
203
    public function getAvailableOptions(string $cmd)
204
    {
205
        echo 'Available options for "' . $cmd . '"' . \PHP_EOL . \PHP_EOL;
206
207
        $className = __NAMESPACE__ . '\Command\\' . $cmd;
208
        $options = \call_user_func([new $className(), 'getUserMethods']);
209
210
        foreach ((array) $options as $option => $methods) {
211
            echo '   ' . $option . \PHP_EOL;
212
        }
213
214
        echo \PHP_EOL . 'Use as: php vendor/bin/console ' . $cmd . ' [OPTIONS]' . \PHP_EOL . \PHP_EOL;
215
216
        die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
217
    }
218
219
    /**
220
     * Print help information to the user.
221
     */
222
    public function showAvailableCommands()
223
    {
224
        echo 'Available commands:' . \PHP_EOL;
225
226
        foreach ($this->getAvailableCommands() as $cmd) {
227
            $className = __NAMESPACE__ . '\Command\\' . $cmd;
228
            echo '   - ' . $cmd . ' : ' . \call_user_func([new $className(), 'getDescription']) . \PHP_EOL;
229
        }
230
        echo \PHP_EOL;
231
    }
232
233
    /**
234
     * Return a list of available commands
235
     *
236
     * @return array
237
     */
238
    public function getAvailableCommands(): array
239
    {
240
        $available = [];
241
        $allClasses = $this->getAllFcqns(__DIR__ . '/Command');
242
        foreach ($allClasses as $class) {
243
            if (0 === \strpos($class, __NAMESPACE__ . '\Command\\')) {
244
                $available[] = \str_replace(__NAMESPACE__ . '\Command\\', '', $class);
245
            }
246
        }
247
        return $available;
248
    }
249
250
    /**
251
     * Show that this option is not available.
252
     *
253
     * @param string $cmd
254
     * @param string $option
255
     */
256
    private function optionNotAvailable(string $cmd, string $option)
257
    {
258
        echo 'Option "' . $option . '" not available for "' . $cmd . '".' . \PHP_EOL . \PHP_EOL;
259
    }
260
261
    /**
262
     * Return all FCQNS.
263
     *
264
     * @param string $projectRoot
265
     *
266
     * @return array
267
     */
268
    private function getAllFcqns(string $projectRoot): array
269
    {
270
        $fileNames = $this->getFileNames($projectRoot);
271
        $fcqns = [];
272
        foreach ($fileNames as $fileName) {
273
            $fcqns[] = $this->getFullNameSpace($fileName) . '\\' . $this->getClassName($fileName);
0 ignored issues
show
Bug introduced by
Are you sure $this->getClassName($fileName) of type array can be used in concatenation? ( Ignorable by Annotation )

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

273
            $fcqns[] = $this->getFullNameSpace($fileName) . '\\' . /** @scrutinizer ignore-type */ $this->getClassName($fileName);
Loading history...
274
        }
275
276
        return $fcqns;
277
    }
278
279
    /**
280
     * Return files on path.
281
     *
282
     * @param string $path
283
     * @param string $pattern
284
     *
285
     * @return array
286
     */
287
    private function getFileNames(string $path, string $pattern = '*.php'): array
288
    {
289
        $finder = new Finder();
290
        $finder->files()->in($path)->name($pattern);
291
        $fileNames = [];
292
        foreach ($finder as $finderFile) {
293
            $fileNames[] = $finderFile->getRealPath();
294
        }
295
296
        return $fileNames;
297
    }
298
299
    /**
300
     * Return full namespace of file.
301
     *
302
     * @param string $fileName
303
     *
304
     * @return string
305
     */
306
    private function getFullNameSpace(string $fileName): string
307
    {
308
        $lines = file($fileName);
309
        if (\is_bool($lines)) {
310
            return '';
311
        }
312
        $array = preg_grep('/^namespace /', $lines);
313
        $namespaceLine = array_shift($array);
314
        $matches = [];
315
        preg_match('/^namespace (.*);$/', $namespaceLine, $matches);
316
        return (string) array_pop($matches);
317
    }
318
319
    /**
320
     * Return the class name.
321
     *
322
     * @param string $fileName
323
     *
324
     * @return array
325
     */
326
    private function getClassName(string $fileName): array
327
    {
328
        $dirsAndFile = explode(DIRECTORY_SEPARATOR, $fileName);
329
        $fileName = array_pop($dirsAndFile);
330
        $nameAndExt = explode('.', $fileName);
331
        return (array) array_shift($nameAndExt);
332
    }
333
}
334