Completed
Push — master ( 91d599...412a7f )
by Lincoln
21s
created

Cli::hasOptions()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 13
ccs 5
cts 5
cp 1
rs 9.2
cc 4
eloc 9
nc 4
nop 1
crap 4
1
<?php
2
3
namespace Garden\Cli;
4
5
/**
6
 * A general purpose command line parser.
7
 *
8
 * @author Todd Burry <[email protected]>
9
 * @license MIT
10
 * @copyright 2010-2014 Vanilla Forums Inc.
11
 */
12
class Cli {
13
    /// Constants ///
14
15
    const META = '__meta';
16
    const ARGS = '__args';
17
18
    /// Properties ///
19
    /**
20
     * @var array All of the schemas, indexed by command pattern.
21
     */
22
    protected $commandSchemas;
23
24
    /**
25
     * @var array A pointer to the current schema.
26
     */
27
    protected $currentSchema;
28
29
    /**
30
     * @var bool Whether or not to format output with console codes.
31
     */
32
    protected $formatOutput;
33
34
    protected static $types = [
35
//        '=' => 'base64',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
36
        'i' => 'integer',
37
        's' => 'string',
38
//        'f' => 'float',
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
39
        'b' => 'boolean',
40
//        'ts' => 'timestamp',
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
41
//        'dt' => 'datetime'
42
    ];
43
44
45
    /// Methods ///
46
47
    /**
48
     * Creates a {@link Cli} instance representing a command line parser for a given schema.
49
     */
50 34
    public function __construct() {
51 34
        $this->commandSchemas = ['*' => [Cli::META => []]];
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
52
53
        // Select the current schema.
54 34
        $this->currentSchema =& $this->commandSchemas['*'];
55
56
        $this->formatOutput = static::guessFormatOutput();
57 34
    }
58
59
    /**
60
     * Backwards compatibility for the **format** property.
61
     *
62
     * @param string $name Must be **format**.
63
     * @return bool|null Returns {@link getFormatOutput()} or null if {@link $name} isn't **format**.
64
     */
65 1
    public function __get($name) {
66 1
        if ($name === 'format') {
67
            trigger_error("Cli->format is deprecated. Use Cli->getFormatOutput() instead.", E_USER_DEPRECATED);
68
            return $this->getFormatOutput();
69
        }
70
        return null;
71
    }
72
73
    /**
74
     * Backwards compatibility for the **format** property.
75
     *
76
     * @param string $name Must be **format**.
77
     * @param bool $value One of **true** or **false**.
78
     */
79 1
    public function __set($name, $value) {
80 1
        if ($name === 'format') {
81
            trigger_error("Cli->format is deprecated. Use Cli->setFormatOutput() instead.", E_USER_DEPRECATED);
82
            $this->setFormatOutput($value);
83
        }
84 1
    }
85
86
    /**
87
     * Get whether or not output should be formatted.
88
     *
89
     * @return boolean Returns **true** if output should be formatted or **false** otherwise.
90
     */
91
    public function getFormatOutput() {
92
        return $this->formatOutput;
93
    }
94
95
    /**
96
     * Set whether or not output should be formatted.
97
     *
98
     * @param boolean $formatOutput Whether or not to format output.
99
     * @return Cli Returns `$this` for fluent calls.
100
     */
101 2
    public function setFormatOutput($formatOutput) {
102 2
        $this->formatOutput = $formatOutput;
103 2
        return $this;
104
    }
105
106
    /**
107
     * Add an argument to an {@link Args} object, checking for a correct name.
108
     *
109
     * @param array $schema The schema for the args.
110
     * @param Args $args The args object to add the argument to.
111
     * @param $arg The value of the argument.
112
     */
113 1
    private function addArg(array $schema, Args $args, $arg) {
114
        $argsCount = count($args->getArgs());
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
115 1
        $schemaArgs = isset($schema[self::META][self::ARGS]) ? array_keys($schema[self::META][self::ARGS]) : [];
116 1
        $name = isset($schemaArgs[$argsCount]) ? $schemaArgs[$argsCount] : $argsCount;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 7 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
117
118
        $args->addArg($arg, $name);
119
    }
120
121
    /**
122
     * Construct and return a new {@link Cli} object.
123
     *
124
     * This method is mainly here so that an entire cli schema can be created and defined with fluent method calls.
125
     *
126
     * @return Cli Returns a new Cli object.
127
     */
128
    public static function create() {
129
        return new Cli();
130
    }
131
132
    /**
133
     * Breaks a cell into several lines according to a given width.
134
     *
135
     * @param string $text The text of the cell.
136
     * @param int $width The width of the cell.
137
     * @param bool $addSpaces Whether or not to right-pad the cell with spaces.
138
     * @return array Returns an array of strings representing the lines in the cell.
139
     */
140
    public static function breakLines($text, $width, $addSpaces = true) {
141
        $rawLines = explode("\n", $text);
142
        $lines = [];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
143
144
        foreach ($rawLines as $line) {
145
            // Check to see if the line needs to be broken.
146
            $sublines = static::breakString($line, $width, $addSpaces);
147
            $lines = array_merge($lines, $sublines);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
148
        }
149
150
        return $lines;
151
    }
152
153
    /**
154
     * Breaks a line of text according to a given width.
155
     *
156
     * @param string $line The text of the line.
157
     * @param int $width The width of the cell.
158
     * @param bool $addSpaces Whether or not to right pad the lines with spaces.
159
     * @return array Returns an array of lines, broken on word boundaries.
160
     */
161
    protected static function breakString($line, $width, $addSpaces = true) {
162
        $words = explode(' ', $line);
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
163
        $result = [];
164
165
        $line = '';
166
        foreach ($words as $word) {
167
            $candidate = trim($line.' '.$word);
168
169
            // Check for a new line.
170
            if (strlen($candidate) > $width) {
171
                if ($line === '') {
172
                    // The word is longer than a line.
173
                    if ($addSpaces) {
174
                        $result[] = substr($candidate, 0, $width);
175
                    } else {
176
                        $result[] = $candidate;
177
                    }
178
                } else {
179
                    if ($addSpaces) {
180
                        $line .= str_repeat(' ', $width - strlen($line));
181
                    }
182
183
                    // Start a new line.
184
                    $result[] = $line;
185
                    $line = $word;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
186
                }
187
            } else {
188
                $line = $candidate;
189
            }
190
        }
191
192
        // Add the remaining line.
193
        if ($line) {
194
            if ($addSpaces) {
195
                $line .= str_repeat(' ', $width - strlen($line));
196
            }
197
198
            // Start a new line.
199
            $result[] = $line;
200
        }
201
202
        return $result;
203
    }
204
205
    /**
206
     * Sets the description for the current command.
207
     *
208
     * @param string $str The description for the current schema or null to get the current description.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $str not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
209
     * @return Cli Returns this class for fluent calls.
210
     */
211
    public function description($str = null) {
212
        return $this->meta('description', $str);
213
    }
214
215
    /**
216
     * Determines whether or not the schema has a command.
217
     *
218
     * @param string $name Check for the specific command name.
219
     * @return bool Returns true if the schema has a command.
220
     */
221
    public function hasCommand($name = '') {
222
        if ($name) {
223
            return array_key_exists($name, $this->commandSchemas);
224
        } else {
225
            foreach ($this->commandSchemas as $pattern => $opts) {
226
                if (strpos($pattern, '*') === false) {
227
                    return true;
228
                }
229
            }
230
            return false;
231
        }
232
    }
233
234
    /**
235
     * Determines whether a command has options.
236
     *
237
     * @param string $command The name of the command or an empty string for any command.
238
     * @return bool Returns true if the command has options. False otherwise.
239
     */
240 6
    public function hasOptions($command = '') {
241 6
        if ($command) {
242
            $def = $this->getSchema($command);
243
            return $this->hasOptionsDef($def);
244
        } else {
245 4
            foreach ($this->commandSchemas as $pattern => $def) {
246
                if ($this->hasOptionsDef($def)) {
247 4
                    return true;
248
                }
249 3
            }
250
        }
251
        return false;
252
    }
253
254
    /**
255
     * Determines whether or not a command definition has options.
256
     *
257
     * @param array $commandDef The command definition as returned from {@link Cli::getSchema()}.
258
     * @return bool Returns true if the command def has options or false otherwise.
259
     */
260
    protected function hasOptionsDef($commandDef) {
261
        return count($commandDef) > 1 || (count($commandDef) > 0 && !isset($commandDef[Cli::META]));
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
262
    }
263
264
    /**
265
     * Determines whether or not a command has args.
266
     *
267
     * @param string $command The command name to check.
268
     * @return int Returns one of the following.
269
     * - 0: The command has no args.
270
     * - 1: The command has optional args.
271
     * - 2: The command has required args.
272
     */
273 6
    public function hasArgs($command = '') {
274 6
        $args = null;
275
276 6
        if ($command) {
277
            // Check to see if the specific command has args.
278
            $def = $this->getSchema($command);
279 2
            if (isset($def[Cli::META][Cli::ARGS])) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
280 2
                $args = $def[Cli::META][Cli::ARGS];
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
281
            }
282
        } else {
283 4
            foreach ($this->commandSchemas as $pattern => $def) {
284 2
                if (isset($def[Cli::META][Cli::ARGS])) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
285 2
                    $args = $def[Cli::META][Cli::ARGS];
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
286
                }
287
            }
288 4
            if (!empty($args)) {
289 2
                return 1;
290
            }
291 2
        }
292
293 4
        if (!$args || empty($args)) {
294 2
            return 0;
295
        }
296
297
        foreach ($args as $arg) {
298
            if (!Cli::val('required', $arg)) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
299 1
                return 1;
300
            }
301 1
        }
302 1
        return 2;
303
    }
304
305
    /**
306
     * Finds our whether a pattern is a command.
307
     *
308
     * @param string $pattern The pattern being evaluated.
309
     * @return bool Returns `true` if `$pattern` is a command, `false` otherwise.
310
     */
311
    public static function isCommand($pattern) {
312
        return strpos($pattern, '*') === false;
313
    }
314
315
    /**
316
     * Parses and validates a set of command line arguments the schema.
317
     *
318
     * @param array $argv The command line arguments a form compatible with the global `$argv` variable.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $argv not be array|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
319
     *
320
     * Note that the `$argv` array must have at least one element and it must represent the path to the command that
321
     * invoked the command. This is used to write usage information.
322
     * @param bool $exit Whether to exit the application when there is an error or when writing help.
323
     * @return Args|null Returns an {@see Args} instance when a command should be executed
324
     * or `null` when one should not be executed.
325
     * @throws \Exception Throws an exception when {@link $exit} is false and the help or errors need to be displayed.
326
     */
327 32
    public function parse($argv = null, $exit = true) {
328 32
        $formatOutputBak = $this->formatOutput;
329
        // Only format commands if we are exiting.
330 32
        if (!$exit) {
331 31
            $this->formatOutput = false;
332
        }
333 32
        if (!$exit) {
334
            ob_start();
335
        }
336
337
        $args = $this->parseRaw($argv);
338
339
        $hasCommand = $this->hasCommand();
340
341
342 28
        if ($hasCommand && !$args->getCommand()) {
343
            // If no command is given then write a list of commands.
344
            $this->writeUsage($args);
345
            $this->writeCommands();
346 2
            $result = null;
347
        } elseif ($args->getOpt('help') || $args->getOpt('?')) {
348
            // Write the help.
349
            $this->writeUsage($args);
350
            $this->writeHelp($args->getCommand());
351 4
            $result = null;
352
        } else {
353
            // Validate the arguments against the schema.
354
            $validArgs = $this->validate($args);
355 26
            $result = $validArgs;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
356 6
        }
357 32
        if (!$exit) {
358 31
            $this->formatOutput = $formatOutputBak;
359
            $output = ob_get_clean();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 13 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
360 31
            if ($result === null) {
361
                throw new \Exception(trim($output));
362
            }
363 1
        } elseif ($result === null) {
364
            exit();
365 17
        }
366 18
        return $result;
367 1
    }
368
369
    /**
370
     * Parse an array of arguments.
371
     *
372
     * If the first item in the array is in the form of a command (no preceding - or --),
373
     * 'command' is filled with its value.
374
     *
375
     * @param array $argv An array of arguments passed in a form compatible with the global `$argv` variable.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $argv not be array|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
376
     * @return Args Returns the raw parsed arguments.
377
     * @throws \Exception Throws an exception when {@see $argv} isn't an array.
378
     */
379 32
    protected function parseRaw($argv = null) {
380 32
        if ($argv === null) {
381
            $argv = $GLOBALS['argv'];
382
        }
383
384
        if (!is_array($argv)) {
385
            throw new \Exception(__METHOD__ . " expects an array", 400);
386
        }
387
388
        $path = array_shift($argv);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 7 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
389
        $hasCommand = $this->hasCommand();
390
391
        $parsed = new Args();
392
        $parsed->setMeta('path', $path);
393
        $parsed->setMeta('filename', basename($path));
394
395
        if (count($argv)) {
396
            // Get possible command.
397
            if (substr($argv[0], 0, 1) != '-') {
398
                $arg0 = array_shift($argv);
399 4
                if ($hasCommand) {
400
                    $parsed->setCommand($arg0);
401
                } else {
402
                    $schema = $this->getSchema($parsed->getCommand());
403
                    $this->addArg($schema, $parsed, $arg0);
404 2
                }
405
            }
406
            // Get the data types for all of the commands.
407 32
            if (!isset($schema)) {
408
                $schema = $this->getSchema($parsed->getCommand());
409
            }
410 32
            $types = [];
411
            foreach ($schema as $sName => $sRow) {
412 2
                if ($sName === Cli::META) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
413 32
                    continue;
414
                }
415
416
                $type = Cli::val('type', $sRow, 'string');
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 10 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
417 2
                $types[$sName] = $type;
418 2
                if (isset($sRow['short'])) {
419 3
                    $types[$sRow['short']] = $type;
420
                }
421
            }
422
423
            // Parse opts.
424
            for ($i = 0; $i < count($argv); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
425 17
                $str = $argv[$i];
426
427 17
                if ($str === '--') {
428
                    // --
429
                    $i++;
430
                    break;
431
                } elseif (strlen($str) > 2 && substr($str, 0, 2) == '--') {
432
                    // --foo
433
                    $str = substr($str, 2);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
434
                    $parts = explode('=', $str);
435 14
                    $key = $parts[0];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
436
437
                    // Does not have an =, so choose the next arg as its value,
438
                    // unless it is defined as 'boolean' in which case there is no
439
                    // value to seek in next arg
440
                    if (count($parts) == 1 && isset($argv[$i + 1]) && preg_match('/^--?.+/', $argv[$i + 1]) == 0) {
441 5
                        $v = $argv[$i + 1];
442
                        if (Cli::val($key, $types) == 'boolean') {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
443
                            if (in_array($v, ['0', '1', 'true', 'false', 'on', 'off', 'yes', 'no'])) {
444
                                // The next arg looks like a boolean to me.
445 1
                                $i++;
446
                            } else {
447
                                // Next arg is not a boolean: set the flag on, and use next arg in its own iteration
448
                                $v = true;
449 1
                            }
450
                        } else {
451 4
                            $i++;
452 1
                        }
453
                    } elseif (count($parts) == 2) {// Has a =, so pick the second piece
454 6
                        $v = $parts[1];
455
                    } else {
456 8
                        $v = true;
457 10
                    }
458
                    $parsed->setOpt($key, $v);
459
                } elseif (strlen($str) == 2 && $str[0] == '-') {
460
                    // -a
461
462 7
                    $key = $str[1];
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
463
                    $type = Cli::val($key, $types, 'boolean');
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
464 7
                    $v = null;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
465
466 7
                    if (isset($argv[$i + 1])) {
467
                        // Try and be smart about the next arg.
468 4
                        $nextArg = $argv[$i + 1];
469
470 4
                        if ($type === 'boolean') {
471
                            if ($this->isStrictBoolean($nextArg)) {
472
                                // The next arg looks like a boolean to me.
473 1
                                $v = $nextArg;
474 1
                                $i++;
475
                            } else {
476 2
                                $v = true;
477 1
                            }
478
                        } elseif (!preg_match('/^--?.+/', $argv[$i + 1])) {
479
                            // The next arg is not an opt.
480 3
                            $v = $nextArg;
481 3
                            $i++;
482
                        } else {
483
                            // The next arg is another opt.
484
                            $v = null;
485 4
                        }
486
                    }
487
488 7
                    if ($v === null) {
489
                        $v = Cli::val($type, ['boolean' => true, 'integer' => 1, 'string' => '']);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
490
                    }
491
492
                    $parsed->setOpt($key, $v);
493
                } elseif (strlen($str) > 1 && $str[0] == '-') {
494
                    // -abcdef
495
                    for ($j = 1; $j < strlen($str); $j++) {
496 10
                        $opt = $str[$j];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 7 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
497
                        $remaining = substr($str, $j + 1);
498
                        $type = Cli::val($opt, $types, 'boolean');
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
499
500
                        // Check for an explicit equals sign.
501
                        if (substr($remaining, 0, 1) === '=') {
502
                            $remaining = substr($remaining, 1);
503 2
                            if ($type === 'boolean') {
504
                                // Bypass the boolean flag checking below.
505
                                $parsed->setOpt($opt, $remaining);
506
                                break;
507
                            }
508
                        }
509
510 10
                        if ($type === 'boolean') {
511
                            if (preg_match('`^([01])`', $remaining, $matches)) {
512
                                // Treat the 0 or 1 as a true or false.
513
                                $parsed->setOpt($opt, $matches[1]);
514
                                $j += strlen($matches[1]);
515
                            } else {
516
                                // Treat the option as a flag.
517
                                $parsed->setOpt($opt, true);
518 1
                            }
519 12
                        } elseif ($type === 'string') {
520
                            // Treat the option as a set with no = sign.
521
                            $parsed->setOpt($opt, $remaining);
522 12
                            break;
523 1
                        } elseif ($type === 'integer') {
524
                            if (preg_match('`^(\d+)`', $remaining, $matches)) {
525
                                // Treat the option as a set with no = sign.
526
                                $parsed->setOpt($opt, $matches[1]);
527
                                $j += strlen($matches[1]);
528
                            } else {
529
                                // Treat the option as either multiple flags.
530
                                $optVal = $parsed->getOpt($opt, 0);
531
                                $parsed->setOpt($opt, $optVal + 1);
532 2
                            }
533
                        } else {
534
                            // This should not happen unless we've put a bug in our code.
535
                            throw new \Exception("Invalid type $type for $opt.", 500);
536 1
                        }
537 9
                    }
538
                } else {
539
                    // End of opts
540 1
                    break;
541 16
                }
542 1
            }
543
544
            // Grab the remaining args.
545
            for (; $i < count($argv); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
546
                $this->addArg($schema, $parsed, $argv[$i]);
547 1
            }
548
        }
549
550 32
        return $parsed;
551
    }
552
553
    /**
554
     * Validates arguments against the schema.
555
     *
556
     * @param Args $args The arguments that were returned from {@link Cli::parseRaw()}.
557
     * @return Args|null
558
     */
559 26
    public function validate(Args $args) {
560 26
        $isValid = true;
561
        $command = $args->getCommand();
562
        $valid = new Args($command);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
563
        $schema = $this->getSchema($command);
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
564
        ksort($schema);
565
566
//        $meta = $schema[Cli::META];
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
567 26
        unset($schema[Cli::META]);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
568
        $opts = $args->getOpts();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
569 26
        $missing = [];
570
571
        // Check to see if the command is correct.
572 26
        if ($command && !$this->hasCommand($command) && $this->hasCommand()) {
573
            echo $this->red("Invalid command: $command.".PHP_EOL);
574
            $isValid = false;
575
        }
576
577
        // Add the args.
578
        $valid->setArgs($args->getArgs());
579
580
        foreach ($schema as $key => $definition) {
581
            // No Parameter (default)
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
582
            $type = Cli::val('type', $definition, 'string');
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
583
584
            if (isset($opts[$key])) {
585
                // Check for --key.
586 8
                $value = $opts[$key];
587
                if ($this->validateType($value, $type, $key, $definition)) {
588
                    $valid->setOpt($key, $value);
589
                } else {
590 3
                    $isValid = false;
591 5
                }
592 8
                unset($opts[$key]);
593
            } elseif (isset($definition['short']) && isset($opts[$definition['short']])) {
594
                // Check for -s.
595 10
                $value = $opts[$definition['short']];
596
                if ($this->validateType($value, $type, $key, $definition)) {
597
                    $valid->setOpt($key, $value);
598
                } else {
599 2
                    $isValid = false;
600 10
                }
601 10
                unset($opts[$definition['short']]);
602 3
            } elseif (isset($opts['no-'.$key])) {
603
                // Check for --no-key.
604 3
                $value = $opts['no-'.$key];
605
606 3
                if ($type !== 'boolean') {
607
                    echo $this->red("Cannot apply the --no- prefix on the non boolean --$key.".PHP_EOL);
608 1
                    $isValid = false;
609
                } elseif ($this->validateType($value, $type, $key, $definition)) {
610
                    $valid->setOpt($key, !$value);
611
                } else {
612 1
                    $isValid = false;
613 2
                }
614 3
                unset($opts['no-'.$key]);
615 3
            } elseif ($definition['required']) {
616
                // The key was not supplied. Is it required?
617 1
                $missing[$key] = true;
618
                $valid->setOpt($key, false);
619 7
            }
620 1
        }
621
622
        if (count($missing)) {
623 2
            $isValid = false;
624
            foreach ($missing as $key => $v) {
625
                echo $this->red("Missing required option: $key".PHP_EOL);
626
            }
627
        }
628
629
        if (count($opts)) {
630
            $isValid = false;
631
            foreach ($opts as $key => $v) {
632
                echo $this->red("Invalid option: $key".PHP_EOL);
633
            }
634
        }
635
636 26
        if ($isValid) {
637 18
            return $valid;
638
        } else {
639 8
            echo PHP_EOL;
640 8
            return null;
641
        }
642 26
    }
643
644
    /**
645
     * Gets the full cli schema.
646
     *
647
     * @param string $command The name of the command. This can be left blank if there is no command.
648
     * @return array Returns the schema that matches the command.
649
     */
650 1
    public function getSchema($command = '') {
651 1
        $result = [];
652 1
        foreach ($this->commandSchemas as $pattern => $opts) {
653
            if (fnmatch($pattern, $command)) {
654
                $result = array_replace_recursive($result, $opts);
655
            }
656
        }
657 1
        return $result;
658
    }
659
660
    /**
661
     * Gets/sets the value for a current meta item.
662
     *
663
     * @param string $name The name of the meta key.
664
     * @param mixed $value Set a new value for the meta key.
665
     * @return Cli|mixed Returns the current value of the meta item or `$this` for fluent setting.
666
     */
667 6
    public function meta($name, $value = null) {
668 6
        if ($value !== null) {
669 6
            $this->currentSchema[Cli::META][$name] = $value;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
670 6
            return $this;
671
        }
672
        if (!isset($this->currentSchema[Cli::META][$name])) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
673
            return null;
674
        }
675
        return $this->currentSchema[Cli::META][$name];
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
676
    }
677
678
    /**
679
     * Adds an option (opt) to the current schema.
680
     *
681
     * @param string $name The long name(s) of the parameter.
682
     * You can use either just one name or a string in the form 'long:short' to specify the long and short name.
683
     * @param string $description A human-readable description for the column.
684
     * @param bool $required Whether or not the opt is required.
685
     * @param string $type The type of parameter.
686
     * This must be one of string, bool, integer.
687
     * @return Cli Returns this object for fluent calls.
688
     * @throws \Exception Throws an exception when the type is invalid.
689
     */
690 26
    public function opt($name, $description, $required = false, $type = 'string') {
691
        switch ($type) {
692 2
            case 'str':
693 2
            case 'string':
694 26
                $type = 'string';
695 26
                break;
696 3
            case 'bool':
697 3
            case 'boolean':
698
                $type = 'boolean';
699
                break;
700 22
            case 'int':
701 22
            case 'integer':
702 22
                $type = 'integer';
703 22
                break;
704
            default:
705
                throw new \Exception("Invalid type: $type. Must be one of string, boolean, or integer.", 422);
706
        }
707
708
        // Break the name up into its long and short form.
709
        $parts = explode(':', $name, 2);
710 2
        $long = $parts[0];
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
711
        $short = static::val(1, $parts, '');
712
713 2
        $this->currentSchema[$long] = ['description' => $description, 'required' => $required, 'type' => $type, 'short' => $short];
714 2
        return $this;
715 5
    }
716
717
    /**
718
     * Define an arg on the current command.
719
     *
720
     * @param string $name The name of the arg.
721
     * @param string $description The arg description.
722
     * @param bool $required Whether or not the arg is required.
723
     * @return Cli Returns $this for fluent calls.
724
     */
725 4
    public function arg($name, $description, $required = false) {
726 4
        $this->currentSchema[Cli::META][Cli::ARGS][$name] =
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
727 4
            ['description' => $description, 'required' => $required];
728 4
        return $this;
729
    }
730
731
    /**
732
     * Selects the current command schema name.
733
     *
734
     * @param string $pattern The command pattern.
735
     * @return Cli Returns $this for fluent calls.
736
     */
737 2
    public function command($pattern) {
738 2
        if (!isset($this->commandSchemas[$pattern])) {
739 2
            $this->commandSchemas[$pattern] = [Cli::META => []];
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
740
        }
741 2
        $this->currentSchema =& $this->commandSchemas[$pattern];
742
743 2
        return $this;
744
    }
745
746
747
    /**
748
     * Determine weather or not a value can be represented as a boolean.
749
     *
750
     * This method is sort of like {@link Cli::validateType()} but requires a more strict check of a boolean value.
751
     *
752
     * @param mixed $value The value to test.
753
     * @return bool
754
     */
755 2
    protected function isStrictBoolean($value, &$boolValue = null) {
756 1
        if ($value === true || $value === false) {
757
            $boolValue = $value;
758
            return true;
759
        } elseif (in_array($value, ['0', 'false', 'off', 'no'])) {
760 1
            $boolValue = false;
761 1
            return true;
762
        } elseif (in_array($value, ['1', 'true', 'on', 'yes'])) {
763
            $boolValue = true;
764
            return true;
765
        } else {
766 2
            $boolValue = null;
767 2
            return false;
768
        }
769 1
    }
770
771
    /**
772
     * Set the schema for a command.
773
     *
774
     * The schema array uses a short syntax so that commands can be specified as quickly as possible.
775
     * This schema is the exact same as those provided to {@link Schema::create()}.
776
     * The basic format of the array is the following:
777
     *
778
     * ```
779
     * [
780
     *     type:name[:shortCode][?],
781
     *     type:name[:shortCode][?],
782
     *     ...
783
     * ]
784
     * ```
785
     *
786
     * @param array $schema The schema array.
787
     */
788
    public function schema(array $schema) {
789
        $parsed = static::parseSchema($schema);
790
791
        $this->currentSchema = array_replace($this->currentSchema, $parsed);
792
    }
793
794
    /**
795
     * Bold some text.
796
     *
797
     * @param string $text The text to format.
798
     * @return string Returns the text surrounded by formatting commands.
799
     */
800
    public function bold($text) {
801
        return $this->formatString($text, ["\033[1m", "\033[0m"]);
802
    }
803
804
    /**
805
     * Bold some text.
806
     *
807
     * @param string $text The text to format.
808
     * @return string Returns the text surrounded by formatting commands.
809
     */
810
    public static function boldText($text) {
811
        return "\033[1m{$text}\033[0m";
812
    }
813
814
    /**
815
     * Make some text red.
816
     *
817
     * @param string $text The text to format.
818
     * @return string Returns  text surrounded by formatting commands.
819
     */
820
    public function red($text) {
821
        return $this->formatString($text, ["\033[1;31m", "\033[0m"]);
822
    }
823
824
    /**
825
     * Make some text red.
826
     *
827
     * @param string $text The text to format.
828
     * @return string Returns  text surrounded by formatting commands.
829
     */
830 1
    public static function redText($text) {
831 1
        return "\033[1;31m{$text}\033[0m";
832
    }
833
834
    /**
835
     * Make some text green.
836
     *
837
     * @param string $text The text to format.
838
     * @return string Returns  text surrounded by formatting commands.
839
     */
840
    public function green($text) {
841
        return $this->formatString($text, ["\033[1;32m", "\033[0m"]);
842
    }
843
844
    /**
845
     * Make some text green.
846
     *
847
     * @param string $text The text to format.
848
     * @return string Returns  text surrounded by formatting commands.
849
     */
850 1
    public static function greenText($text) {
851 1
        return "\033[1;32m{$text}\033[0m";
852
    }
853
854
    /**
855
     * Make some text blue.
856
     *
857
     * @param string $text The text to format.
858
     * @return string Returns  text surrounded by formatting commands.
859
     */
860
    public function blue($text) {
861
        return $this->formatString($text, ["\033[1;34m", "\033[0m"]);
862
    }
863
864
    /**
865
     * Make some text blue.
866
     *
867
     * @param string $text The text to format.
868
     * @return string Returns  text surrounded by formatting commands.
869
     */
870
    public static function blueText($text) {
871
        return "\033[1;34m{$text}\033[0m";
872
    }
873
874
    /**
875
     * Make some text purple.
876
     *
877
     * @param string $text The text to format.
878
     * @return string Returns  text surrounded by formatting commands.
879
     */
880
    public function purple($text) {
881
        return $this->formatString($text, ["\033[0;35m", "\033[0m"]);
882
    }
883
884
    /**
885
     * Make some text purple.
886
     *
887
     * @param string $text The text to format.
888
     * @return string Returns  text surrounded by formatting commands.
889
     */
890
    public static function purpleText($text) {
891
        return "\033[0;35m{$text}\033[0m";
892
    }
893
894
    /**
895
     * Format some text for the console.
896
     *
897
     * @param string $text The text to format.
898
     * @param string[] $wrap The format to wrap in the form ['before', 'after'].
899
     * @return string Returns the string formatted according to {@link Cli::$format}.
900
     */
901 8
    protected function formatString($text, array $wrap) {
902 8
        if ($this->formatOutput) {
903 1
            return "{$wrap[0]}$text{$wrap[1]}";
904
        } else {
905 7
            return $text;
906
        }
907 8
    }
908
909
    /**
910
     * Guess whether or not to format the output with colors.
911
     *
912
     * If the current environment is being redirected to a file then output should not be formatted. Also, Windows
913
     * machines do not support terminal colors so formatting should be suppressed on them too.
914
     *
915
     * @return bool Returns **true** if the output can be formatter or **false** otherwise.
916
     */
917
    public static function guessFormatOutput() {
918
        if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
919
            return false;
920
        } elseif (function_exists('posix_isatty')) {
921
            return posix_isatty(STDOUT);
922
        } else {
923
            return true;
924
        }
925
    }
926
927
    /**
928
     * Sleep for a number of seconds, echoing out a dot on each second.
929
     *
930
     * @param int $seconds The number of seconds to sleep.
931
     */
932
    public static function sleep($seconds) {
933
        for ($i = 0; $i < $seconds; $i++) {
934
            sleep(1);
935
            echo '.';
936
        }
937
    }
938
939
    /**
940
     * Validate the type of a value and coerce it into the proper type.
941
     *
942
     * @param mixed &$value The value to validate.
943
     * @param string $type One of: bool, int, string.
944
     * @param string $name The name of the option if you want to print an error message.
945
     * @param array|null $def The option def if you want to print an error message.
946
     * @return bool Returns `true` if the value is the correct type.
947
     * @throws \Exception Throws an exception when {@see $type} is not a known value.
948
     */
949 20
    protected function validateType(&$value, $type, $name = '', $def = null) {
950
        switch ($type) {
951 8
            case 'boolean':
952
                if (is_bool($value)) {
953 6
                    $valid = true;
954 6
                } elseif ($value === 0) {
955
                    // 0 doesn't work well with in_array() so check it separately.
956
                    $value = false;
957
                    $valid = true;
958
                } elseif (in_array($value, [null, '', '0', 'false', 'no', 'disabled'])) {
959 5
                    $value = false;
960 5
                    $valid = true;
961
                } elseif (in_array($value, [1, '1', 'true', 'yes', 'enabled'])) {
962 2
                    $value = true;
963 2
                    $valid = true;
964
                } else {
965 3
                    $valid = false;
966 3
                }
967 6
                break;
968 13
            case 'integer':
969
                if (is_numeric($value)) {
970 6
                    $value = (int)$value;
971 6
                    $valid = true;
972
                } else {
973 3
                    $valid = false;
974 6
                }
975 9
                break;
976 20
            case 'string':
977 20
                $value = (string)$value;
978 20
                $valid = true;
979 20
                break;
980
            default:
981
                throw new \Exception("Unknown type: $type.", 400);
982
        }
983
984 8
        if (!$valid && $name) {
985
            $short = static::val('short', (array)$def);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
986 6
            $nameStr = "--$name".($short ? " (-$short)" : '');
987
            echo $this->red("The value of $nameStr is not a valid $type.".PHP_EOL);
988
        }
989
990 8
        return $valid;
991
    }
992
993
    /**
994
     * Writes a lis of all of the commands.
995
     */
996 2
    protected function writeCommands() {
997
        echo static::bold("COMMANDS").PHP_EOL;
998
999
        $table = new Table();
1000 2
        foreach ($this->commandSchemas as $pattern => $schema) {
1001
            if (static::isCommand($pattern)) {
1002
                $table
1003 1
                    ->row()
1004 1
                    ->cell($pattern)
1005
                    ->cell(Cli::val('description', Cli::val(Cli::META, $schema), ''));
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1006
            }
1007
        }
1008
        $table->write();
1009 2
    }
1010
1011
    /**
1012
     * Writes the cli help.
1013
     *
1014
     * @param string $command The name of the command or blank if there is no command.
1015
     */
1016 4
    public function writeHelp($command = '') {
1017
        $schema = $this->getSchema($command);
1018
        $this->writeSchemaHelp($schema);
1019 4
    }
1020
1021
    /**
1022
     * Writes the help for a given schema.
1023
     *
1024
     * @param array $schema A command line scheme returned from {@see Cli::getSchema()}.
1025
     */
1026 5
    protected function writeSchemaHelp($schema) {
1027
        // Write the command description.
1028
        $meta = Cli::val(Cli::META, $schema, []);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 8 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1029
        $description = Cli::val('description', $meta);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1030
1031 5
        if ($description) {
1032
            echo implode("\n", Cli::breakLines($description, 80, false)).PHP_EOL.PHP_EOL;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1033
        }
1034
1035 5
        unset($schema[Cli::META]);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1036
1037
        // Add the help.
1038
        $schema['help'] = [
1039
            'description' => 'Display this help.',
1040
            'type' => 'boolean',
1041
            'short' => '?'
1042 5
        ];
1043
1044
        echo Cli::bold('OPTIONS').PHP_EOL;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1045
1046
        ksort($schema);
1047
1048
        $table = new Table();
1049
        $table->setFormatOutput($this->formatOutput);
1050
1051
        foreach ($schema as $key => $definition) {
1052
            $table->row();
1053
1054
            // Write the keys.
1055
            $keys = "--{$key}";
1056
            if ($shortKey = Cli::val('short', $definition, false)) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1057 1
                $keys .= ", -$shortKey";
1058
            }
1059
            if (Cli::val('required', $definition)) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1060
                $table->bold($keys);
1061
            } else {
1062
                $table->cell($keys);
1063 2
            }
1064
1065
            // Write the description.
1066
            $table->cell(Cli::val('description', $definition, ''));
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1067
        }
1068
1069
        $table->write();
1070 5
        echo PHP_EOL;
1071
1072
        $args = Cli::val(Cli::ARGS, $meta, []);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1073 5
        if (!empty($args)) {
1074
            echo Cli::bold('ARGUMENTS').PHP_EOL;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1075
1076
            $table = new Table();
1077
            $table->setFormatOutput($this->formatOutput);
1078
1079
            foreach ($args as $argName => $arg) {
1080
                $table->row();
1081
1082
                if (Cli::val('required', $arg)) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1083
                    $table->bold($argName);
1084
                } else {
1085
                    $table->cell($argName);
1086 1
                }
1087
1088
                $table->cell(Cli::val('description', $arg, ''));
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1089
            }
1090
            $table->write();
1091 2
            echo PHP_EOL;
1092
        }
1093 5
    }
1094
1095
    /**
1096
     * Writes the basic usage information of the command.
1097
     *
1098
     * @param Args $args The parsed args returned from {@link Cli::parseRaw()}.
1099
     */
1100 6
    protected function writeUsage(Args $args) {
1101
        if ($filename = $args->getMeta('filename')) {
1102
            $schema = $this->getSchema($args->getCommand());
1103
            unset($schema[Cli::META]);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1104
1105
            echo static::bold("usage: ").$filename;
1106
1107
            if ($this->hasCommand()) {
1108
                if ($args->getCommand() && isset($this->commandSchemas[$args->getCommand()])) {
1109
                    echo ' '.$args->getCommand();
1110
1111
                } else {
1112 2
                    echo ' <command>';
1113 2
                }
1114
            }
1115
1116
            if ($this->hasOptions($args->getCommand())) {
1117 6
                echo " [<options>]";
1118
            }
1119
1120
            if ($hasArgs = $this->hasArgs($args->getCommand())) {
1121 4
                echo $hasArgs === 2 ? " <args>" : " [<args>]";
1122
            }
1123
1124 6
            echo PHP_EOL.PHP_EOL;
1125
        }
1126
    }
1127
1128
    /**
1129
     * Parse a schema in short form into a full schema array.
1130
     *
1131
     * @param array $arr The array to parse into a schema.
1132
     * @return array The full schema array.
1133
     * @throws \InvalidArgumentException Throws an exception when an item in the schema is invalid.
1134
     */
1135 1
    public static function parseSchema(array $arr) {
1136 1
        $result = [];
1137
1138
        foreach ($arr as $key => $value) {
1139
            if (is_int($key)) {
1140
                if (is_string($value)) {
1141
                    // This is a short param value.
1142
                    $param = static::parseShortParam($value);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 9 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
1143 1
                    $name = $param['name'];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 10 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
1144 1
                    $result[$name] = $param;
1145
                } else {
1146
                    throw new \InvalidArgumentException("Schema at position $key is not a valid param.", 500);
1147 1
                }
1148
            } else {
1149
                // The parameter is defined in the key.
1150
                $param = static::parseShortParam($key, $value);
1151
                $name = $param['name'];
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
1152
1153
                if (is_array($value)) {
1154
                    // The value describes a bit more about the schema.
1155
                    switch ($param['type']) {
1156
                        case 'array':
1157
                            if (isset($value['items'])) {
1158
                                // The value includes array schema information.
1159
                                $param = array_replace($param, $value);
1160
                            } else {
1161
                                // The value is a schema of items.
1162
                                $param['items'] = $value;
1163
                            }
1164
                            break;
1165
                        case 'object':
1166
                            // The value is a schema of the object.
1167
                            $param['properties'] = static::parseSchema($value);
1168
                            break;
1169
                        default:
1170
                            $param = array_replace($param, $value);
1171
                            break;
1172
                    }
1173
                } elseif (is_string($value)) {
1174
                    if ($param['type'] === 'array') {
1175
                        // Check to see if the value is the item type in the array.
1176
                        if (isset(self::$types[$value])) {
1177
                            $arrType = self::$types[$value];
1178
                        } elseif (($index = array_search($value, self::$types)) !== false) {
1179
                            $arrType = self::$types[$value];
1180
                        }
1181
1182
                        if (isset($arrType)) {
1183
                            $param['items'] = ['type' => $arrType];
1184
                        } else {
1185
                            $param['description'] = $value;
1186
                        }
1187
                    } else {
1188
                        // The value is the schema description.
1189
                        $param['description'] = $value;
1190
                    }
1191
                }
1192
1193
                $result[$name] = $param;
1194 1
            }
1195
        }
1196
1197 1
        return $result;
1198 1
    }
1199
1200
    /**
1201
     * Parse a short parameter string into a full array parameter.
1202
     *
1203
     * @param string $str The short parameter string to parse.
1204
     * @param array $other An array of other information that might help resolve ambiguity.
1205
     * @return array Returns an array in the form [name, [param]].
1206
     * @throws \InvalidArgumentException Throws an exception if the short param is not in the correct format.
1207
     */
1208 1
    protected static function parseShortParam($str, $other = []) {
1209
        // Is the parameter optional?
1210
        if (substr($str, -1) === '?') {
1211
            $required = false;
1212
            $str = substr($str, 0, -1);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
1213
        } else {
1214 1
            $required = true;
1215
        }
1216
1217
        // Check for a type.
1218
        $parts = explode(':', $str);
1219
1220
        if (count($parts) === 1) {
1221 1
            if (isset($other['type'])) {
1222
                $type = $other['type'];
1223
            } else {
1224 1
                $type = 'string';
1225
            }
1226 1
            $name = $parts[0];
1227
        } else {
1228
            $name = $parts[1];
1229
1230
            if (isset(self::$types[$parts[0]])) {
1231
                $type = self::$types[$parts[0]];
1232
            } else {
1233
                throw new \InvalidArgumentException("Invalid type {$parts[1]} for field $name.", 500);
1234
            }
1235
1236
            if (isset($parts[2])) {
1237 1
                $short = $parts[2];
1238
            }
1239 1
        }
1240
1241
        $result = ['name' => $name, 'type' => $type, 'required' => $required];
1242
1243
        if (isset($short)) {
1244 1
            $result['short'] = $short;
1245
        }
1246
1247
        return $result;
1248 1
    }
1249
1250
    /**
1251
     * Safely get a value out of an array.
1252
     *
1253
     * This function uses optimizations found in the [facebook libphputil library](https://github.com/facebook/libphutil).
1254
     *
1255
     * @param string|int $key The array key.
1256
     * @param array $array The array to get the value from.
1257
     * @param mixed $default The default value to return if the key doesn't exist.
1258
     * @return mixed The item from the array or `$default` if the array key doesn't exist.
1259
     */
1260 3
    public static function val($key, array $array, $default = null) {
1261
        // isset() is a micro-optimization - it is fast but fails for null values.
1262
        if (isset($array[$key])) {
1263
            return $array[$key];
1264
        }
1265
1266
        // Comparing $default is also a micro-optimization.
1267
        if ($default === null || array_key_exists($key, $array)) {
1268 3
            return null;
1269
        }
1270
1271 3
        return $default;
1272
    }
1273
}
1274