Completed
Push — feature/logger ( 7bcbf9...bd5b6f )
by
unknown
01:54
created

Cli::__get()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.032

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 4
cts 5
cp 0.8
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 1
crap 2.032
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 41
    public function __construct() {
51 41
        $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 41
        $this->currentSchema =& $this->commandSchemas['*'];
55
56 41
        $this->formatOutput = static::guessFormatOutput();
57 41
    }
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 1
            trigger_error("Cli->format is deprecated. Use Cli->getFormatOutput() instead.", E_USER_DEPRECATED);
68 1
            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 1
            trigger_error("Cli->format is deprecated. Use Cli->setFormatOutput() instead.", E_USER_DEPRECATED);
82 1
            $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 1
    public function getFormatOutput() {
92 1
        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 $this
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 mixed $arg The value of the argument.
112
     */
113 2
    private function addArg(array $schema, Args $args, $arg) {
114 2
        $argsCount = count($args->getArgs());
115 2
        $schemaArgs = isset($schema[self::META][self::ARGS]) ? array_keys($schema[self::META][self::ARGS]) : [];
116 2
        $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 2
        $args->addArg($arg, $name);
119 2
    }
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
     * @param array $args The constructor arguments, if any.
127
     * @return static Returns a new Cli object.
128
     */
129 2
    public static function create(...$args) {
130 2
        return new static(...$args);
0 ignored issues
show
Unused Code introduced by
The call to Cli::__construct() has too many arguments starting with $args.

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

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

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

Loading history...
131
    }
132
133
    /**
134
     * Breaks a cell into several lines according to a given width.
135
     *
136
     * @param string $text The text of the cell.
137
     * @param int $width The width of the cell.
138
     * @param bool $addSpaces Whether or not to right-pad the cell with spaces.
139
     * @return array Returns an array of strings representing the lines in the cell.
140
     */
141 7
    public static function breakLines($text, $width, $addSpaces = true) {
142 7
        $rawLines = explode("\n", $text);
143 7
        $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...
144
145 7
        foreach ($rawLines as $line) {
146
            // Check to see if the line needs to be broken.
147 7
            $sublines = static::breakString($line, $width, $addSpaces);
148 7
            $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...
149
        }
150
151 7
        return $lines;
152
    }
153
154
    /**
155
     * Breaks a line of text according to a given width.
156
     *
157
     * @param string $line The text of the line.
158
     * @param int $width The width of the cell.
159
     * @param bool $addSpaces Whether or not to right pad the lines with spaces.
160
     * @return array Returns an array of lines, broken on word boundaries.
161
     */
162 7
    protected static function breakString($line, $width, $addSpaces = true) {
163 7
        $words = explode(' ', $line);
164 7
        $result = [];
165
166 7
        $line = '';
167 7
        foreach ($words as $word) {
168 7
            $candidate = trim($line.' '.$word);
169
170
            // Check for a new line.
171 7
            if (strlen($candidate) > $width) {
172 2
                if ($line === '') {
173
                    // The word is longer than a line.
174
                    if ($addSpaces) {
175
                        $result[] = substr($candidate, 0, $width);
176
                    } else {
177
                        $result[] = $candidate;
178
                    }
179
                } else {
180 2
                    if ($addSpaces) {
181
                        $line .= str_repeat(' ', $width - strlen($line));
182
                    }
183
184
                    // Start a new line.
185 2
                    $result[] = $line;
186 2
                    $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...
187
                }
188
            } else {
189 7
                $line = $candidate;
190
            }
191
        }
192
193
        // Add the remaining line.
194 7
        if ($line) {
195 7
            if ($addSpaces) {
196 7
                $line .= str_repeat(' ', $width - strlen($line));
197
            }
198
199
            // Start a new line.
200 7
            $result[] = $line;
201
        }
202
203 7
        return $result;
204
    }
205
206
    /**
207
     * Sets the description for the current command.
208
     *
209
     * @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...
210
     * @return $this
211
     */
212 8
    public function description($str = null) {
213 8
        return $this->meta('description', $str);
214
    }
215
216
    /**
217
     * Determines whether or not the schema has a command.
218
     *
219
     * @param string $name Check for the specific command name.
220
     * @return bool Returns true if the schema has a command.
221
     */
222 39
    public function hasCommand($name = '') {
223 39
        if ($name) {
224
            return array_key_exists($name, $this->commandSchemas);
225
        } else {
226 39
            foreach ($this->commandSchemas as $pattern => $opts) {
227 39
                if (strpos($pattern, '*') === false) {
228 39
                    return true;
229
                }
230
            }
231 35
            return false;
232
        }
233
    }
234
235
    /**
236
     * Determines whether a command has options.
237
     *
238
     * @param string $command The name of the command or an empty string for any command.
239
     * @return bool Returns true if the command has options. False otherwise.
240
     */
241 6
    public function hasOptions($command = '') {
242 6
        if ($command) {
243 2
            $def = $this->getSchema($command);
244 2
            return $this->hasOptionsDef($def);
245
        } else {
246 4
            foreach ($this->commandSchemas as $pattern => $def) {
247 4
                if ($this->hasOptionsDef($def)) {
248 4
                    return true;
249
                }
250
            }
251
        }
252
        return false;
253
    }
254
255
    /**
256
     * Determines whether or not a command definition has options.
257
     *
258
     * @param array $commandDef The command definition as returned from {@link Cli::getSchema()}.
259
     * @return bool Returns true if the command def has options or false otherwise.
260
     */
261 6
    protected function hasOptionsDef($commandDef) {
262 6
        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...
263
    }
264
265
    /**
266
     * Determines whether or not a command has args.
267
     *
268
     * @param string $command The command name to check.
269
     * @return int Returns one of the following.
270
     * - 0: The command has no args.
271
     * - 1: The command has optional args.
272
     * - 2: The command has required args.
273
     */
274 6
    public function hasArgs($command = '') {
275 6
        $args = null;
276
277 6
        if ($command) {
278
            // Check to see if the specific command has args.
279 2
            $def = $this->getSchema($command);
280 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...
281 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...
282
            }
283
        } else {
284 4
            foreach ($this->commandSchemas as $pattern => $def) {
285 4
                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...
286 4
                    $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...
287
                }
288
            }
289 4
            if (!empty($args)) {
290 2
                return 1;
291
            }
292
        }
293
294 4
        if (!$args || empty($args)) {
295 2
            return 0;
296
        }
297
298 2
        foreach ($args as $arg) {
299 2
            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...
300 2
                return 1;
301
            }
302
        }
303 1
        return 2;
304
    }
305
306
    /**
307
     * Finds our whether a pattern is a command.
308
     *
309
     * @param string $pattern The pattern being evaluated.
310
     * @return bool Returns `true` if `$pattern` is a command, `false` otherwise.
311
     */
312 2
    public static function isCommand($pattern) {
313 2
        return strpos($pattern, '*') === false;
314
    }
315
316
    /**
317
     * Parses and validates a set of command line arguments the schema.
318
     *
319
     * @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...
320
     *
321
     * Note that the `$argv` array must have at least one element and it must represent the path to the command that
322
     * invoked the command. This is used to write usage information.
323
     * @param bool $exit Whether to exit the application when there is an error or when writing help.
324
     * @return Args|null Returns an {@see Args} instance when a command should be executed
325
     * or `null` when one should not be executed.
326
     * @throws \Exception Throws an exception when {@link $exit} is false and the help or errors need to be displayed.
327
     */
328 39
    public function parse($argv = null, $exit = true) {
329 39
        $formatOutputBak = $this->formatOutput;
330
        // Only format commands if we are exiting.
331 39
        if (!$exit) {
332 38
            $this->formatOutput = false;
333
        }
334 39
        if (!$exit) {
335 38
            ob_start();
336
        }
337
338 39
        $args = $this->parseRaw($argv);
339
340 39
        $hasCommand = $this->hasCommand();
341
342 39
        if ($hasCommand && !$args->getCommand()) {
343
            // If no command is given then write a list of commands.
344 2
            $this->writeUsage($args);
345 2
            $this->writeCommands();
346 2
            $result = null;
347 37
        } elseif ($args->getOpt('help') || $args->getOpt('?')) {
348
            // Write the help.
349 4
            $this->writeUsage($args);
350 4
            $this->writeHelp($args->getCommand());
351 4
            $result = null;
352
        } else {
353
            // Validate the arguments against the schema.
354 33
            $validArgs = $this->validate($args);
355 33
            $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
        }
357 39
        if (!$exit) {
358 38
            $this->formatOutput = $formatOutputBak;
359 38
            $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 38
            if ($result === null) {
361 38
                throw new \Exception(trim($output));
362
            }
363 1
        } elseif ($result === null) {
364
            exit();
365
        }
366 25
        return $result;
367
    }
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 39
    protected function parseRaw($argv = null) {
380 39
        if ($argv === null) {
381
            $argv = $GLOBALS['argv'];
382
        }
383
384 39
        if (!is_array($argv)) {
385
            throw new \Exception(__METHOD__ . " expects an array", 400);
386
        }
387
388 39
        $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 39
        $hasCommand = $this->hasCommand();
390
391 39
        $parsed = new Args();
392 39
        $parsed->setMeta('path', $path);
393 39
        $parsed->setMeta('filename', basename($path));
394
395 39
        if (count($argv)) {
396
            // Get possible command.
397 38
            if (substr($argv[0], 0, 1) != '-') {
398 4
                $arg0 = array_shift($argv);
399 4
                if ($hasCommand) {
400 2
                    $parsed->setCommand($arg0);
401
                } else {
402 2
                    $schema = $this->getSchema($parsed->getCommand());
403 2
                    $this->addArg($schema, $parsed, $arg0);
404
                }
405
            }
406
            // Get the data types for all of the commands.
407 38
            if (!isset($schema)) {
408 36
                $schema = $this->getSchema($parsed->getCommand());
409
            }
410
411 38
            $types = [];
412 38
            foreach ($schema as $sName => $sRow) {
413 38
                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...
414 38
                    continue;
415
                }
416
417 36
                $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...
418 36
                $types[$sName] = $type;
419 36
                if (isset($sRow['short'])) {
420 36
                    $types[$sRow['short']] = $type;
421
                }
422
            }
423
424
            // Parse opts.
425 38
            for ($i = 0; $i < count($argv); $i++) {
426 38
                $str = $argv[$i];
427
428
                // Parse the help command as a boolean since it is not part of the schema!
429 38
                if (in_array($str, ['-?', '--help'])) {
430 6
                    $parsed->setOpt('help', true);
431 6
                    continue;
432
                }
433
434 32
                if ($str === '--') {
435
                    // --
436
                    $i++;
437
                    break;
438 32
                } elseif (strlen($str) > 2 && substr($str, 0, 2) == '--') {
439
                    // --foo
440 16
                    $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...
441 16
                    $parts = explode('=', $str);
442 16
                    $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...
443 16
                    $v = null;
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...
444
445
                    // Has a =, so pick the second piece
446 16
                    if (count($parts) == 2) {
447 9
                        $v = $parts[1];
448
                    // Does not have an =
449
                    } else {
450
                        // If there is a value (even if there are no equals)
451 9
                        if (isset($argv[$i + 1]) && preg_match('/^--?.+/', $argv[$i + 1]) == 0) {
452
                            // so choose the next arg as its value if any,
453 6
                            $v = $argv[$i + 1];
454
                            // If this is a boolean we need to coerce the value
455 6
                            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...
456 1
                                if (in_array($v, ['0', '1', 'true', 'false', 'on', 'off', 'yes', 'no'])) {
457
                                    // The next arg looks like a boolean to me.
458 1
                                    $i++;
459
                                } else {
460
                                    // Next arg is not a boolean: set the flag on, and use next arg in its own iteration
461 1
                                    $v = true;
462
                                }
463
                            } else {
464 6
                                $i++;
465
                            }
466
                        // If there is no value but we have a no- before the command
467 5
                        } elseif (strpos($key, 'no-') === 0) {
468 2
                            $tmpKey = str_replace('no-', null, $key);
469 2
                            if (Cli::val($tmpKey, $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...
470 2
                                $key = $tmpKey;
471 2
                                $v = false;
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...
472
                            }
473 3
                        } elseif (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...
474 2
                            $v = true;
475
                        }
476
                    }
477 16
                    $parsed->setOpt($key, $v);
478 24
                } elseif (strlen($str) == 2 && $str[0] == '-') {
479
                    // -a
480
481 9
                    $key = $str[1];
482 9
                    $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...
483 9
                    $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...
484
485 9
                    if (isset($argv[$i + 1])) {
486
                        // Try and be smart about the next arg.
487 6
                        $nextArg = $argv[$i + 1];
488
489 6
                        if ($type === 'boolean') {
490 2
                            if ($this->isStrictBoolean($nextArg)) {
491
                                // The next arg looks like a boolean to me.
492 1
                                $v = $nextArg;
493 1
                                $i++;
494
                            } else {
495 2
                                $v = true;
496
                            }
497 5
                        } elseif (!preg_match('/^--?.+/', $argv[$i + 1])) {
498
                            // The next arg is not an opt.
499 5
                            $v = $nextArg;
500 5
                            $i++;
501
                        } else {
502
                            // The next arg is another opt.
503
                            $v = null;
504
                        }
505
                    }
506
507 9
                    if ($v === null) {
508 3
                        $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...
509
                    }
510
511 9
                    $parsed->setOpt($key, $v);
512 18
                } elseif (strlen($str) > 1 && $str[0] == '-') {
513
                    // -abcdef
514 17
                    for ($j = 1; $j < strlen($str); $j++) {
515 17
                        $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...
516 17
                        $remaining = substr($str, $j + 1);
517 17
                        $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...
518
519
                        // Check for an explicit equals sign.
520 17
                        if (substr($remaining, 0, 1) === '=') {
521 3
                            $remaining = substr($remaining, 1);
522 3
                            if ($type === 'boolean') {
523
                                // Bypass the boolean flag checking below.
524
                                $parsed->setOpt($opt, $remaining);
525
                                break;
526
                            }
527
                        }
528
529 17
                        if ($type === 'boolean') {
530 5
                            if (preg_match('`^([01])`', $remaining, $matches)) {
531
                                // Treat the 0 or 1 as a true or false.
532 3
                                $parsed->setOpt($opt, $matches[1]);
533 3
                                $j += strlen($matches[1]);
534
                            } else {
535
                                // Treat the option as a flag.
536 5
                                $parsed->setOpt($opt, true);
537
                            }
538 16
                        } elseif ($type === 'string') {
539
                            // Treat the option as a set with no = sign.
540 14
                            $parsed->setOpt($opt, $remaining);
541 14
                            break;
542 3
                        } elseif ($type === 'integer') {
543 3
                            if (preg_match('`^(\d+)`', $remaining, $matches)) {
544
                                // Treat the option as a set with no = sign.
545 2
                                $parsed->setOpt($opt, $matches[1]);
546 2
                                $j += strlen($matches[1]);
547
                            } else {
548
                                // Treat the option as either multiple flags.
549 2
                                $optVal = $parsed->getOpt($opt, 0);
550 3
                                $parsed->setOpt($opt, $optVal + 1);
551
                            }
552
                        } else {
553
                            // This should not happen unless we've put a bug in our code.
554
                            throw new \Exception("Invalid type $type for $opt.", 500);
555
                        }
556
                    }
557
                } else {
558
                    // End of opts
559 1
                    break;
560
                }
561
            }
562
563
            // Grab the remaining args.
564 38
            for (; $i < count($argv); $i++) {
565 1
                $this->addArg($schema, $parsed, $argv[$i]);
566
            }
567
        }
568
569 39
        return $parsed;
570
    }
571
572
    /**
573
     * Validates arguments against the schema.
574
     *
575
     * @param Args $args The arguments that were returned from {@link Cli::parseRaw()}.
576
     * @return Args|null
577
     */
578 33
    public function validate(Args $args) {
579 33
        $isValid = true;
580 33
        $command = $args->getCommand();
581 33
        $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...
582 33
        $schema = $this->getSchema($command);
583 33
        ksort($schema);
584
585
//        $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...
586 33
        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...
587 33
        $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...
588 33
        $missing = [];
589
590
        // Check to see if the command is correct.
591 33
        if ($command && !$this->hasCommand($command) && $this->hasCommand()) {
592
            echo $this->red("Invalid command: $command.".PHP_EOL);
593
            $isValid = false;
594
        }
595
596
        // Add the args.
597 33
        $valid->setArgs($args->getArgs());
598
599 33
        foreach ($schema as $key => $definition) {
600
            // 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...
601 32
            $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...
602
603 32
            if (array_key_exists($key, $opts)) {
604
                // Check for --key.
605 14
                $value = $opts[$key];
606 14
                if ($this->validateType($value, $type, $key, $definition)) {
607 11
                    $valid->setOpt($key, $value);
608
                } else {
609 3
                    $isValid = false;
610
                }
611 14
                unset($opts[$key]);
612 27
            } elseif (isset($definition['short']) && array_key_exists($definition['short'], $opts)) {
613
                // Check for -s.
614 23
                $value = $opts[$definition['short']];
615 23
                if ($this->validateType($value, $type, $key, $definition)) {
616 22
                    $valid->setOpt($key, $value);
617
                } else {
618 2
                    $isValid = false;
619
                }
620 23
                unset($opts[$definition['short']]);
621 18
            } elseif (array_key_exists('no-'.$key, $opts)) {
622
                // Check for --no-key.
623 2
                $value = $opts['no-'.$key];
624
625 2
                if ($type !== 'boolean') {
626 1
                    echo $this->red("Cannot apply the --no- prefix on the non boolean --$key.".PHP_EOL);
627 1
                    $isValid = false;
628 1
                } elseif ($this->validateType($value, $type, $key, $definition)) {
629
                    $valid->setOpt($key, !$value);
630
                } else {
631 1
                    $isValid = false;
632
                }
633 2
                unset($opts['no-'.$key]);
634 18
            } elseif ($definition['required']) {
635
                // The key was not supplied. Is it required?
636 2
                $missing[$key] = true;
637 32
                $valid->setOpt($key, false);
638
            }
639
        }
640
641 33
        if (count($missing)) {
642 2
            $isValid = false;
643 2
            foreach ($missing as $key => $v) {
644 2
                echo $this->red("Missing required option: $key".PHP_EOL);
645
            }
646
        }
647
648 33
        if (count($opts)) {
649
            $isValid = false;
650
            foreach ($opts as $key => $v) {
651
                echo $this->red("Invalid option: $key".PHP_EOL);
652
            }
653
        }
654
655 33
        if ($isValid) {
656 25
            return $valid;
657
        } else {
658 8
            echo PHP_EOL;
659 8
            return null;
660
        }
661
    }
662
663
    /**
664
     * Gets the full cli schema.
665
     *
666
     * @param string $command The name of the command. This can be left blank if there is no command.
667
     * @return array Returns the schema that matches the command.
668
     */
669 40
    public function getSchema($command = '') {
670 40
        $result = [];
671 40
        foreach ($this->commandSchemas as $pattern => $opts) {
672 40
            if (fnmatch($pattern, $command)) {
673 40
                $result = array_replace_recursive($result, $opts);
674
            }
675
        }
676 40
        return $result;
677
    }
678
679
    /**
680
     * Gets/sets the value for a current meta item.
681
     *
682
     * @param string $name The name of the meta key.
683
     * @param mixed $value Set a new value for the meta key.
684
     * @return $this|mixed Returns the current value of the meta item or `$this` for fluent setting.
685
     */
686 8
    public function meta($name, $value = null) {
687 8
        if ($value !== null) {
688 8
            $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...
689 8
            return $this;
690
        }
691
        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...
692
            return null;
693
        }
694
        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...
695
    }
696
697
    /**
698
     * Adds an option (opt) to the current schema.
699
     *
700
     * @param string $name The long name(s) of the parameter.
701
     * You can use either just one name or a string in the form 'long:short' to specify the long and short name.
702
     * @param string $description A human-readable description for the column.
703
     * @param bool $required Whether or not the opt is required.
704
     * @param string $type The type of parameter.
705
     * This must be one of string, bool, integer.
706
     * @return $this
707
     * @throws \Exception Throws an exception when the type is invalid.
708
     */
709 38
    public function opt($name, $description, $required = false, $type = 'string') {
710
        switch ($type) {
711 38
            case 'str':
712 38
            case 'string':
713 36
                $type = 'string';
714 36
                break;
715 29
            case 'bool':
716 29
            case 'boolean':
717 26
                $type = 'boolean';
718 26
                break;
719 22
            case 'int':
720 22
            case 'integer':
721 22
                $type = 'integer';
722 22
                break;
723
            default:
724
                throw new \Exception("Invalid type: $type. Must be one of string, boolean, or integer.", 422);
725
        }
726
727
        // Break the name up into its long and short form.
728 38
        $parts = explode(':', $name, 2);
729 38
        $long = $parts[0];
730 38
        $short = static::val(1, $parts, '');
731
732 38
        $this->currentSchema[$long] = ['description' => $description, 'required' => $required, 'type' => $type, 'short' => $short];
733 38
        return $this;
734
    }
735
736
    /**
737
     * Define an arg on the current command.
738
     *
739
     * @param string $name The name of the arg.
740
     * @param string $description The arg description.
741
     * @param bool $required Whether or not the arg is required.
742
     * @return $this
743
     */
744 5
    public function arg($name, $description, $required = false) {
745 5
        $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...
746 5
            ['description' => $description, 'required' => $required];
747 5
        return $this;
748
    }
749
750
    /**
751
     * Selects the current command schema name.
752
     *
753
     * @param string $pattern The command pattern.
754
     * @return $this
755
     */
756 4
    public function command($pattern) {
757 4
        if (!isset($this->commandSchemas[$pattern])) {
758 4
            $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...
759
        }
760 4
        $this->currentSchema =& $this->commandSchemas[$pattern];
761
762 4
        return $this;
763
    }
764
765
766
    /**
767
     * Determine weather or not a value can be represented as a boolean.
768
     *
769
     * This method is sort of like {@link Cli::validateType()} but requires a more strict check of a boolean value.
770
     *
771
     * @param mixed $value The value to test.
772
     * @return bool
773
     */
774 2
    protected function isStrictBoolean($value, &$boolValue = null) {
775 2
        if ($value === true || $value === false) {
776
            $boolValue = $value;
777
            return true;
778 2
        } elseif (in_array($value, ['0', 'false', 'off', 'no'])) {
779 1
            $boolValue = false;
780 1
            return true;
781 2
        } elseif (in_array($value, ['1', 'true', 'on', 'yes'])) {
782
            $boolValue = true;
783
            return true;
784
        } else {
785 2
            $boolValue = null;
786 2
            return false;
787
        }
788
    }
789
790
    /**
791
     * Set the schema for a command.
792
     *
793
     * The schema array uses a short syntax so that commands can be specified as quickly as possible.
794
     * This schema is the exact same as those provided to {@link Schema::create()}.
795
     * The basic format of the array is the following:
796
     *
797
     * ```
798
     * [
799
     *     type:name[:shortCode][?],
800
     *     type:name[:shortCode][?],
801
     *     ...
802
     * ]
803
     * ```
804
     *
805
     * @param array $schema The schema array.
806
     */
807 1
    public function schema(array $schema) {
808 1
        $parsed = static::parseSchema($schema);
809
810 1
        $this->currentSchema = array_replace($this->currentSchema, $parsed);
811 1
    }
812
813
    /**
814
     * Bold some text.
815
     *
816
     * @param string $text The text to format.
817
     * @return string Returns the text surrounded by formatting commands.
818
     */
819 7
    public function bold($text) {
820 7
        return $this->formatString($text, ["\033[1m", "\033[0m"]);
821
    }
822
823
    /**
824
     * Bold some text.
825
     *
826
     * @param string $text The text to format.
827
     * @return string Returns the text surrounded by formatting commands.
828
     */
829
    public static function boldText($text) {
830
        return "\033[1m{$text}\033[0m";
831
    }
832
833
    /**
834
     * Make some text red.
835
     *
836
     * @param string $text The text to format.
837
     * @return string Returns  text surrounded by formatting commands.
838
     */
839 8
    public function red($text) {
840 8
        return $this->formatString($text, ["\033[1;31m", "\033[0m"]);
841
    }
842
843
    /**
844
     * Make some text red.
845
     *
846
     * @param string $text The text to format.
847
     * @return string Returns  text surrounded by formatting commands.
848
     */
849 1
    public static function redText($text) {
850 1
        return "\033[1;31m{$text}\033[0m";
851
    }
852
853
    /**
854
     * Make some text green.
855
     *
856
     * @param string $text The text to format.
857
     * @return string Returns  text surrounded by formatting commands.
858
     */
859
    public function green($text) {
860
        return $this->formatString($text, ["\033[1;32m", "\033[0m"]);
861
    }
862
863
    /**
864
     * Make some text green.
865
     *
866
     * @param string $text The text to format.
867
     * @return string Returns  text surrounded by formatting commands.
868
     */
869 1
    public static function greenText($text) {
870 1
        return "\033[1;32m{$text}\033[0m";
871
    }
872
873
    /**
874
     * Make some text blue.
875
     *
876
     * @param string $text The text to format.
877
     * @return string Returns  text surrounded by formatting commands.
878
     */
879
    public function blue($text) {
880
        return $this->formatString($text, ["\033[1;34m", "\033[0m"]);
881
    }
882
883
    /**
884
     * Make some text blue.
885
     *
886
     * @param string $text The text to format.
887
     * @return string Returns  text surrounded by formatting commands.
888
     */
889
    public static function blueText($text) {
890
        return "\033[1;34m{$text}\033[0m";
891
    }
892
893
    /**
894
     * Make some text purple.
895
     *
896
     * @param string $text The text to format.
897
     * @return string Returns  text surrounded by formatting commands.
898
     */
899
    public function purple($text) {
900
        return $this->formatString($text, ["\033[0;35m", "\033[0m"]);
901
    }
902
903
    /**
904
     * Make some text purple.
905
     *
906
     * @param string $text The text to format.
907
     * @return string Returns  text surrounded by formatting commands.
908
     */
909
    public static function purpleText($text) {
910
        return "\033[0;35m{$text}\033[0m";
911
    }
912
913
    /**
914
     * Format some text for the console.
915
     *
916
     * @param string $text The text to format.
917
     * @param string[] $wrap The format to wrap in the form ['before', 'after'].
918
     * @return string Returns the string formatted according to {@link Cli::$format}.
919
     */
920 15
    protected function formatString($text, array $wrap) {
921 15
        if ($this->formatOutput) {
922 1
            return "{$wrap[0]}$text{$wrap[1]}";
923
        } else {
924 14
            return $text;
925
        }
926
    }
927
928
    /**
929
     * Guess whether or not to format the output with colors.
930
     *
931
     * If the current environment is being redirected to a file then output should not be formatted. Also, Windows
932
     * machines do not support terminal colors so formatting should be suppressed on them too.
933
     *
934
     * @return bool Returns **true** if the output can be formatter or **false** otherwise.
935
     */
936 54
    public static function guessFormatOutput() {
937 54
        if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
938
            return false;
939 54
        } elseif (function_exists('posix_isatty')) {
940 54
            return posix_isatty(STDOUT);
941
        } else {
942
            return true;
943
        }
944
    }
945
946
    /**
947
     * Sleep for a number of seconds, echoing out a dot on each second.
948
     *
949
     * @param int $seconds The number of seconds to sleep.
950
     */
951
    public static function sleep($seconds) {
952
        for ($i = 0; $i < $seconds; $i++) {
953
            sleep(1);
954
            echo '.';
955
        }
956
    }
957
958
    /**
959
     * Validate the type of a value and coerce it into the proper type.
960
     *
961
     * @param mixed &$value The value to validate.
962
     * @param string $type One of: bool, int, string.
963
     * @param string $name The name of the option if you want to print an error message.
964
     * @param array|null $def The option def if you want to print an error message.
965
     * @return bool Returns `true` if the value is the correct type.
966
     * @throws \Exception Throws an exception when {@see $type} is not a known value.
967
     */
968 31
    protected function validateType(&$value, $type, $name = '', $def = null) {
969
        switch ($type) {
970 31
            case 'boolean':
971 15
                if (is_bool($value)) {
972 10
                    $valid = true;
973 8
                } elseif ($value === 0) {
974
                    // 0 doesn't work well with in_array() so check it separately.
975
                    $value = false;
976
                    $valid = true;
977 8
                } elseif (in_array($value, [null, '', '0', 'false', 'no', 'disabled'])) {
978 5
                    $value = false;
979 5
                    $valid = true;
980 5
                } elseif (in_array($value, [1, '1', 'true', 'yes', 'enabled'])) {
981 2
                    $value = true;
982 2
                    $valid = true;
983
                } else {
984 3
                    $valid = false;
985
                }
986 15
                break;
987 28
            case 'integer':
988 9
                if (is_numeric($value)) {
989 6
                    $value = (int)$value;
990 6
                    $valid = true;
991
                } else {
992 3
                    $valid = false;
993
                }
994 9
                break;
995 27
            case 'string':
996 27
                $value = (string)$value;
997 27
                $valid = true;
998 27
                break;
999
            default:
1000
                throw new \Exception("Unknown type: $type.", 400);
1001
        }
1002
1003 31
        if (!$valid && $name) {
1004 6
            $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...
1005 6
            $nameStr = "--$name".($short ? " (-$short)" : '');
1006 6
            echo $this->red("The value of $nameStr is not a valid $type.".PHP_EOL);
1007
        }
1008
1009 31
        return $valid;
1010
    }
1011
1012
    /**
1013
     * Writes a lis of all of the commands.
1014
     */
1015 2
    protected function writeCommands() {
1016 2
        echo static::bold("COMMANDS").PHP_EOL;
1017
1018 2
        $table = new Table();
1019 2
        foreach ($this->commandSchemas as $pattern => $schema) {
1020 2
            if (static::isCommand($pattern)) {
1021
                $table
1022 2
                    ->row()
1023 2
                    ->cell($pattern)
1024 2
                    ->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...
1025
            }
1026
        }
1027 2
        $table->write();
1028 2
    }
1029
1030
    /**
1031
     * Writes the cli help.
1032
     *
1033
     * @param string $command The name of the command or blank if there is no command.
1034
     */
1035 5
    public function writeHelp($command = '') {
1036 5
        $schema = $this->getSchema($command);
1037 5
        $this->writeSchemaHelp($schema);
1038 5
    }
1039
1040
    /**
1041
     * Writes the help for a given schema.
1042
     *
1043
     * @param array $schema A command line scheme returned from {@see Cli::getSchema()}.
1044
     */
1045 5
    protected function writeSchemaHelp($schema) {
1046
        // Write the command description.
1047 5
        $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...
1048 5
        $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...
1049
1050 5
        if ($description) {
1051 3
            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...
1052
        }
1053
1054 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...
1055
1056
        // Add the help.
1057 5
        $schema['help'] = [
1058
            'description' => 'Display this help.',
1059
            'type' => 'boolean',
1060
            'short' => '?'
1061
        ];
1062
1063 5
        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...
1064
1065 5
        ksort($schema);
1066
1067 5
        $table = new Table();
1068 5
        $table->setFormatOutput($this->formatOutput);
1069
1070 5
        foreach ($schema as $key => $definition) {
1071 5
            $table->row();
1072
1073
            // Write the keys.
1074 5
            $keys = "--{$key}";
1075 5
            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...
1076 5
                $keys .= ", -$shortKey";
1077
            }
1078 5
            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...
1079 3
                $table->bold($keys);
1080
            } else {
1081 5
                $table->cell($keys);
1082
            }
1083
1084
            // Write the description.
1085 5
            $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...
1086
        }
1087
1088 5
        $table->write();
1089 5
        echo PHP_EOL;
1090
1091 5
        $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...
1092 5
        if (!empty($args)) {
1093 2
            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...
1094
1095 2
            $table = new Table();
1096 2
            $table->setFormatOutput($this->formatOutput);
1097
1098 2
            foreach ($args as $argName => $arg) {
1099 2
                $table->row();
1100
1101 2
                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...
1102 1
                    $table->bold($argName);
1103
                } else {
1104 1
                    $table->cell($argName);
1105
                }
1106
1107 2
                $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...
1108
            }
1109 2
            $table->write();
1110 2
            echo PHP_EOL;
1111
        }
1112 5
    }
1113
1114
    /**
1115
     * Writes the basic usage information of the command.
1116
     *
1117
     * @param Args $args The parsed args returned from {@link Cli::parseRaw()}.
1118
     */
1119 6
    protected function writeUsage(Args $args) {
1120 6
        if ($filename = $args->getMeta('filename')) {
1121 6
            $schema = $this->getSchema($args->getCommand());
1122 6
            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...
1123
1124 6
            echo static::bold("usage: ").$filename;
1125
1126 6
            if ($this->hasCommand()) {
1127 4
                if ($args->getCommand() && isset($this->commandSchemas[$args->getCommand()])) {
1128 2
                    echo ' '.$args->getCommand();
1129
1130
                } else {
1131 2
                    echo ' <command>';
1132
                }
1133
            }
1134
1135 6
            if ($this->hasOptions($args->getCommand())) {
1136 6
                echo " [<options>]";
1137
            }
1138
1139 6
            if ($hasArgs = $this->hasArgs($args->getCommand())) {
1140 4
                echo $hasArgs === 2 ? " <args>" : " [<args>]";
1141
            }
1142
1143 6
            echo PHP_EOL.PHP_EOL;
1144
        }
1145 6
    }
1146
1147
    /**
1148
     * Parse a schema in short form into a full schema array.
1149
     *
1150
     * @param array $arr The array to parse into a schema.
1151
     * @return array The full schema array.
1152
     * @throws \InvalidArgumentException Throws an exception when an item in the schema is invalid.
1153
     */
1154 1
    public static function parseSchema(array $arr) {
1155 1
        $result = [];
1156
1157 1
        foreach ($arr as $key => $value) {
1158 1
            if (is_int($key)) {
1159 1
                if (is_string($value)) {
1160
                    // This is a short param value.
1161 1
                    $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...
1162 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...
1163 1
                    $result[$name] = $param;
1164
                } else {
1165 1
                    throw new \InvalidArgumentException("Schema at position $key is not a valid param.", 500);
1166
                }
1167
            } else {
1168
                // The parameter is defined in the key.
1169 1
                $param = static::parseShortParam($key, $value);
1170 1
                $name = $param['name'];
1171
1172 1
                if (is_array($value)) {
1173
                    // The value describes a bit more about the schema.
1174
                    switch ($param['type']) {
1175
                        case 'array':
1176
                            if (isset($value['items'])) {
1177
                                // The value includes array schema information.
1178
                                $param = array_replace($param, $value);
1179
                            } else {
1180
                                // The value is a schema of items.
1181
                                $param['items'] = $value;
1182
                            }
1183
                            break;
1184
                        case 'object':
1185
                            // The value is a schema of the object.
1186
                            $param['properties'] = static::parseSchema($value);
1187
                            break;
1188
                        default:
1189
                            $param = array_replace($param, $value);
1190
                            break;
1191
                    }
1192 1
                } elseif (is_string($value)) {
1193 1
                    if ($param['type'] === 'array') {
1194
                        // Check to see if the value is the item type in the array.
1195
                        if (isset(self::$types[$value])) {
1196
                            $arrType = self::$types[$value];
1197
                        } elseif (($index = array_search($value, self::$types)) !== false) {
1198
                            $arrType = self::$types[$value];
1199
                        }
1200
1201
                        if (isset($arrType)) {
1202
                            $param['items'] = ['type' => $arrType];
1203
                        } else {
1204
                            $param['description'] = $value;
1205
                        }
1206
                    } else {
1207
                        // The value is the schema description.
1208 1
                        $param['description'] = $value;
1209
                    }
1210
                }
1211
1212 1
                $result[$name] = $param;
1213
            }
1214
        }
1215
1216 1
        return $result;
1217
    }
1218
1219
    /**
1220
     * Parse a short parameter string into a full array parameter.
1221
     *
1222
     * @param string $str The short parameter string to parse.
1223
     * @param array $other An array of other information that might help resolve ambiguity.
1224
     * @return array Returns an array in the form [name, [param]].
1225
     * @throws \InvalidArgumentException Throws an exception if the short param is not in the correct format.
1226
     */
1227 1
    protected static function parseShortParam($str, $other = []) {
1228
        // Is the parameter optional?
1229 1
        if (substr($str, -1) === '?') {
1230 1
            $required = false;
1231 1
            $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...
1232
        } else {
1233 1
            $required = true;
1234
        }
1235
1236
        // Check for a type.
1237 1
        $parts = explode(':', $str);
1238
1239 1
        if (count($parts) === 1) {
1240 1
            if (isset($other['type'])) {
1241
                $type = $other['type'];
1242
            } else {
1243 1
                $type = 'string';
1244
            }
1245 1
            $name = $parts[0];
1246
        } else {
1247 1
            $name = $parts[1];
1248
1249 1
            if (isset(self::$types[$parts[0]])) {
1250 1
                $type = self::$types[$parts[0]];
1251
            } else {
1252
                throw new \InvalidArgumentException("Invalid type {$parts[1]} for field $name.", 500);
1253
            }
1254
1255 1
            if (isset($parts[2])) {
1256 1
                $short = $parts[2];
1257
            }
1258
        }
1259
1260 1
        $result = ['name' => $name, 'type' => $type, 'required' => $required];
1261
1262 1
        if (isset($short)) {
1263 1
            $result['short'] = $short;
1264
        }
1265
1266 1
        return $result;
1267
    }
1268
1269
    /**
1270
     * Safely get a value out of an array.
1271
     *
1272
     * This function uses optimizations found in the [facebook libphputil library](https://github.com/facebook/libphutil).
1273
     *
1274
     * @param string|int $key The array key.
1275
     * @param array $array The array to get the value from.
1276
     * @param mixed $default The default value to return if the key doesn't exist.
1277
     * @return mixed The item from the array or `$default` if the array key doesn't exist.
1278
     */
1279 41
    public static function val($key, array $array, $default = null) {
1280
        // isset() is a micro-optimization - it is fast but fails for null values.
1281 41
        if (isset($array[$key])) {
1282 40
            return $array[$key];
1283
        }
1284
1285
        // Comparing $default is also a micro-optimization.
1286 41
        if ($default === null || array_key_exists($key, $array)) {
1287 39
            return null;
1288
        }
1289
1290 10
        return $default;
1291
    }
1292
}
1293