Option::setDefaultValue()   B
last analyzed

Complexity

Conditions 6
Paths 5

Size

Total Lines 20
Code Lines 12

Duplication

Lines 20
Ratio 100 %

Code Coverage

Tests 12
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 20
loc 20
ccs 12
cts 12
cp 1
rs 8.8571
cc 6
eloc 12
nc 5
nop 1
crap 6
1
<?php
2
3
/*
4
 * This file is part of the webmozart/console package.
5
 *
6
 * (c) Bernhard Schussek <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Webmozart\Console\Api\Args\Format;
13
14
use InvalidArgumentException;
15
use Webmozart\Assert\Assert;
16
use Webmozart\Console\Util\StringUtil;
17
18
/**
19
 * An input option.
20
 *
21
 * Args options are passed after the command name(s). Each option has a
22
 * long name that is prefixed by two dashes ("--") and optionally a short name
23
 * that is prefixed by one dash only ("-"). The long name must have at least
24
 * two characters, the short name must contain a single letter only.
25
 *
26
 * In the example below, "--verbose" and "-v" are the long and short names of
27
 * the same option:
28
 *
29
 * ```
30
 * $ console server --verbose
31
 * $ console server -v
32
 * ```
33
 *
34
 * The long and short names are passed to the constructor of this class. The
35
 * leading dashes can be omitted:
36
 *
37
 * ```php
38
 * $option = new Option('verbose', 'v');
39
 * ```
40
 *
41
 * If an option accepts a value, you must pass one of the flags
42
 * {@link VALUE_REQUIRED}, {@link VALUE_OPTIONAL} or {@link MULTI_VALUED} to
43
 * the constructor:
44
 *
45
 * ```php
46
 * $option = new Option('format', 'f', Option::VALUE_REQUIRED);
47
 * ```
48
 *
49
 *  * The flag {@link VALUE_REQUIRED} indicates that a value must always be
50
 *    passed.
51
 *  * The flag {@link VALUE_OPTIONAL} indicates that a value may optionally be
52
 *    passed. If no value is passed, the default value passed to the constructor
53
 *    is returned, which defaults to `null`.
54
 *  * The flag {@link MULTI_VALUED} indicates that the option can be passed
55
 *    multiple times with different values. The passed values are returned to
56
 *    the application as array. The value of a multi-valued option is always
57
 *    required.
58
 *
59
 * @since  1.0
60
 *
61
 * @author Bernhard Schussek <[email protected]>
62
 */
63
class Option extends AbstractOption
64
{
65
    /**
66
     * Flag: The option has no value.
67
     */
68
    const NO_VALUE = 4;
69
70
    /**
71
     * Flag: The option has a required value.
72
     */
73
    const REQUIRED_VALUE = 8;
74
75
    /**
76
     * Flag: The option has an optional value.
77
     */
78
    const OPTIONAL_VALUE = 16;
79
80
    /**
81
     * Flag: The option can be stated multiple times with different values.
82
     */
83
    const MULTI_VALUED = 32;
84
85
    /**
86
     * Flag: The option value is parsed as string.
87
     */
88
    const STRING = 128;
89
90
    /**
91
     * Flag: The option value is parsed as boolean.
92
     */
93
    const BOOLEAN = 256;
94
95
    /**
96
     * Flag: The option value is parsed as integer.
97
     */
98
    const INTEGER = 512;
99
100
    /**
101
     * Flag: The option value is parsed as float.
102
     */
103
    const FLOAT = 1024;
104
105
    /**
106
     * Flag: The option value "null" should be parsed as `null`.
107
     */
108
    const NULLABLE = 2048;
109
110
    /**
111
     * @var mixed
112
     */
113
    private $defaultValue;
114
115
    /**
116
     * @var string
117
     */
118
    private $valueName;
119
120
    /**
121
     * Creates a new option.
122
     *
123
     * @param string      $longName     The long option name.
124
     * @param string|null $shortName    The short option name.
125
     * @param int         $flags        A bitwise combination of the option flag
126
     *                                  constants.
127
     * @param string      $description  A human-readable description of the option.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $description 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...
128
     * @param mixed       $defaultValue The default value (must be null for
129
     *                                  {@link VALUE_REQUIRED} or
130
     *                                  {@link VALUE_NONE}).
131
     * @param string      $valueName    The name of the value to be used in
132
     *                                  usage examples of the option.
133
     *
134
     * @throws InvalidValueException If the default value is invalid.
135
     */
136 276
    public function __construct($longName, $shortName = null, $flags = 0, $description = null, $defaultValue = null, $valueName = '...')
137
    {
138 276
        Assert::string($valueName, 'The option value name must be a string. Got: %s');
139 274
        Assert::notEmpty($valueName, 'The option value name must not be empty.');
140
141 273
        $this->assertFlagsValid($flags);
142 262
        $this->addDefaultFlags($flags);
143
144 262
        parent::__construct($longName, $shortName, $flags, $description);
145
146 247
        $this->valueName = $valueName;
147
148 247
        if ($this->acceptsValue() || null !== $defaultValue) {
149 136
            $this->setDefaultValue($defaultValue);
150
        }
151 245
    }
152
153
    /**
154
     * Returns whether the option accepts a value.
155
     *
156
     * @return bool Returns `true` if a value flag other than {@link VALUE_NONE}
157
     *              was passed to the constructor.
158
     */
159 397
    public function acceptsValue()
160
    {
161 397
        return !(self::NO_VALUE & $this->flags);
162
    }
163
164
    /**
165
     * Parses an option value.
166
     *
167
     * Pass one of the flags {@link STRING}, {@link BOOLEAN}, {@link INTEGER}
168
     * and {@link FLOAT} to the constructor to configure the result of this
169
     * method. You can optionally combine the flags with {@link NULLABLE} to
170
     * support the conversion of "null" to `null`.
171
     *
172
     * @param mixed $value The value to parse.
173
     *
174
     * @return mixed The parsed value.
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use null|boolean|integer|double|string.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
175
     *
176
     * @throws InvalidValueException
177
     */
178 157 View Code Duplication
    public function parseValue($value)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
179
    {
180 157
        $nullable = ($this->flags & self::NULLABLE);
181
182 157
        if ($this->flags & self::BOOLEAN) {
183 4
            return StringUtil::parseBoolean($value, $nullable);
0 ignored issues
show
Documentation introduced by
$nullable is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
184
        }
185
186 153
        if ($this->flags & self::INTEGER) {
187 8
            return StringUtil::parseInteger($value, $nullable);
0 ignored issues
show
Documentation introduced by
$nullable is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
188
        }
189
190 145
        if ($this->flags & self::FLOAT) {
191 5
            return StringUtil::parseFloat($value, $nullable);
0 ignored issues
show
Documentation introduced by
$nullable is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
192
        }
193
194 140
        return StringUtil::parseString($value, $nullable);
0 ignored issues
show
Documentation introduced by
$nullable is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
195
    }
196
197
    /**
198
     * Returns whether the option requires a value.
199
     *
200
     * @return bool Returns `true` if the flag {@link VALUE_REQUIRED} was
201
     *              passed to the constructor.
202
     */
203 293
    public function isValueRequired()
204
    {
205 293
        return (bool) (self::REQUIRED_VALUE & $this->flags);
206
    }
207
208
    /**
209
     * Returns whether the option takes an optional value.
210
     *
211
     * @return bool Returns `true` if the flag {@link VALUE_OPTIONAL} was
212
     *              passed to the constructor.
213
     */
214 292
    public function isValueOptional()
215
    {
216 292
        return (bool) (self::OPTIONAL_VALUE & $this->flags);
217
    }
218
219
    /**
220
     * Returns whether the option accepts multiple values.
221
     *
222
     * @return bool Returns `true` if the flag {@link MULTI_VALUED} was
223
     *              passed to the constructor.
224
     */
225 333
    public function isMultiValued()
226
    {
227 333
        return (bool) (self::MULTI_VALUED & $this->flags);
228
    }
229
230
    /**
231
     * Sets the default value of the option.
232
     *
233
     * If the option does not accept a value, this method throws an exception.
234
     *
235
     * If the option is multi-valued, the passed value must be an array or
236
     * `null`.
237
     *
238
     * @param mixed $defaultValue The default value.
239
     *
240
     * @throws InvalidValueException If the default value is invalid.
241
     */
242 136 View Code Duplication
    public function setDefaultValue($defaultValue = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
243
    {
244 136
        if (!$this->acceptsValue()) {
245 1
            throw new InvalidValueException('Cannot set a default value when using the flag VALUE_NONE.');
246
        }
247
248 135
        if ($this->isMultiValued()) {
249 6
            if (null === $defaultValue) {
250 4
                $defaultValue = array();
251 2
            } elseif (!is_array($defaultValue)) {
252 1
                throw new InvalidValueException(sprintf(
253
                    'The default value of a multi-valued option must be an '.
254 1
                    'array. Got: %s',
255 1
                    is_object($defaultValue) ? get_class($defaultValue) : gettype($defaultValue)
256
                ));
257
            }
258
        }
259
260 134
        $this->defaultValue = $defaultValue;
261 134
    }
262
263
    /**
264
     * Returns the default value of the option.
265
     *
266
     * @return mixed The default value.
267
     */
268 298
    public function getDefaultValue()
269
    {
270 298
        return $this->defaultValue;
271
    }
272
273
    /**
274
     * Returns the name of the option value.
275
     *
276
     * This name can be used as placeholder of the value when displaying the
277
     * option's usage.
278
     *
279
     * @return string The name of the option value.
280
     */
281 25
    public function getValueName()
282
    {
283 25
        return $this->valueName;
284
    }
285
286 273
    private function assertFlagsValid($flags)
287
    {
288 273
        Assert::integer($flags, 'The option flags must be an integer. Got: %s');
289
290 272
        if ($flags & self::NO_VALUE) {
291 110
            if ($flags & self::REQUIRED_VALUE) {
292 1
                throw new InvalidArgumentException('The option flags VALUE_NONE and VALUE_REQUIRED cannot be combined.');
293
            }
294
295 109
            if ($flags & self::OPTIONAL_VALUE) {
296 1
                throw new InvalidArgumentException('The option flags VALUE_NONE and VALUE_OPTIONAL cannot be combined.');
297
            }
298
299 108
            if ($flags & self::MULTI_VALUED) {
300 1
                throw new InvalidArgumentException('The option flags VALUE_NONE and MULTI_VALUED cannot be combined.');
301
            }
302
        }
303
304 269
        if (($flags & self::OPTIONAL_VALUE) && ($flags & self::MULTI_VALUED)) {
305 1
            throw new InvalidArgumentException('The option flags VALUE_OPTIONAL and MULTI_VALUED cannot be combined.');
306
        }
307
308 268 View Code Duplication
        if ($flags & self::STRING) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
309 11
            if ($flags & self::BOOLEAN) {
310 1
                throw new InvalidArgumentException('The option flags STRING and BOOLEAN cannot be combined.');
311
            }
312
313 10
            if ($flags & self::INTEGER) {
314 1
                throw new InvalidArgumentException('The option flags STRING and INTEGER cannot be combined.');
315
            }
316
317 9
            if ($flags & self::FLOAT) {
318 9
                throw new InvalidArgumentException('The option flags STRING and FLOAT cannot be combined.');
319
            }
320 257
        } elseif ($flags & self::BOOLEAN) {
321 6
            if ($flags & self::INTEGER) {
322 1
                throw new InvalidArgumentException('The option flags BOOLEAN and INTEGER cannot be combined.');
323
            }
324
325 5
            if ($flags & self::FLOAT) {
326 5
                throw new InvalidArgumentException('The option flags BOOLEAN and FLOAT cannot be combined.');
327
            }
328 251
        } elseif ($flags & self::INTEGER) {
329 9
            if ($flags & self::FLOAT) {
330 1
                throw new InvalidArgumentException('The option flags INTEGER and FLOAT cannot be combined.');
331
            }
332
        }
333 262
    }
334
335 262
    private function addDefaultFlags(&$flags)
336
    {
337 262 View Code Duplication
        if (!($flags & (self::NO_VALUE | self::REQUIRED_VALUE | self::OPTIONAL_VALUE | self::MULTI_VALUED))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
338 121
            $flags |= self::NO_VALUE;
339
        }
340
341 262 View Code Duplication
        if (!($flags & (self::STRING | self::BOOLEAN | self::INTEGER | self::FLOAT))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
342 237
            $flags |= self::STRING;
343
        }
344
345 262
        if (($flags & self::MULTI_VALUED) && !($flags & self::REQUIRED_VALUE)) {
346 3
            $flags |= self::REQUIRED_VALUE;
347
        }
348 262
    }
349
}
350