Console_Getopt   F
last analyzed

Complexity

Total Complexity 63

Size/Duplication

Total Lines 332
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 125
dl 0
loc 332
rs 3.36
c 0
b 0
f 0
wmc 63

8 Methods

Rating   Name   Duplication   Size   Complexity  
A getopt2() 0 3 1
A _isShortOpt() 0 4 3
C _parseShortOption() 0 45 14
D doGetopt() 0 74 18
A readPHPArgv() 0 14 4
A _isLongOpt() 0 4 4
A getopt() 0 3 1
D _parseLongOption() 0 68 18

How to fix   Complexity   

Complex Class

Complex classes like Console_Getopt 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 Console_Getopt, and based on these observations, apply Extract Interface, too.

1
<?php
2
/* vim: set expandtab tabstop=4 shiftwidth=4: */
3
4
/**
5
 *
6
 * Copyright (c) 2001-2015, The PEAR developers
7
 *
8
 * This source file is subject to the BSD-2-Clause license,
9
 * that is bundled with this package in the file LICENSE, and is
10
 * available through the world-wide-web at the following url:
11
 * http://opensource.org/licenses/bsd-license.php.
12
 *
13
 * @category Console
14
 * @package  Console_Getopt
15
 * @author   Andrei Zmievski <[email protected]>
16
 * @license  http://opensource.org/licenses/bsd-license.php BSD-2-Clause
17
 * @version  CVS: $Id$
18
 * @link     http://pear.php.net/package/Console_Getopt
19
 */
20
21
require_once 'PEAR.php';
22
23
/**
24
 * Command-line options parsing class.
25
 *
26
 * @category Console
27
 * @package  Console_Getopt
28
 * @author   Andrei Zmievski <[email protected]>
29
 * @license  http://opensource.org/licenses/bsd-license.php BSD-2-Clause
30
 * @link     http://pear.php.net/package/Console_Getopt
31
 */
32
class Console_Getopt
33
{
34
    /**
35
     * Parses the command-line options.
36
     *
37
     * The first parameter to this function should be the list of command-line
38
     * arguments without the leading reference to the running program.
39
     *
40
     * The second parameter is a string of allowed short options. Each of the
41
     * option letters can be followed by a colon ':' to specify that the option
42
     * requires an argument, or a double colon '::' to specify that the option
43
     * takes an optional argument.
44
     *
45
     * The third argument is an optional array of allowed long options. The
46
     * leading '--' should not be included in the option name. Options that
47
     * require an argument should be followed by '=', and options that take an
48
     * option argument should be followed by '=='.
49
     *
50
     * The return value is an array of two elements: the list of parsed
51
     * options and the list of non-option command-line arguments. Each entry in
52
     * the list of parsed options is a pair of elements - the first one
53
     * specifies the option, and the second one specifies the option argument,
54
     * if there was one.
55
     *
56
     * Long and short options can be mixed.
57
     *
58
     * Most of the semantics of this function are based on GNU getopt_long().
59
     *
60
     * @param array  $args          an array of command-line arguments
61
     * @param string $short_options specifies the list of allowed short options
62
     * @param array  $long_options  specifies the list of allowed long options
63
     * @param bool   $skip_unknown  suppresses Console_Getopt: unrecognized option
64
     *
65
     * @return array two-element array containing the list of parsed options and
66
     * the non-option arguments
67
     */
68
    public static function getopt2($args, $short_options, $long_options = null, $skip_unknown = false)
69
    {
70
        return self::doGetopt(2, $args, $short_options, $long_options, $skip_unknown);
71
    }
72
73
    /**
74
     * This function expects $args to start with the script name (POSIX-style).
75
     * Preserved for backwards compatibility.
76
     *
77
     * @param array  $args          an array of command-line arguments
78
     * @param string $short_options specifies the list of allowed short options
79
     * @param array  $long_options  specifies the list of allowed long options
80
     *
81
     * @return array two-element array containing the list of parsed options and
82
     * the non-option arguments
83
     * @see getopt2()
84
     */
85
    public static function getopt($args, $short_options, $long_options = null, $skip_unknown = false)
86
    {
87
        return self::doGetopt(1, $args, $short_options, $long_options, $skip_unknown);
88
    }
89
90
    /**
91
     * The actual implementation of the argument parsing code.
92
     *
93
     * @param int    $version       Version to use
94
     * @param array  $args          an array of command-line arguments
95
     * @param string $short_options specifies the list of allowed short options
96
     * @param array  $long_options  specifies the list of allowed long options
97
     * @param bool   $skip_unknown  suppresses Console_Getopt: unrecognized option
98
     *
99
     * @return array
100
     */
101
    public static function doGetopt($version, $args, $short_options, $long_options = null, $skip_unknown = false)
102
    {
103
        // in case you pass directly readPHPArgv() as the first arg
104
        if (PEAR::isError($args)) {
105
            return $args;
106
        }
107
108
        if (empty($args)) {
109
            return [[], []];
110
        }
111
112
        $non_opts = $opts = [];
113
114
        $args = (array)$args;
115
116
        if ($long_options) {
117
            sort($long_options);
118
        }
119
120
        /*
121
         * Preserve backwards compatibility with callers that relied on
122
         * erroneous POSIX fix.
123
         */
124
        if ($version < 2) {
125
            if (isset($args[0][0]) && '-' != $args[0][0]) {
126
                array_shift($args);
127
            }
128
        }
129
130
        for ($i = 0, $iMax = count($args); $i < $iMax; ++$i) {
131
            $arg = $args[$i];
132
            /* The special element '--' means explicit end of
133
               options. Treat the rest of the arguments as non-options
134
               and end the loop. */
135
            if ('--' == $arg) {
136
                $non_opts = array_merge($non_opts, array_slice($args, $i + 1));
137
                break;
138
            }
139
140
            if ('-' != $arg[0] || (strlen($arg) > 1 && '-' == $arg[1] && !$long_options)) {
141
                $non_opts = array_merge($non_opts, array_slice($args, $i));
142
                break;
143
            } elseif (strlen($arg) > 1 && '-' == $arg[1]) {
144
                $error = self::_parseLongOption(
145
                    substr($arg, 2),
146
                    $long_options,
147
                    $opts,
148
                    $i,
149
                    $args,
150
                    $skip_unknown
151
                );
152
                if (PEAR::isError($error)) {
153
                    return $error;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $error returns the type PEAR_Error which is incompatible with the documented return type array.
Loading history...
154
                }
155
            } elseif ('-' == $arg) {
156
                // - is stdin
157
                $non_opts = array_merge($non_opts, array_slice($args, $i));
158
                break;
159
            } else {
160
                $error = self::_parseShortOption(
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $error is correct as self::_parseShortOption(..., $args, $skip_unknown) targeting Console_Getopt::_parseShortOption() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
161
                    substr($arg, 1),
162
                    $short_options,
0 ignored issues
show
Bug introduced by
$short_options of type string is incompatible with the type string[] expected by parameter $short_options of Console_Getopt::_parseShortOption(). ( Ignorable by Annotation )

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

162
                    /** @scrutinizer ignore-type */ $short_options,
Loading history...
163
                    $opts,
164
                    $i,
165
                    $args,
166
                    $skip_unknown
167
                );
168
                if (PEAR::isError($error)) {
169
                    return $error;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $error returns the type void which is incompatible with the documented return type array.
Loading history...
170
                }
171
            }
172
        }
173
174
        return [$opts, $non_opts];
175
    }
176
177
    /**
178
     * Parse short option
179
     *
180
     * @param string      $arg           Argument
181
     * @param string[]    $short_options Available short options
182
     * @param string[][] &$opts
183
     * @param int        &$argIdx
184
     * @param string[]    $args
185
     * @param bool        $skip_unknown  suppresses Console_Getopt: unrecognized option
186
     *
187
     * @return void
188
     */
189
    protected static function _parseShortOption($arg, $short_options, &$opts, &$argIdx, $args, $skip_unknown)
190
    {
191
        for ($i = 0, $iMax = strlen($arg); $i < $iMax; ++$i) {
192
            $opt     = $arg[$i];
193
            $opt_arg = null;
194
195
            /* Try to find the short option in the specifier string. */
196
            if (false === ($spec = strstr($short_options, $opt)) || ':' == $arg[$i]) {
0 ignored issues
show
Bug introduced by
$short_options of type string[] is incompatible with the type string expected by parameter $haystack of strstr(). ( Ignorable by Annotation )

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

196
            if (false === ($spec = strstr(/** @scrutinizer ignore-type */ $short_options, $opt)) || ':' == $arg[$i]) {
Loading history...
197
                if (true === $skip_unknown) {
198
                    break;
199
                }
200
201
                $msg = "Console_Getopt: unrecognized option -- $opt";
202
                return PEAR::raiseError($msg);
0 ignored issues
show
Bug introduced by
The method raiseError() does not exist on PEAR. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

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

202
                return PEAR::/** @scrutinizer ignore-call */ raiseError($msg);
Loading history...
203
            }
204
205
            if (strlen($spec) > 1 && ':' == $spec[1]) {
206
                if (strlen($spec) > 2 && ':' == $spec[2]) {
207
                    if ($i + 1 < strlen($arg)) {
208
                        /* Option takes an optional argument. Use the remainder of
209
                           the arg string if there is anything left. */
210
                        $opts[] = [$opt, substr($arg, $i + 1)];
211
                        break;
212
                    }
213
                } else {
214
                    /* Option requires an argument. Use the remainder of the arg
215
                       string if there is anything left. */
216
                    if ($i + 1 < strlen($arg)) {
217
                        $opts[] = [$opt, substr($arg, $i + 1)];
218
                        break;
219
                    } elseif (isset($args[++$argIdx])) {
220
                        $opt_arg = $args[$argIdx];/* Else use the next argument. */;
221
                        if (self::_isShortOpt($opt_arg)
222
                            || self::_isLongOpt($opt_arg)) {
223
                            $msg = "option requires an argument --$opt";
224
                            return PEAR::raiseError('Console_Getopt: ' . $msg);
225
                        }
226
                    } else {
227
                        $msg = "option requires an argument --$opt";
228
                        return PEAR::raiseError('Console_Getopt: ' . $msg);
229
                    }
230
                }
231
            }
232
233
            $opts[] = [$opt, $opt_arg];
234
        }
235
    }
236
237
    /**
238
     * Checks if an argument is a short option
239
     *
240
     * @param string $arg Argument to check
241
     *
242
     * @return bool
243
     */
244
    protected static function _isShortOpt($arg)
245
    {
246
        return 2 == strlen($arg) && '-' == $arg[0]
247
               && preg_match('/[a-zA-Z]/', $arg[1]);
248
    }
249
250
    /**
251
     * Checks if an argument is a long option
252
     *
253
     * @param string $arg Argument to check
254
     *
255
     * @return bool
256
     */
257
    protected static function _isLongOpt($arg)
258
    {
259
        return strlen($arg) > 2 && '-' == $arg[0] && '-' == $arg[1]
260
               && preg_match('/[a-zA-Z]+$/', substr($arg, 2));
261
    }
262
263
    /**
264
     * Parse long option
265
     *
266
     * @param string      $arg          Argument
267
     * @param string[]    $long_options Available long options
268
     * @param string[][] &$opts
269
     * @param int        &$argIdx
270
     * @param string[]    $args
271
     *
272
     * @return void|PEAR_Error
273
     */
274
    protected static function _parseLongOption($arg, $long_options, &$opts, &$argIdx, $args, $skip_unknown)
275
    {
276
        @list($opt, $opt_arg) = explode('=', $arg, 2);
277
278
        $opt_len = strlen($opt);
279
280
        for ($i = 0, $iMax = count($long_options); $i < $iMax; ++$i) {
281
            $long_opt  = $long_options[$i];
282
            $opt_start = substr($long_opt, 0, $opt_len);
0 ignored issues
show
Unused Code introduced by
The assignment to $opt_start is dead and can be removed.
Loading history...
283
284
            $long_opt_name = str_replace('=', '', $long_opt);
285
286
            /* Option doesn't match. Go on to the next one. */
287
            if ($long_opt_name != $opt) {
288
                continue;
289
            }
290
291
            $opt_rest = substr($long_opt, $opt_len);
292
293
            /* Check that the options uniquely matches one of the allowed
294
               options. */
295
            if ($i + 1 < count($long_options)) {
296
                $next_option_rest = substr($long_options[$i + 1], $opt_len);
297
            } else {
298
                $next_option_rest = '';
299
            }
300
301
            if ('' != $opt_rest && '=' != $opt[0]
302
                && $i + 1 < count($long_options)
303
                && $opt == substr($long_options[$i + 1], 0, $opt_len)
304
                && '' != $next_option_rest
305
                && '=' != $next_option_rest[0]) {
306
                $msg = "Console_Getopt: option --$opt is ambiguous";
307
                return PEAR::raiseError($msg);
308
            }
309
310
            if ('=' == substr($long_opt, -1)) {
311
                if ('==' != substr($long_opt, -2)) {
312
                    /* Long option requires an argument.
313
                       Take the next argument if one wasn't specified. */;
314
                    if (!strlen($opt_arg)) {
315
                        if (!isset($args[++$argIdx])) {
316
                            $msg = "Console_Getopt: option requires an argument --$opt";
317
                            return PEAR::raiseError($msg);
318
                        }
319
                        $opt_arg = $args[$argIdx];
320
                    }
321
322
                    if (self::_isShortOpt($opt_arg)
323
                        || self::_isLongOpt($opt_arg)) {
324
                        $msg = "Console_Getopt: option requires an argument --$opt";
325
                        return PEAR::raiseError($msg);
326
                    }
327
                }
328
            } elseif ($opt_arg) {
329
                $msg = "Console_Getopt: option --$opt doesn't allow an argument";
330
                return PEAR::raiseError($msg);
331
            }
332
333
            $opts[] = ['--' . $opt, $opt_arg];
334
            return;
335
        }
336
337
        if (true === $skip_unknown) {
338
            return;
339
        }
340
341
        return PEAR::raiseError("Console_Getopt: unrecognized option --$opt");
342
    }
343
344
    /**
345
     * Safely read the $argv PHP array across different PHP configurations.
346
     * Will take care on register_globals and register_argc_argv ini directives
347
     *
348
     * @return mixed the $argv PHP array or PEAR error if not registered
349
     */
350
    public static function readPHPArgv()
351
    {
352
        global $argv;
353
        if (!is_array($argv)) {
354
            if (!@is_array($_SERVER['argv'])) {
355
                if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) {
356
                    $msg = 'Could not read cmd args (register_argc_argv=Off?)';
357
                    return PEAR::raiseError('Console_Getopt: ' . $msg);
358
                }
359
                return $GLOBALS['HTTP_SERVER_VARS']['argv'];
360
            }
361
            return $_SERVER['argv'];
362
        }
363
        return $argv;
364
    }
365
}
366