Passed
Push — master ( 8014e2...1065ba )
by Francesc
01:48
created

ConsoleManager::getAvailableCommandsMsg()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 0
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
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
                    echo $this->getAvailableCommandsMsg();
48
                    break;
49
50
                case '-h':
51
                case '--help':
52
                    echo $this->getHelpMsg();
53
                    break;
54
55
                case 0 === \strpos($this->argv[1], '-'):
56
                case 0 === \strpos($this->argv[1], '--'):
57
                    $this->optionNotAvailable($this->argv[0], $this->argv[1]);
58
                    break;
59
                default:
60
                    $this->run();
61
            }
62
        } else {
63
            echo $this->getHelpMsg();
64
        }
65
    }
66
67
    /**
68
     * Start point to run the command.
69
     *
70
     * @param array $params
71
     *
72
     * @return int
73
     */
74
    public function run(...$params): int
75
    {
76
        $cmd = $this->argv[1];
77
        if (class_exists(__NAMESPACE__ . '\Command\\' . $cmd)) {
78
            return $this->execute();
79
        }
80
        echo \PHP_EOL . 'ERROR: Command "' . $cmd . '" not found.' . \PHP_EOL . \PHP_EOL;
81
        echo $this->getHelpMsg();
82
        return -1;
83
    }
84
85
    /**
86
     * Return description about this class.
87
     *
88
     * @return string
89
     */
90
    public function getDescription(): string
91
    {
92
        return 'The Console Manager';
93
    }
94
95
    /**
96
     * Print help information to the user.
97
     *
98
     * @return string
99
     */
100
    public function getHelpMsg(): string
101
    {
102
        return 'Use as: php vendor/bin/console [COMMAND] [OPTIONS]' . \PHP_EOL
103
            . 'Available options:' . \PHP_EOL
104
            . '   -h, --help        Show this help.' . \PHP_EOL
105
            . '   -l, --list        Show a list of available commands.' . \PHP_EOL
106
            . \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'        => 'getHelpMsg',
114
     *          '--help'    => 'getHelpMsg',
115
     *          '-l'        => 'getAvailableCommandsMsg',
116
     *          '--list'    => 'getAvailableCommandsMsg',
117
     *      ]
118
     *
119
     * @return array
120
     */
121
    public function getUserMethods(): array
122
    {
123
        // Adding extra method
124
        $methods = parent::getUserMethods();
125
        $methods['-l'] = 'getAvailableCommandsMsg';
126
        $methods['--list'] = 'getAvailableCommandsMsg';
127
        return $methods;
128
    }
129
130
    /**
131
     * Exec the command with the given options
132
     *
133
     * @return int
134
     */
135
    public function execute(): int
136
    {
137
        $status = -1;
138
        $params = $this->argv;
139
        \array_shift($params); // Extract console
140
        // command class
141
        $cmd = \array_shift($params); // Extract command
142
        // $params contains adicional parameters if are received
143
144
        switch ($cmd) {
145
            case '-h':
146
            case '--help':
147
                $status = $this->getAvailableOptions($cmd);
148
                break;
149
            default:
150
                $className = __NAMESPACE__ . '\Command\\' . $cmd;
151
                $methods = \call_user_func([new $className(), 'getUserMethods']);
152
                // Forced in ConsoleAbstract, but we don't want to show it to users
153
                $methods['run'] = 'run';
154
155
                // If not alias, we want to directly run
156
                $alias = $params[0] ?? 'run';
157
                // If not method match, show how it works
158
                $method = $methods[$alias[0]] ?? 'getHelpMsg';
159
160
                if (\array_key_exists($alias, $methods)) {
161
                    // Check if method is in class or parent class
162
                    if (\in_array($method, \get_class_methods($className), false) ||
163
                        \in_array($method, \get_class_methods(\get_parent_class($className)), false)
164
                    ) {
165
                        $status = (int) \call_user_func_array([new $className(), 'run'], $params);
166
                        break;
167
                    }
168
                    // Can be deleted, but starting with this can be helpful
169
                    if (\FS_DEBUG) {
170
                        $msg = '#######################################################################################'
171
                            . \PHP_EOL . '# ERROR: "' . $method . '" not defined in "' . $className . '"' . \PHP_EOL
172
                            . '#    Maybe you have a misspelling on the method name or is a missing declaration?'
173
                            . \PHP_EOL
174
                            . '#######################################################################################'
175
                            . \PHP_EOL;
176
                        echo $msg;
177
                    }
178
                    break;
179
                }
180
181
                // Can be deleted, but starting with this can be helpful
182
                if (\FS_DEBUG) {
183
                    $msg = '#######################################################################################'
184
                        . \PHP_EOL . '# ERROR: "' . $alias . '" not in "getUserMethods" for "' . $className . '"'
185
                        . \PHP_EOL . '#    Maybe you are missing to put it in to getUserMethods?' . \PHP_EOL
186
                        . '#######################################################################################'
187
                        . \PHP_EOL;
188
                    echo $msg;
189
                }
190
191
                $this->optionNotAvailable($cmd, $alias);
192
                $status = $this->getAvailableOptions($cmd);
193
        }
194
        return $status;
195
    }
196
197
    /**
198
     * Returns a list of available methods for this command.
199
     *
200
     * @param string $cmd
201
     *
202
     * @return int
203
     */
204
    public function getAvailableOptions(string $cmd): int
205
    {
206
        echo 'Available options for "' . $cmd . '"' . \PHP_EOL . \PHP_EOL;
207
        $className = __NAMESPACE__ . '\Command\\' . $cmd;
208
        $options = \call_user_func([new $className(), 'getUserMethods']);
209
        foreach ((array) $options as $option => $methods) {
210
            echo '   ' . $option . \PHP_EOL;
211
        }
212
        echo \PHP_EOL . 'Use as: php vendor/bin/console ' . $cmd . ' [OPTIONS]' . \PHP_EOL . \PHP_EOL;
213
        return -1;
214
    }
215
216
    /**
217
     * Print help information to the user.
218
     *
219
     * @return string
220
     */
221
    public function getAvailableCommandsMsg(): string
222
    {
223
        $msg = 'Available commands:' . \PHP_EOL;
224
        foreach ($this->getAvailableCommands() as $cmd) {
225
            $className = __NAMESPACE__ . '\Command\\' . $cmd;
226
            $msg .= '   - ' . $cmd . ' : ' . \call_user_func([new $className(), 'getDescription']) . \PHP_EOL;
227
        }
228
        return $msg . \PHP_EOL;
229
    }
230
231
    /**
232
     * Return a list of available commands
233
     *
234
     * @return array
235
     */
236
    public function getAvailableCommands(): array
237
    {
238
        $available = [];
239
        $allClasses = $this->getAllFcqns(__DIR__ . '/Command');
240
        foreach ($allClasses as $class) {
241
            if (0 === \strpos($class, __NAMESPACE__ . '\Command\\')) {
242
                $available[] = \str_replace(__NAMESPACE__ . '\Command\\', '', $class);
243
            }
244
        }
245
        return $available;
246
    }
247
248
    /**
249
     * Show that this option is not available.
250
     *
251
     * @param string $cmd
252
     * @param string $option
253
     */
254
    private function optionNotAvailable(string $cmd, string $option)
255
    {
256
        echo 'Option "' . $option . '" not available for "' . $cmd . '".' . \PHP_EOL . \PHP_EOL;
257
    }
258
259
    /**
260
     * Return all FCQNS.
261
     *
262
     * @param string $projectRoot
263
     *
264
     * @return array
265
     */
266
    private function getAllFcqns(string $projectRoot): array
267
    {
268
        $fileNames = $this->getFileNames($projectRoot);
269
        $fcqns = [];
270
        foreach ($fileNames as $fileName) {
271
            $fcqns[] = $this->getFullNameSpace($fileName) . '\\' . $this->getClassName($fileName);
272
        }
273
        return $fcqns;
274
    }
275
276
    /**
277
     * Return files on path.
278
     *
279
     * @param string $path
280
     * @param string $pattern
281
     *
282
     * @return array
283
     */
284
    private function getFileNames(string $path, string $pattern = '*.php'): array
285
    {
286
        $finder = new Finder();
287
        $finder->files()->in($path)->name($pattern);
288
        $fileNames = [];
289
        foreach ($finder as $finderFile) {
290
            $fileNames[] = $finderFile->getRealPath();
291
        }
292
        return $fileNames;
293
    }
294
295
    /**
296
     * Return full namespace of file.
297
     *
298
     * @param string $fileName
299
     *
300
     * @return string
301
     */
302
    private function getFullNameSpace(string $fileName): string
303
    {
304
        $lines = file($fileName);
305
        if (\is_bool($lines)) {
306
            return '';
307
        }
308
        $array = preg_grep('/^namespace /', $lines);
309
        $namespaceLine = array_shift($array);
310
        $matches = [];
311
        preg_match('/^namespace (.*);$/', $namespaceLine, $matches);
312
        return (string) array_pop($matches);
313
    }
314
315
    /**
316
     * Return the class name.
317
     *
318
     * @param string $fileName
319
     *
320
     * @return string
321
     */
322
    private function getClassName(string $fileName): string
323
    {
324
        $dirsAndFile = explode(DIRECTORY_SEPARATOR, $fileName);
325
        $fileName = array_pop($dirsAndFile);
326
        $nameAndExt = explode('.', $fileName);
327
        return (string) array_shift($nameAndExt);
328
    }
329
}
330