Completed
Push — 2.1-master-merge ( 240673 )
by Alexander
13:45
created

Formatter::getUnitMessage()   D

Complexity

Conditions 9
Paths 19

Size

Total Lines 34
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 50.472

Importance

Changes 0
Metric Value
dl 0
loc 34
rs 4.909
c 0
b 0
f 0
ccs 4
cts 20
cp 0.2
cc 9
eloc 21
nc 19
nop 4
crap 50.472
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\i18n;
9
10
use Closure;
11
use DateInterval;
12
use DateTime;
13
use DateTimeInterface;
14
use DateTimeZone;
15
use IntlDateFormatter;
16
use NumberFormatter;
17
use Yii;
18
use yii\base\Component;
19
use yii\base\InvalidConfigException;
20
use yii\base\InvalidArgumentException;
21
use yii\helpers\FormatConverter;
22
use yii\helpers\Html;
23
use yii\helpers\HtmlPurifier;
24
25
/**
26
 * Formatter provides a set of commonly used data formatting methods.
27
 *
28
 * The formatting methods provided by Formatter are all named in the form of `asXyz()`.
29
 * The behavior of some of them may be configured via the properties of Formatter. For example,
30
 * by configuring [[dateFormat]], one may control how [[asDate()]] formats the value into a date string.
31
 *
32
 * Formatter is configured as an application component in [[\yii\base\Application]] by default.
33
 * You can access that instance via `Yii::$app->formatter`.
34
 *
35
 * The Formatter class is designed to format values according to a [[locale]]. For this feature to work
36
 * the [PHP intl extension](http://php.net/manual/en/book.intl.php) has to be installed.
37
 * Most of the methods however work also if the PHP intl extension is not installed by providing
38
 * a fallback implementation. Without intl month and day names are in English only.
39
 * Note that even if the intl extension is installed, formatting date and time values for years >=2038 or <=1901
40
 * on 32bit systems will fall back to the PHP implementation because intl uses a 32bit UNIX timestamp internally.
41
 * On a 64bit system the intl formatter is used in all cases if installed.
42
 *
43
 * @author Qiang Xue <[email protected]>
44
 * @author Enrica Ruedin <[email protected]>
45
 * @author Carsten Brandt <[email protected]>
46
 * @since 2.0
47
 */
48
class Formatter extends Component
49
{
50
    /**
51
     * @since 2.0.13
52
     */
53
    const UNIT_SYSTEM_METRIC = 'metric';
54
    /**
55
     * @since 2.0.13
56
     */
57
    const UNIT_SYSTEM_IMPERIAL = 'imperial';
58
    /**
59
     * @since 2.0.13
60
     */
61
    const FORMAT_WIDTH_LONG = 'long';
62
    /**
63
     * @since 2.0.13
64
     */
65
    const FORMAT_WIDTH_SHORT = 'short';
66
    /**
67
     * @since 2.0.13
68
     */
69
    const UNIT_LENGTH = 'length';
70
    /**
71
     * @since 2.0.13
72
     */
73
    const UNIT_WEIGHT = 'mass';
74
75
    /**
76
     * @var string the text to be displayed when formatting a `null` value.
77
     * Defaults to `'<span class="not-set">(not set)</span>'`, where `(not set)`
78
     * will be translated according to [[locale]].
79
     */
80
    public $nullDisplay;
81
    /**
82
     * @var array the text to be displayed when formatting a boolean value. The first element corresponds
83
     * to the text displayed for `false`, the second element for `true`.
84
     * Defaults to `['No', 'Yes']`, where `Yes` and `No`
85
     * will be translated according to [[locale]].
86
     */
87
    public $booleanFormat;
88
    /**
89
     * @var string the locale ID that is used to localize the date and number formatting.
90
     * For number and date formatting this is only effective when the
91
     * [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed.
92
     * If not set, [[\yii\base\Application::language]] will be used.
93
     */
94
    public $locale;
95
    /**
96
     * @var string the time zone to use for formatting time and date values.
97
     *
98
     * This can be any value that may be passed to [date_default_timezone_set()](http://www.php.net/manual/en/function.date-default-timezone-set.php)
99
     * e.g. `UTC`, `Europe/Berlin` or `America/Chicago`.
100
     * Refer to the [php manual](http://www.php.net/manual/en/timezones.php) for available time zones.
101
     * If this property is not set, [[\yii\base\Application::timeZone]] will be used.
102
     *
103
     * Note that the default time zone for input data is assumed to be UTC by default if no time zone is included in the input date value.
104
     * If you store your data in a different time zone in the database, you have to adjust [[defaultTimeZone]] accordingly.
105
     */
106
    public $timeZone;
107
    /**
108
     * @var string the time zone that is assumed for input values if they do not include a time zone explicitly.
109
     *
110
     * The value must be a valid time zone identifier, e.g. `UTC`, `Europe/Berlin` or `America/Chicago`.
111
     * Please refer to the [php manual](http://www.php.net/manual/en/timezones.php) for available time zones.
112
     *
113
     * It defaults to `UTC` so you only have to adjust this value if you store datetime values in another time zone in your database.
114
     *
115
     * Note that a UNIX timestamp is always in UTC by its definition. That means that specifying a default time zone different from
116
     * UTC has no effect on date values given as UNIX timestamp.
117
     *
118
     * @since 2.0.1
119
     */
120
    public $defaultTimeZone = 'UTC';
121
    /**
122
     * @var string the default format string to be used to format a [[asDate()|date]].
123
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
124
     *
125
     * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax).
126
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
127
     * PHP [date()](http://php.net/manual/en/function.date.php)-function.
128
     *
129
     * For example:
130
     *
131
     * ```php
132
     * 'MM/dd/yyyy' // date in ICU format
133
     * 'php:m/d/Y' // the same date in PHP format
134
     * ```
135
     */
136
    public $dateFormat = 'medium';
137
    /**
138
     * @var string the default format string to be used to format a [[asTime()|time]].
139
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
140
     *
141
     * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax).
142
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
143
     * PHP [date()](http://php.net/manual/en/function.date.php)-function.
144
     *
145
     * For example:
146
     *
147
     * ```php
148
     * 'HH:mm:ss' // time in ICU format
149
     * 'php:H:i:s' // the same time in PHP format
150
     * ```
151
     */
152
    public $timeFormat = 'medium';
153
    /**
154
     * @var string the default format string to be used to format a [[asDatetime()|date and time]].
155
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
156
     *
157
     * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax).
158
     *
159
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
160
     * PHP [date()](http://php.net/manual/en/function.date.php)-function.
161
     *
162
     * For example:
163
     *
164
     * ```php
165
     * 'MM/dd/yyyy HH:mm:ss' // date and time in ICU format
166
     * 'php:m/d/Y H:i:s' // the same date and time in PHP format
167
     * ```
168
     */
169
    public $datetimeFormat = 'medium';
170
    /**
171
     * @var \IntlCalendar|int|null the calendar to be used for date formatting. The value of this property will be directly
172
     * passed to the [constructor of the `IntlDateFormatter` class](http://php.net/manual/en/intldateformatter.create.php).
173
     *
174
     * Defaults to `null`, which means the Gregorian calendar will be used. You may also explicitly pass the constant
175
     * `\IntlDateFormatter::GREGORIAN` for Gregorian calendar.
176
     *
177
     * To use an alternative calendar like for example the [Jalali calendar](https://en.wikipedia.org/wiki/Jalali_calendar),
178
     * set this property to `\IntlDateFormatter::TRADITIONAL`.
179
     * The calendar must then be specified in the [[locale]], for example for the persian calendar the configuration for the formatter would be:
180
     *
181
     * ```php
182
     * 'formatter' => [
183
     *     'locale' => 'fa_IR@calendar=persian',
184
     *     'calendar' => \IntlDateFormatter::TRADITIONAL,
185
     * ],
186
     * ```
187
     *
188
     * Available calendar names can be found in the [ICU manual](http://userguide.icu-project.org/datetime/calendar).
189
     *
190
     * Since PHP 5.5 you may also use an instance of the [[\IntlCalendar]] class.
191
     * Check the [PHP manual](http://php.net/manual/en/intldateformatter.create.php) for more details.
192
     *
193
     * If the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, setting this property will have no effect.
194
     *
195
     * @see http://php.net/manual/en/intldateformatter.create.php
196
     * @see http://php.net/manual/en/class.intldateformatter.php#intl.intldateformatter-constants.calendartypes
197
     * @see http://php.net/manual/en/class.intlcalendar.php
198
     * @since 2.0.7
199
     */
200
    public $calendar;
201
    /**
202
     * @var string the character displayed as the decimal point when formatting a number.
203
     * If not set, the decimal separator corresponding to [[locale]] will be used.
204
     * If [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value is '.'.
205
     */
206
    public $decimalSeparator;
207
    /**
208
     * @var string the character displayed as the thousands separator (also called grouping separator) character when formatting a number.
209
     * If not set, the thousand separator corresponding to [[locale]] will be used.
210
     * If [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value is ','.
211
     */
212
    public $thousandSeparator;
213
    /**
214
     * @var array a list of name value pairs that are passed to the
215
     * intl [NumberFormatter::setAttribute()](http://php.net/manual/en/numberformatter.setattribute.php) method of all
216
     * the number formatter objects created by [[createNumberFormatter()]].
217
     * This property takes only effect if the [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed.
218
     *
219
     * Please refer to the [PHP manual](http://php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformatattribute)
220
     * for the possible options.
221
     *
222
     * For example to adjust the maximum and minimum value of fraction digits you can configure this property like the following:
223
     *
224
     * ```php
225
     * [
226
     *     NumberFormatter::MIN_FRACTION_DIGITS => 0,
227
     *     NumberFormatter::MAX_FRACTION_DIGITS => 2,
228
     * ]
229
     * ```
230
     */
231
    public $numberFormatterOptions = [];
232
    /**
233
     * @var array a list of name value pairs that are passed to the
234
     * intl [NumberFormatter::setTextAttribute()](http://php.net/manual/en/numberformatter.settextattribute.php) method of all
235
     * the number formatter objects created by [[createNumberFormatter()]].
236
     * This property takes only effect if the [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed.
237
     *
238
     * Please refer to the [PHP manual](http://php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformattextattribute)
239
     * for the possible options.
240
     *
241
     * For example to change the minus sign for negative numbers you can configure this property like the following:
242
     *
243
     * ```php
244
     * [
245
     *     NumberFormatter::NEGATIVE_PREFIX => 'MINUS',
246
     * ]
247
     * ```
248
     */
249
    public $numberFormatterTextOptions = [];
250
    /**
251
     * @var array a list of name value pairs that are passed to the
252
     * intl [NumberFormatter::setSymbol()](http://php.net/manual/en/numberformatter.setsymbol.php) method of all
253
     * the number formatter objects created by [[createNumberFormatter()]].
254
     * This property takes only effect if the [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed.
255
     *
256
     * Please refer to the [PHP manual](http://php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformatsymbol)
257
     * for the possible options.
258
     *
259
     * For example to choose a custom currency symbol, e.g. [U+20BD](http://unicode-table.com/en/20BD/) instead of `руб.` for Russian Ruble:
260
     *
261
     * ```php
262
     * [
263
     *     NumberFormatter::CURRENCY_SYMBOL => '₽',
264
     * ]
265
     * ```
266
     *
267
     * @since 2.0.4
268
     */
269
    public $numberFormatterSymbols = [];
270
    /**
271
     * @var string the 3-letter ISO 4217 currency code indicating the default currency to use for [[asCurrency]].
272
     * If not set, the currency code corresponding to [[locale]] will be used.
273
     * Note that in this case the [[locale]] has to be specified with a country code, e.g. `en-US` otherwise it
274
     * is not possible to determine the default currency.
275
     */
276
    public $currencyCode;
277
    /**
278
     * @var int the base at which a kilobyte is calculated (1000 or 1024 bytes per kilobyte), used by [[asSize]] and [[asShortSize]].
279
     * Defaults to 1024.
280
     */
281
    public $sizeFormatBase = 1024;
282
    /**
283
     * @var string default system of measure units. Defaults to [[UNIT_SYSTEM_METRIC]].
284
     * Possible values:
285
     *  - [[UNIT_SYSTEM_METRIC]]
286
     *  - [[UNIT_SYSTEM_IMPERIAL]]
287
     *
288
     * @see asLength
289
     * @see asWeight
290
     * @since 2.0.13
291
     */
292
    public $systemOfUnits = self::UNIT_SYSTEM_METRIC;
293
    /**
294
     * @var array configuration of weight and length measurement units.
295
     * This array contains the most usable measurement units, but you can change it
296
     * in case you have some special requirements.
297
     *
298
     * For example, you can add smaller measure unit:
299
     *
300
     * ```php
301
     * $this->measureUnits[self::UNIT_LENGTH][self::UNIT_SYSTEM_METRIC] = [
302
     *     'nanometer' => 0.000001
303
     * ]
304
     * ```
305
     * @see asLength
306
     * @see asWeight
307
     * @since 2.0.13
308
     */
309
    public $measureUnits = [
310
        self::UNIT_LENGTH => [
311
            self::UNIT_SYSTEM_IMPERIAL => [
312
                'inch' => 1,
313
                'foot' => 12,
314
                'yard' => 36,
315
                'chain' => 792,
316
                'furlong' => 7920,
317
                'mile' => 63360,
318
            ],
319
            self::UNIT_SYSTEM_METRIC => [
320
                'millimeter' => 1,
321
                'centimeter' => 10,
322
                'meter' => 1000,
323
                'kilometer' => 1000000,
324
            ],
325
        ],
326
        self::UNIT_WEIGHT => [
327
            self::UNIT_SYSTEM_IMPERIAL => [
328
                'grain' => 1,
329
                'drachm' => 27.34375,
330
                'ounce' => 437.5,
331
                'pound' => 7000,
332
                'stone' => 98000,
333
                'quarter' => 196000,
334
                'hundredweight' => 784000,
335
                'ton' => 15680000,
336
            ],
337
            self::UNIT_SYSTEM_METRIC => [
338
                'gram' => 1,
339
                'kilogram' => 1000,
340
                'ton' => 1000000,
341
            ],
342
        ],
343
    ];
344
    /**
345
     * @var array The base units that are used as multipliers for smallest possible unit from [[measureUnits]].
346
     * @since 2.0.13
347
     */
348
    public $baseUnits = [
349
        self::UNIT_LENGTH => [
350
            self::UNIT_SYSTEM_IMPERIAL => 12, // 1 feet = 12 inches
351
            self::UNIT_SYSTEM_METRIC => 1000, // 1 meter = 1000 millimeters
352
        ],
353
        self::UNIT_WEIGHT => [
354
            self::UNIT_SYSTEM_IMPERIAL => 7000, // 1 pound = 7000 grains
355
            self::UNIT_SYSTEM_METRIC => 1000, // 1 kilogram = 1000 grams
356
        ],
357
    ];
358
    /**
359
     * @var bool whether the [PHP intl extension](http://php.net/manual/en/book.intl.php) is loaded.
360
     */
361
    private $_intlLoaded = false;
362
    /**
363
     * @var \ResourceBundle cached ResourceBundle object used to read unit translations
364
     */
365
    private $_resourceBundle;
366
    /**
367
     * @var array cached unit translation patterns
368
     */
369
    private $_unitMessages = [];
370
371
372
    /**
373
     * @inheritdoc
374
     */
375 257
    public function init()
376
    {
377 257
        if ($this->timeZone === null) {
378 257
            $this->timeZone = Yii::$app->timeZone;
379
        }
380 257
        if ($this->locale === null) {
381 38
            $this->locale = Yii::$app->language;
382
        }
383 257
        if ($this->booleanFormat === null) {
384 257
            $this->booleanFormat = [Yii::t('yii', 'No', [], $this->locale), Yii::t('yii', 'Yes', [], $this->locale)];
385
        }
386 257
        if ($this->nullDisplay === null) {
387 257
            $this->nullDisplay = '<span class="not-set">' . Yii::t('yii', '(not set)', [], $this->locale) . '</span>';
388
        }
389 257
        $this->_intlLoaded = extension_loaded('intl');
390 257
        if (!$this->_intlLoaded) {
391 117
            if ($this->decimalSeparator === null) {
392 117
                $this->decimalSeparator = '.';
393
            }
394 117
            if ($this->thousandSeparator === null) {
395 117
                $this->thousandSeparator = ',';
396
            }
397
        }
398 257
    }
399
400
    /**
401
     * Formats the value based on the given format type.
402
     * This method will call one of the "as" methods available in this class to do the formatting.
403
     * For type "xyz", the method "asXyz" will be used. For example, if the format is "html",
404
     * then [[asHtml()]] will be used. Format names are case insensitive.
405
     * @param mixed $value the value to be formatted.
406
     * @param string|array|Closure $format the format of the value, e.g., "html", "text" or an anonymous function
407
     * returning the formatted value.
408
     *
409
     * To specify additional parameters of the formatting method, you may use an array.
410
     * The first element of the array specifies the format name, while the rest of the elements will be used as the
411
     * parameters to the formatting method. For example, a format of `['date', 'Y-m-d']` will cause the invocation
412
     * of `asDate($value, 'Y-m-d')`.
413
     *
414
     * The anonymous function signature should be: `function($value, $formatter)`,
415
     * where `$value` is the value that should be formatted and `$formatter` is an instance of the Formatter class,
416
     * which can be used to call other formatting functions.
417
     * The possibility to use an anonymous function is available since version 2.0.13.
418
     * @return string the formatting result.
419
     * @throws InvalidArgumentException if the format type is not supported by this class.
420
     */
421 11
    public function format($value, $format)
422
    {
423 11
        if ($format instanceof Closure) {
424
            return call_user_func($format, $value, $this);
425 11
        } elseif (is_array($format)) {
426 7
            if (!isset($format[0])) {
427
                throw new InvalidArgumentException('The $format array must contain at least one element.');
428
            }
429 7
            $f = $format[0];
430 7
            $format[0] = $value;
431 7
            $params = $format;
432 7
            $format = $f;
433
        } else {
434 6
            $params = [$value];
435
        }
436 11
        $method = 'as' . $format;
437 11
        if ($this->hasMethod($method)) {
438 11
            return call_user_func_array([$this, $method], $params);
439
        }
440
441 2
        throw new InvalidArgumentException("Unknown format type: $format");
442
    }
443
444
445
    // simple formats
446
447
448
    /**
449
     * Formats the value as is without any formatting.
450
     * This method simply returns back the parameter without any format.
451
     * The only exception is a `null` value which will be formatted using [[nullDisplay]].
452
     * @param mixed $value the value to be formatted.
453
     * @return string the formatted result.
454
     */
455 1
    public function asRaw($value)
456
    {
457 1
        if ($value === null) {
458 1
            return $this->nullDisplay;
459
        }
460
461 1
        return $value;
462
    }
463
464
    /**
465
     * Formats the value as an HTML-encoded plain text.
466
     * @param string $value the value to be formatted.
467
     * @return string the formatted result.
468
     */
469 5
    public function asText($value)
470
    {
471 5
        if ($value === null) {
472 2
            return $this->nullDisplay;
473
        }
474
475 5
        return Html::encode($value);
476
    }
477
478
    /**
479
     * Formats the value as an HTML-encoded plain text with newlines converted into breaks.
480
     * @param string $value the value to be formatted.
481
     * @return string the formatted result.
482
     */
483 1
    public function asNtext($value)
484
    {
485 1
        if ($value === null) {
486 1
            return $this->nullDisplay;
487
        }
488
489 1
        return nl2br(Html::encode($value));
490
    }
491
492
    /**
493
     * Formats the value as HTML-encoded text paragraphs.
494
     * Each text paragraph is enclosed within a `<p>` tag.
495
     * One or multiple consecutive empty lines divide two paragraphs.
496
     * @param string $value the value to be formatted.
497
     * @return string the formatted result.
498
     */
499 1
    public function asParagraphs($value)
500
    {
501 1
        if ($value === null) {
502 1
            return $this->nullDisplay;
503
        }
504
505 1
        return str_replace('<p></p>', '', '<p>' . preg_replace('/\R{2,}/u', "</p>\n<p>", Html::encode($value)) . '</p>');
506
    }
507
508
    /**
509
     * Formats the value as HTML text.
510
     * The value will be purified using [[HtmlPurifier]] to avoid XSS attacks.
511
     * Use [[asRaw()]] if you do not want any purification of the value.
512
     * @param string $value the value to be formatted.
513
     * @param array|null $config the configuration for the HTMLPurifier class.
514
     * @return string the formatted result.
515
     */
516
    public function asHtml($value, $config = null)
517
    {
518
        if ($value === null) {
519
            return $this->nullDisplay;
520
        }
521
522
        return HtmlPurifier::process($value, $config);
523
    }
524
525
    /**
526
     * Formats the value as a mailto link.
527
     * @param string $value the value to be formatted.
528
     * @param array $options the tag options in terms of name-value pairs. See [[Html::mailto()]].
529
     * @return string the formatted result.
530
     */
531 1
    public function asEmail($value, $options = [])
532
    {
533 1
        if ($value === null) {
534 1
            return $this->nullDisplay;
535
        }
536
537 1
        return Html::mailto(Html::encode($value), $value, $options);
538
    }
539
540
    /**
541
     * Formats the value as an image tag.
542
     * @param mixed $value the value to be formatted.
543
     * @param array $options the tag options in terms of name-value pairs. See [[Html::img()]].
544
     * @return string the formatted result.
545
     */
546 1
    public function asImage($value, $options = [])
547
    {
548 1
        if ($value === null) {
549 1
            return $this->nullDisplay;
550
        }
551
552 1
        return Html::img($value, $options);
553
    }
554
555
    /**
556
     * Formats the value as a hyperlink.
557
     * @param mixed $value the value to be formatted.
558
     * @param array $options the tag options in terms of name-value pairs. See [[Html::a()]].
559
     * @return string the formatted result.
560
     */
561 1
    public function asUrl($value, $options = [])
562
    {
563 1
        if ($value === null) {
564 1
            return $this->nullDisplay;
565
        }
566 1
        $url = $value;
567 1
        if (strpos($url, '://') === false) {
568 1
            $url = 'http://' . $url;
569
        }
570
571 1
        return Html::a(Html::encode($value), $url, $options);
572
    }
573
574
    /**
575
     * Formats the value as a boolean.
576
     * @param mixed $value the value to be formatted.
577
     * @return string the formatted result.
578
     * @see booleanFormat
579
     */
580 1
    public function asBoolean($value)
581
    {
582 1
        if ($value === null) {
583 1
            return $this->nullDisplay;
584
        }
585
586 1
        return $value ? $this->booleanFormat[1] : $this->booleanFormat[0];
587
    }
588
589
590
    // date and time formats
591
592
593
    /**
594
     * Formats the value as a date.
595
     * @param int|string|DateTime $value the value to be formatted. The following
596
     * types of value are supported:
597
     *
598
     * - an integer representing a UNIX timestamp. A UNIX timestamp is always in UTC by its definition.
599
     * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
600
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
601
     * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object. You may set the time zone
602
     *   for the DateTime object to specify the source time zone.
603
     *
604
     * The formatter will convert date values according to [[timeZone]] before formatting it.
605
     * If no timezone conversion should be performed, you need to set [[defaultTimeZone]] and [[timeZone]] to the same value.
606
     * Also no conversion will be performed on values that have no time information, e.g. `"2017-06-05"`.
607
     *
608
     * @param string $format the format used to convert the value into a date string.
609
     * If null, [[dateFormat]] will be used.
610
     *
611
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
612
     * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
613
     *
614
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
615
     * PHP [date()](http://php.net/manual/en/function.date.php)-function.
616
     *
617
     * @return string the formatted result.
618
     * @throws InvalidArgumentException if the input value can not be evaluated as a date value.
619
     * @throws InvalidConfigException if the date format is invalid.
620
     * @see dateFormat
621
     */
622 168
    public function asDate($value, $format = null)
623
    {
624 168
        if ($format === null) {
625 146
            $format = $this->dateFormat;
626
        }
627
628 168
        return $this->formatDateTimeValue($value, $format, 'date');
629
    }
630
631
    /**
632
     * Formats the value as a time.
633
     * @param int|string|DateTime $value the value to be formatted. The following
634
     * types of value are supported:
635
     *
636
     * - an integer representing a UNIX timestamp. A UNIX timestamp is always in UTC by its definition.
637
     * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
638
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
639
     * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object. You may set the time zone
640
     *   for the DateTime object to specify the source time zone.
641
     *
642
     * The formatter will convert date values according to [[timeZone]] before formatting it.
643
     * If no timezone conversion should be performed, you need to set [[defaultTimeZone]] and [[timeZone]] to the same value.
644
     *
645
     * @param string $format the format used to convert the value into a date string.
646
     * If null, [[timeFormat]] will be used.
647
     *
648
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
649
     * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
650
     *
651
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
652
     * PHP [date()](http://php.net/manual/en/function.date.php)-function.
653
     *
654
     * @return string the formatted result.
655
     * @throws InvalidArgumentException if the input value can not be evaluated as a date value.
656
     * @throws InvalidConfigException if the date format is invalid.
657
     * @see timeFormat
658
     */
659 148
    public function asTime($value, $format = null)
660
    {
661 148
        if ($format === null) {
662 144
            $format = $this->timeFormat;
663
        }
664
665 148
        return $this->formatDateTimeValue($value, $format, 'time');
666
    }
667
668
    /**
669
     * Formats the value as a datetime.
670
     * @param int|string|DateTime $value the value to be formatted. The following
671
     * types of value are supported:
672
     *
673
     * - an integer representing a UNIX timestamp. A UNIX timestamp is always in UTC by its definition.
674
     * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
675
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
676
     * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object. You may set the time zone
677
     *   for the DateTime object to specify the source time zone.
678
     *
679
     * The formatter will convert date values according to [[timeZone]] before formatting it.
680
     * If no timezone conversion should be performed, you need to set [[defaultTimeZone]] and [[timeZone]] to the same value.
681
     *
682
     * @param string $format the format used to convert the value into a date string.
683
     * If null, [[datetimeFormat]] will be used.
684
     *
685
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
686
     * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
687
     *
688
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
689
     * PHP [date()](http://php.net/manual/en/function.date.php)-function.
690
     *
691
     * @return string the formatted result.
692
     * @throws InvalidArgumentException if the input value can not be evaluated as a date value.
693
     * @throws InvalidConfigException if the date format is invalid.
694
     * @see datetimeFormat
695
     */
696 152
    public function asDatetime($value, $format = null)
697
    {
698 152
        if ($format === null) {
699 144
            $format = $this->datetimeFormat;
700
        }
701
702 152
        return $this->formatDateTimeValue($value, $format, 'datetime');
703
    }
704
705
    /**
706
     * @var array map of short format names to IntlDateFormatter constant values.
707
     */
708
    private $_dateFormats = [
709
        'short' => 3, // IntlDateFormatter::SHORT,
710
        'medium' => 2, // IntlDateFormatter::MEDIUM,
711
        'long' => 1, // IntlDateFormatter::LONG,
712
        'full' => 0, // IntlDateFormatter::FULL,
713
    ];
714
715
    /**
716
     * @param int|string|DateTime $value the value to be formatted. The following
717
     * types of value are supported:
718
     *
719
     * - an integer representing a UNIX timestamp
720
     * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
721
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
722
     * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object
723
     *
724
     * @param string $format the format used to convert the value into a date string.
725
     * @param string $type 'date', 'time', or 'datetime'.
726
     * @throws InvalidConfigException if the date format is invalid.
727
     * @return string the formatted result.
728
     */
729 176
    private function formatDateTimeValue($value, $format, $type)
730
    {
731 176
        $timeZone = $this->timeZone;
732
        // avoid time zone conversion for date-only and time-only values
733 176
        if ($type === 'date' || $type === 'time') {
734 170
            [$timestamp, $hasTimeInfo, $hasDateInfo] = $this->normalizeDatetimeValue($value, true);
0 ignored issues
show
Bug introduced by
The variable $timestamp seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
Bug introduced by
The variable $hasTimeInfo does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $hasDateInfo does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
735 168
            if ($type === 'date' && !$hasTimeInfo || $type === 'time' && !$hasDateInfo) {
736 168
                $timeZone = $this->defaultTimeZone;
737
            }
738
        } else {
739 152
            $timestamp = $this->normalizeDatetimeValue($value);
740
        }
741 174
        if ($timestamp === null) {
0 ignored issues
show
Bug introduced by
The variable $timestamp does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
742 6
            return $this->nullDisplay;
743
        }
744
745
        // intl does not work with dates >=2038 or <=1901 on 32bit machines, fall back to PHP
746 174
        $year = $timestamp->format('Y');
747 174
        if ($this->_intlLoaded && !(PHP_INT_SIZE === 4 && ($year <= 1901 || $year >= 2038))) {
748 85
            if (strncmp($format, 'php:', 4) === 0) {
749 6
                $format = FormatConverter::convertDatePhpToIcu(substr($format, 4));
750
            }
751 85
            if (isset($this->_dateFormats[$format])) {
752 3
                if ($type === 'date') {
753 1
                    $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], IntlDateFormatter::NONE, $timeZone, $this->calendar);
754 2
                } elseif ($type === 'time') {
755 1
                    $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, $this->_dateFormats[$format], $timeZone, $this->calendar);
756
                } else {
757 3
                    $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], $this->_dateFormats[$format], $timeZone, $this->calendar);
758
                }
759
            } else {
760 85
                $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE, $timeZone, $this->calendar, $format);
761
            }
762 85
            if ($formatter === null) {
763
                throw new InvalidConfigException(intl_get_error_message());
764
            }
765
            // make IntlDateFormatter work with DateTimeImmutable
766 85
            if ($timestamp instanceof \DateTimeImmutable) {
767 14
                $timestamp = new DateTime($timestamp->format(DateTime::ISO8601), $timestamp->getTimezone());
768
            }
769
770 85
            return $formatter->format($timestamp);
771
        }
772
773 89
        if (strncmp($format, 'php:', 4) === 0) {
774 11
            $format = substr($format, 4);
775
        } else {
776 84
            $format = FormatConverter::convertDateIcuToPhp($format, $type, $this->locale);
777
        }
778 89
        if ($timeZone != null) {
779 89
            if ($timestamp instanceof \DateTimeImmutable) {
780 13
                $timestamp = $timestamp->setTimezone(new DateTimeZone($timeZone));
781
            } else {
782 79
                $timestamp->setTimezone(new DateTimeZone($timeZone));
783
            }
784
        }
785
786 89
        return $timestamp->format($format);
787
    }
788
789
    /**
790
     * Normalizes the given datetime value as a DateTime object that can be taken by various date/time formatting methods.
791
     *
792
     * @param int|string|DateTime $value the datetime value to be normalized. The following
793
     * types of value are supported:
794
     *
795
     * - an integer representing a UNIX timestamp
796
     * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
797
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
798
     * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object
799
     *
800
     * @param bool $checkDateTimeInfo whether to also check if the date/time value has some time and date information attached.
801
     * Defaults to `false`. If `true`, the method will then return an array with the first element being the normalized
802
     * timestamp, the second a boolean indicating whether the timestamp has time information and third a boolean indicating
803
     * whether the timestamp has date information.
804
     * This parameter is available since version 2.0.1.
805
     * @return DateTime|array the normalized datetime value.
806
     * Since version 2.0.1 this may also return an array if `$checkDateTimeInfo` is true.
807
     * The first element of the array is the normalized timestamp and the second is a boolean indicating whether
808
     * the timestamp has time information or it is just a date value.
809
     * Since version 2.0.12 the array has third boolean element indicating whether the timestamp has date information
810
     * or it is just a time value.
811
     * @throws InvalidArgumentException if the input value can not be evaluated as a date value.
812
     */
813 181
    protected function normalizeDatetimeValue($value, $checkDateTimeInfo = false)
814
    {
815
        // checking for DateTime and DateTimeInterface is not redundant, DateTimeInterface is only in PHP>5.5
816 181
        if ($value === null || $value instanceof DateTime || $value instanceof DateTimeInterface) {
817
            // skip any processing
818 50
            return $checkDateTimeInfo ? [$value, true, true] : $value;
819
        }
820 141
        if (empty($value)) {
821 10
            $value = 0;
822
        }
823
        try {
824 141
            if (is_numeric($value)) { // process as unix timestamp, which is always in UTC
825 31
                $timestamp = new DateTime('@' . (int) $value, new DateTimeZone('UTC'));
826 31
                return $checkDateTimeInfo ? [$timestamp, true, true] : $timestamp;
827 115
            } elseif (($timestamp = DateTime::createFromFormat('Y-m-d|', $value, new DateTimeZone($this->defaultTimeZone))) !== false) { // try Y-m-d format (support invalid dates like 2012-13-01)
828 12
                return $checkDateTimeInfo ? [$timestamp, false, true] : $timestamp;
829 103
            } elseif (($timestamp = DateTime::createFromFormat('Y-m-d H:i:s', $value, new DateTimeZone($this->defaultTimeZone))) !== false) { // try Y-m-d H:i:s format (support invalid dates like 2012-13-01 12:63:12)
830 19
                return $checkDateTimeInfo ? [$timestamp, true, true] : $timestamp;
831
            }
832
            // finally try to create a DateTime object with the value
833 88
            if ($checkDateTimeInfo) {
834 86
                $timestamp = new DateTime($value, new DateTimeZone($this->defaultTimeZone));
835 84
                $info = date_parse($value);
836
                return [
837 84
                    $timestamp,
838 84
                    !($info['hour'] === false && $info['minute'] === false && $info['second'] === false),
839 84
                    !($info['year'] === false && $info['month'] === false && $info['day'] === false),
840
                ];
841
            }
842
843 86
            return new DateTime($value, new DateTimeZone($this->defaultTimeZone));
844 2
        } catch (\Exception $e) {
845 2
            throw new InvalidArgumentException("'$value' is not a valid date time value: " . $e->getMessage()
846 2
                . "\n" . print_r(DateTime::getLastErrors(), true), $e->getCode(), $e);
847
        }
848
    }
849
850
    /**
851
     * Formats a date, time or datetime in a float number as UNIX timestamp (seconds since 01-01-1970).
852
     * @param int|string|DateTime $value the value to be formatted. The following
853
     * types of value are supported:
854
     *
855
     * - an integer representing a UNIX timestamp
856
     * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
857
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
858
     * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object
859
     *
860
     * @return string the formatted result.
861
     */
862 145
    public function asTimestamp($value)
863
    {
864 145
        if ($value === null) {
865 2
            return $this->nullDisplay;
866
        }
867 145
        $timestamp = $this->normalizeDatetimeValue($value);
868 145
        return number_format($timestamp->format('U'), 0, '.', '');
869
    }
870
871
    /**
872
     * Formats the value as the time interval between a date and now in human readable form.
873
     *
874
     * This method can be used in three different ways:
875
     *
876
     * 1. Using a timestamp that is relative to `now`.
877
     * 2. Using a timestamp that is relative to the `$referenceTime`.
878
     * 3. Using a `DateInterval` object.
879
     *
880
     * @param int|string|DateTime|DateInterval $value the value to be formatted. The following
881
     * types of value are supported:
882
     *
883
     * - an integer representing a UNIX timestamp
884
     * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
885
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
886
     * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object
887
     * - a PHP DateInterval object (a positive time interval will refer to the past, a negative one to the future)
888
     *
889
     * @param int|string|DateTime $referenceTime if specified the value is used as a reference time instead of `now`
890
     * when `$value` is not a `DateInterval` object.
891
     * @return string the formatted result.
892
     * @throws InvalidArgumentException if the input value can not be evaluated as a date value.
893
     */
894 92
    public function asRelativeTime($value, $referenceTime = null)
895
    {
896 92
        if ($value === null) {
897 2
            return $this->nullDisplay;
898
        }
899
900 92
        if ($value instanceof DateInterval) {
901 2
            $interval = $value;
902
        } else {
903 92
            $timestamp = $this->normalizeDatetimeValue($value);
904
905 92
            if ($timestamp === false) {
906
                // $value is not a valid date/time value, so we try
907
                // to create a DateInterval with it
908
                try {
909
                    $interval = new DateInterval($value);
910
                } catch (\Exception $e) {
911
                    // invalid date/time and invalid interval
912
                    return $this->nullDisplay;
913
                }
914
            } else {
915 92
                $timeZone = new DateTimeZone($this->timeZone);
916
917 92
                if ($referenceTime === null) {
918
                    $dateNow = new DateTime('now', $timeZone);
919
                } else {
920 92
                    $dateNow = $this->normalizeDatetimeValue($referenceTime);
921 92
                    $dateNow->setTimezone($timeZone);
922
                }
923
924 92
                $dateThen = $timestamp->setTimezone($timeZone);
925
926 92
                $interval = $dateThen->diff($dateNow);
927
            }
928
        }
929
930 92
        if ($interval->invert) {
931 92
            if ($interval->y >= 1) {
932 2
                return Yii::t('yii', 'in {delta, plural, =1{a year} other{# years}}', ['delta' => $interval->y], $this->locale);
933
            }
934 92
            if ($interval->m >= 1) {
935 2
                return Yii::t('yii', 'in {delta, plural, =1{a month} other{# months}}', ['delta' => $interval->m], $this->locale);
936
            }
937 92
            if ($interval->d >= 1) {
938 2
                return Yii::t('yii', 'in {delta, plural, =1{a day} other{# days}}', ['delta' => $interval->d], $this->locale);
939
            }
940 92
            if ($interval->h >= 1) {
941 92
                return Yii::t('yii', 'in {delta, plural, =1{an hour} other{# hours}}', ['delta' => $interval->h], $this->locale);
942
            }
943 2
            if ($interval->i >= 1) {
944 2
                return Yii::t('yii', 'in {delta, plural, =1{a minute} other{# minutes}}', ['delta' => $interval->i], $this->locale);
945
            }
946 2
            if ($interval->s == 0) {
947
                return Yii::t('yii', 'just now', [], $this->locale);
948
            }
949
950 2
            return Yii::t('yii', 'in {delta, plural, =1{a second} other{# seconds}}', ['delta' => $interval->s], $this->locale);
951
        }
952
953 92
        if ($interval->y >= 1) {
954 2
            return Yii::t('yii', '{delta, plural, =1{a year} other{# years}} ago', ['delta' => $interval->y], $this->locale);
955
        }
956 92
        if ($interval->m >= 1) {
957 2
            return Yii::t('yii', '{delta, plural, =1{a month} other{# months}} ago', ['delta' => $interval->m], $this->locale);
958
        }
959 92
        if ($interval->d >= 1) {
960 2
            return Yii::t('yii', '{delta, plural, =1{a day} other{# days}} ago', ['delta' => $interval->d], $this->locale);
961
        }
962 92
        if ($interval->h >= 1) {
963 92
            return Yii::t('yii', '{delta, plural, =1{an hour} other{# hours}} ago', ['delta' => $interval->h], $this->locale);
964
        }
965 2
        if ($interval->i >= 1) {
966 2
            return Yii::t('yii', '{delta, plural, =1{a minute} other{# minutes}} ago', ['delta' => $interval->i], $this->locale);
967
        }
968 2
        if ($interval->s == 0) {
969 2
            return Yii::t('yii', 'just now', [], $this->locale);
970
        }
971
972 2
        return Yii::t('yii', '{delta, plural, =1{a second} other{# seconds}} ago', ['delta' => $interval->s], $this->locale);
973
    }
974
975
    /**
976
     * Represents the value as duration in human readable format.
977
     *
978
     * @param DateInterval|string|int $value the value to be formatted. Acceptable formats:
979
     *  - [DateInterval object](http://php.net/manual/ru/class.dateinterval.php)
980
     *  - integer - number of seconds. For example: value `131` represents `2 minutes, 11 seconds`
981
     *  - ISO8601 duration format. For example, all of these values represent `1 day, 2 hours, 30 minutes` duration:
982
     *    `2015-01-01T13:00:00Z/2015-01-02T13:30:00Z` - between two datetime values
983
     *    `2015-01-01T13:00:00Z/P1D2H30M` - time interval after datetime value
984
     *    `P1D2H30M/2015-01-02T13:30:00Z` - time interval before datetime value
985
     *    `P1D2H30M` - simply a date interval
986
     *    `P-1D2H30M` - a negative date interval (`-1 day, 2 hours, 30 minutes`)
987
     *
988
     * @param string $implodeString will be used to concatenate duration parts. Defaults to `, `.
989
     * @param string $negativeSign will be prefixed to the formatted duration, when it is negative. Defaults to `-`.
990
     * @return string the formatted duration.
991
     * @since 2.0.7
992
     */
993 2
    public function asDuration($value, $implodeString = ', ', $negativeSign = '-')
994
    {
995 2
        if ($value === null) {
996 2
            return $this->nullDisplay;
997
        }
998
999 2
        if ($value instanceof DateInterval) {
1000 2
            $isNegative = $value->invert;
1001 2
            $interval = $value;
1002 2
        } elseif (is_numeric($value)) {
1003 2
            $isNegative = $value < 0;
1004 2
            $zeroDateTime = (new DateTime())->setTimestamp(0);
1005 2
            $valueDateTime = (new DateTime())->setTimestamp(abs($value));
1006 2
            $interval = $valueDateTime->diff($zeroDateTime);
1007 2
        } elseif (strpos($value, 'P-') === 0) {
1008 2
            $interval = new DateInterval('P' . substr($value, 2));
1009 2
            $isNegative = true;
1010
        } else {
1011 2
            $interval = new DateInterval($value);
1012 2
            $isNegative = $interval->invert;
1013
        }
1014
1015 2
        if ($interval->y > 0) {
1016 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 year} other{# years}}', ['delta' => $interval->y], $this->locale);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$parts was never initialized. Although not strictly required by PHP, it is generally a good practice to add $parts = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1017
        }
1018 2
        if ($interval->m > 0) {
1019 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 month} other{# months}}', ['delta' => $interval->m], $this->locale);
0 ignored issues
show
Bug introduced by
The variable $parts does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1020
        }
1021 2
        if ($interval->d > 0) {
1022 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 day} other{# days}}', ['delta' => $interval->d], $this->locale);
1023
        }
1024 2
        if ($interval->h > 0) {
1025 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 hour} other{# hours}}', ['delta' => $interval->h], $this->locale);
1026
        }
1027 2
        if ($interval->i > 0) {
1028 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 minute} other{# minutes}}', ['delta' => $interval->i], $this->locale);
1029
        }
1030 2
        if ($interval->s > 0) {
1031 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 second} other{# seconds}}', ['delta' => $interval->s], $this->locale);
1032
        }
1033 2
        if ($interval->s === 0 && empty($parts)) {
1034 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 second} other{# seconds}}', ['delta' => $interval->s], $this->locale);
1035 2
            $isNegative = false;
1036
        }
1037
1038 2
        return empty($parts) ? $this->nullDisplay : (($isNegative ? $negativeSign : '') . implode($implodeString, $parts));
1039
    }
1040
1041
1042
    // number formats
1043
1044
1045
    /**
1046
     * Formats the value as an integer number by removing any decimal digits without rounding.
1047
     *
1048
     * @param mixed $value the value to be formatted.
1049
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1050
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1051
     * @return string the formatted result.
1052
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1053
     */
1054 7
    public function asInteger($value, $options = [], $textOptions = [])
1055
    {
1056 7
        if ($value === null) {
1057 5
            return $this->nullDisplay;
1058
        }
1059 7
        $value = $this->normalizeNumericValue($value);
1060 5
        if ($this->_intlLoaded) {
1061 4
            $f = $this->createNumberFormatter(NumberFormatter::DECIMAL, null, $options, $textOptions);
1062 4
            $f->setAttribute(NumberFormatter::FRACTION_DIGITS, 0);
1063 4
            if (($result = $f->format($value, NumberFormatter::TYPE_INT64)) === false) {
1064
                throw new InvalidArgumentException('Formatting integer value failed: ' . $f->getErrorCode() . ' '
1065
                    . $f->getErrorMessage());
1066
            }
1067
1068 4
            return $result;
1069
        }
1070
1071 1
        return number_format((int) $value, 0, $this->decimalSeparator, $this->thousandSeparator);
1072
    }
1073
1074
    /**
1075
     * Formats the value as a decimal number.
1076
     *
1077
     * Property [[decimalSeparator]] will be used to represent the decimal point. The
1078
     * value is rounded automatically to the defined decimal digits.
1079
     *
1080
     * @param mixed $value the value to be formatted.
1081
     * @param int $decimals the number of digits after the decimal point.
1082
     * If not given, the number of digits depends in the input value and is determined based on
1083
     * `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
1084
     * using [[$numberFormatterOptions]].
1085
     * If the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value is `2`.
1086
     * If you want consistent behavior between environments where intl is available and not, you should explicitly
1087
     * specify a value here.
1088
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1089
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1090
     * @return string the formatted result.
1091
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1092
     * @see decimalSeparator
1093
     * @see thousandSeparator
1094
     */
1095 14
    public function asDecimal($value, $decimals = null, $options = [], $textOptions = [])
1096
    {
1097 14
        if ($value === null) {
1098 2
            return $this->nullDisplay;
1099
        }
1100 14
        $value = $this->normalizeNumericValue($value);
1101
1102 14
        if ($this->_intlLoaded) {
1103 6
            $f = $this->createNumberFormatter(NumberFormatter::DECIMAL, $decimals, $options, $textOptions);
1104 6
            if (($result = $f->format($value)) === false) {
1105
                throw new InvalidArgumentException('Formatting decimal value failed: ' . $f->getErrorCode() . ' '
1106
                    . $f->getErrorMessage());
1107
            }
1108
1109 6
            return $result;
1110
        }
1111
1112 8
        if ($decimals === null) {
1113 6
            $decimals = 2;
1114
        }
1115
1116 8
        return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator);
1117
    }
1118
1119
1120
    /**
1121
     * Formats the value as a percent number with "%" sign.
1122
     *
1123
     * @param mixed $value the value to be formatted. It must be a factor e.g. `0.75` will result in `75%`.
1124
     * @param int $decimals the number of digits after the decimal point.
1125
     * If not given, the number of digits depends in the input value and is determined based on
1126
     * `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
1127
     * using [[$numberFormatterOptions]].
1128
     * If the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value is `0`.
1129
     * If you want consistent behavior between environments where intl is available and not, you should explicitly
1130
     * specify a value here.
1131
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1132
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1133
     * @return string the formatted result.
1134
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1135
     */
1136 2
    public function asPercent($value, $decimals = null, $options = [], $textOptions = [])
1137
    {
1138 2
        if ($value === null) {
1139 2
            return $this->nullDisplay;
1140
        }
1141 2
        $value = $this->normalizeNumericValue($value);
1142
1143 2
        if ($this->_intlLoaded) {
1144 1
            $f = $this->createNumberFormatter(NumberFormatter::PERCENT, $decimals, $options, $textOptions);
1145 1
            if (($result = $f->format($value)) === false) {
1146
                throw new InvalidArgumentException('Formatting percent value failed: ' . $f->getErrorCode() . ' '
1147
                    . $f->getErrorMessage());
1148
            }
1149
1150 1
            return $result;
1151
        }
1152
1153 1
        if ($decimals === null) {
1154 1
            $decimals = 0;
1155
        }
1156
1157 1
        $value *= 100;
1158 1
        return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator) . '%';
1159
    }
1160
1161
    /**
1162
     * Formats the value as a scientific number.
1163
     *
1164
     * @param mixed $value the value to be formatted.
1165
     * @param int $decimals the number of digits after the decimal point.
1166
     * If not given, the number of digits depends in the input value and is determined based on
1167
     * `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
1168
     * using [[$numberFormatterOptions]].
1169
     * If the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value depends on your PHP configuration.
1170
     * If you want consistent behavior between environments where intl is available and not, you should explicitly
1171
     * specify a value here.
1172
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1173
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1174
     * @return string the formatted result.
1175
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1176
     */
1177 2
    public function asScientific($value, $decimals = null, $options = [], $textOptions = [])
1178
    {
1179 2
        if ($value === null) {
1180 2
            return $this->nullDisplay;
1181
        }
1182 2
        $value = $this->normalizeNumericValue($value);
1183
1184 2
        if ($this->_intlLoaded) {
1185 1
            $f = $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $decimals, $options, $textOptions);
1186 1
            if (($result = $f->format($value)) === false) {
1187
                throw new InvalidArgumentException('Formatting scientific number value failed: ' . $f->getErrorCode()
1188
                    . ' ' . $f->getErrorMessage());
1189
            }
1190
1191 1
            return $result;
1192
        }
1193
1194 1
        if ($decimals !== null) {
1195 1
            return sprintf("%.{$decimals}E", $value);
1196
        }
1197
1198 1
        return sprintf('%.E', $value);
1199
    }
1200
1201
    /**
1202
     * Formats the value as a currency number.
1203
     *
1204
     * This function does not require the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed
1205
     * to work, but it is highly recommended to install it to get good formatting results.
1206
     *
1207
     * @param mixed $value the value to be formatted.
1208
     * @param string $currency the 3-letter ISO 4217 currency code indicating the currency to use.
1209
     * If null, [[currencyCode]] will be used.
1210
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1211
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1212
     * @return string the formatted result.
1213
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1214
     * @throws InvalidConfigException if no currency is given and [[currencyCode]] is not defined.
1215
     */
1216 4
    public function asCurrency($value, $currency = null, $options = [], $textOptions = [])
1217
    {
1218 4
        if ($value === null) {
1219 2
            return $this->nullDisplay;
1220
        }
1221 4
        $value = $this->normalizeNumericValue($value);
1222
1223 4
        if ($this->_intlLoaded) {
1224 3
            $currency = $currency ?: $this->currencyCode;
1225
            // currency code must be set before fraction digits
1226
            // http://php.net/manual/en/numberformatter.formatcurrency.php#114376
1227 3
            if ($currency && !isset($textOptions[NumberFormatter::CURRENCY_CODE])) {
1228 3
                $textOptions[NumberFormatter::CURRENCY_CODE] = $currency;
1229
            }
1230 3
            $formatter = $this->createNumberFormatter(NumberFormatter::CURRENCY, null, $options, $textOptions);
1231 3
            if ($currency === null) {
1232 2
                $result = $formatter->format($value);
1233
            } else {
1234 3
                $result = $formatter->formatCurrency($value, $currency);
1235
            }
1236 3
            if ($result === false) {
1237
                throw new InvalidArgumentException('Formatting currency value failed: ' . $formatter->getErrorCode() . ' ' . $formatter->getErrorMessage());
1238
            }
1239
1240 3
            return $result;
1241
        }
1242
1243 1
        if ($currency === null) {
1244 1
            if ($this->currencyCode === null) {
1245
                throw new InvalidConfigException('The default currency code for the formatter is not defined and the php intl extension is not installed which could take the default currency from the locale.');
1246
            }
1247 1
            $currency = $this->currencyCode;
1248
        }
1249
1250 1
        return $currency . ' ' . $this->asDecimal($value, 2, $options, $textOptions);
1251
    }
1252
1253
    /**
1254
     * Formats the value as a number spellout.
1255
     *
1256
     * This function requires the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed.
1257
     *
1258
     * @param mixed $value the value to be formatted
1259
     * @return string the formatted result.
1260
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1261
     * @throws InvalidConfigException when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available.
1262
     */
1263 1
    public function asSpellout($value)
1264
    {
1265 1
        if ($value === null) {
1266 1
            return $this->nullDisplay;
1267
        }
1268 1
        $value = $this->normalizeNumericValue($value);
1269 1
        if ($this->_intlLoaded) {
1270 1
            $f = $this->createNumberFormatter(NumberFormatter::SPELLOUT);
1271 1
            if (($result = $f->format($value)) === false) {
1272
                throw new InvalidArgumentException('Formatting number as spellout failed: ' . $f->getErrorCode() . ' '
1273
                    . $f->getErrorMessage());
1274
            }
1275
1276 1
            return $result;
1277
        }
1278
1279
        throw new InvalidConfigException('Format as Spellout is only supported when PHP intl extension is installed.');
1280
    }
1281
1282
    /**
1283
     * Formats the value as a ordinal value of a number.
1284
     *
1285
     * This function requires the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed.
1286
     *
1287
     * @param mixed $value the value to be formatted
1288
     * @return string the formatted result.
1289
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1290
     * @throws InvalidConfigException when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available.
1291
     */
1292 2
    public function asOrdinal($value)
1293
    {
1294 2
        if ($value === null) {
1295 1
            return $this->nullDisplay;
1296
        }
1297 2
        $value = $this->normalizeNumericValue($value);
1298 2
        if ($this->_intlLoaded) {
1299 2
            $f = $this->createNumberFormatter(NumberFormatter::ORDINAL);
1300 2
            if (($result = $f->format($value)) === false) {
1301
                throw new InvalidArgumentException('Formatting number as ordinal failed: ' . $f->getErrorCode() . ' '
1302
                    . $f->getErrorMessage());
1303
            }
1304
1305 2
            return $result;
1306
        }
1307
1308
        throw new InvalidConfigException('Format as Ordinal is only supported when PHP intl extension is installed.');
1309
    }
1310
1311
    /**
1312
     * Formats the value in bytes as a size in human readable form for example `12 KB`.
1313
     *
1314
     * This is the short form of [[asSize]].
1315
     *
1316
     * If [[sizeFormatBase]] is 1024, [binary prefixes](http://en.wikipedia.org/wiki/Binary_prefix) (e.g. kibibyte/KiB, mebibyte/MiB, ...)
1317
     * are used in the formatting result.
1318
     *
1319
     * @param string|int|float $value value in bytes to be formatted.
1320
     * @param int $decimals the number of digits after the decimal point.
1321
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1322
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1323
     * @return string the formatted result.
1324
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1325
     * @see sizeFormatBase
1326
     * @see asSize
1327
     */
1328 5
    public function asShortSize($value, $decimals = null, $options = [], $textOptions = [])
1329
    {
1330 5
        if ($value === null) {
1331 2
            return $this->nullDisplay;
1332
        }
1333
1334 5
        [$params, $position] = $this->formatNumber($value, $decimals, 4, $this->sizeFormatBase, $options, $textOptions);
0 ignored issues
show
Bug introduced by
The variable $params does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $position does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
1335
1336 5
        if ($this->sizeFormatBase == 1024) {
1337
            switch ($position) {
1338 5
                case 0:
1339 5
                    return Yii::t('yii', '{nFormatted} B', $params, $this->locale);
1340 3
                case 1:
1341 3
                    return Yii::t('yii', '{nFormatted} KiB', $params, $this->locale);
1342 3
                case 2:
1343 3
                    return Yii::t('yii', '{nFormatted} MiB', $params, $this->locale);
1344 2
                case 3:
1345 2
                    return Yii::t('yii', '{nFormatted} GiB', $params, $this->locale);
1346 2
                case 4:
1347
                    return Yii::t('yii', '{nFormatted} TiB', $params, $this->locale);
1348
                default:
1349 2
                    return Yii::t('yii', '{nFormatted} PiB', $params, $this->locale);
1350
            }
1351
        } else {
1352
            switch ($position) {
1353 2
                case 0:
1354 2
                    return Yii::t('yii', '{nFormatted} B', $params, $this->locale);
1355 2
                case 1:
1356 2
                    return Yii::t('yii', '{nFormatted} KB', $params, $this->locale);
1357 2
                case 2:
1358 2
                    return Yii::t('yii', '{nFormatted} MB', $params, $this->locale);
1359 2
                case 3:
1360 2
                    return Yii::t('yii', '{nFormatted} GB', $params, $this->locale);
1361 2
                case 4:
1362
                    return Yii::t('yii', '{nFormatted} TB', $params, $this->locale);
1363
                default:
1364 2
                    return Yii::t('yii', '{nFormatted} PB', $params, $this->locale);
1365
            }
1366
        }
1367
    }
1368
1369
    /**
1370
     * Formats the value in bytes as a size in human readable form, for example `12 kilobytes`.
1371
     *
1372
     * If [[sizeFormatBase]] is 1024, [binary prefixes](http://en.wikipedia.org/wiki/Binary_prefix) (e.g. kibibyte/KiB, mebibyte/MiB, ...)
1373
     * are used in the formatting result.
1374
     *
1375
     * @param string|int|float $value value in bytes to be formatted.
1376
     * @param int $decimals the number of digits after the decimal point.
1377
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1378
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1379
     * @return string the formatted result.
1380
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1381
     * @see sizeFormatBase
1382
     * @see asShortSize
1383
     */
1384 6
    public function asSize($value, $decimals = null, $options = [], $textOptions = [])
1385
    {
1386 6
        if ($value === null) {
1387 2
            return $this->nullDisplay;
1388
        }
1389
1390 6
        [$params, $position] = $this->formatNumber($value, $decimals, 4, $this->sizeFormatBase, $options, $textOptions);
0 ignored issues
show
Bug introduced by
The variable $params does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $position does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
1391
1392 6
        if ($this->sizeFormatBase == 1024) {
1393
            switch ($position) {
1394 6
                case 0:
1395 6
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->locale);
1396 4
                case 1:
1397 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}', $params, $this->locale);
1398 4
                case 2:
1399 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}', $params, $this->locale);
1400 4
                case 3:
1401 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}', $params, $this->locale);
1402 4
                case 4:
1403
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}', $params, $this->locale);
1404
                default:
1405 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}', $params, $this->locale);
1406
            }
1407
        } else {
1408
            switch ($position) {
1409 4
                case 0:
1410 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->locale);
1411 4
                case 1:
1412 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}', $params, $this->locale);
1413 4
                case 2:
1414 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}', $params, $this->locale);
1415 4
                case 3:
1416 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}', $params, $this->locale);
1417 4
                case 4:
1418
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}', $params, $this->locale);
1419
                default:
1420 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}', $params, $this->locale);
1421
            }
1422
        }
1423
    }
1424
1425
    /**
1426
     * Formats the value as a length in human readable form for example `12 meters`.
1427
     * Check properties [[baseUnits]] if you need to change unit of value as the multiplier
1428
     * of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
1429
     *
1430
     * @param float|int $value value to be formatted.
1431
     * @param int $decimals the number of digits after the decimal point.
1432
     * @param array $numberOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1433
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1434
     * @return string the formatted result.
1435
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1436
     * @throws InvalidConfigException when INTL is not installed or does not contain required information.
1437
     * @see asLength
1438
     * @since 2.0.13
1439
     * @author John Was <[email protected]>
1440
     */
1441
    public function asLength($value, $decimals = null, $numberOptions = [], $textOptions = [])
1442
    {
1443
        return $this->formatUnit(self::UNIT_LENGTH, self::FORMAT_WIDTH_LONG, $value, null, null, $decimals, $numberOptions, $textOptions);
1444
    }
1445
1446
    /**
1447
     * Formats the value as a length in human readable form for example `12 m`.
1448
     * This is the short form of [[asLength]].
1449
     *
1450
     * Check properties [[baseUnits]] if you need to change unit of value as the multiplier
1451
     * of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
1452
     *
1453
     * @param float|int $value value to be formatted.
1454
     * @param int $decimals the number of digits after the decimal point.
1455
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1456
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1457
     * @return string the formatted result.
1458
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1459
     * @throws InvalidConfigException when INTL is not installed or does not contain required information.
1460
     * @see asLength
1461
     * @since 2.0.13
1462
     * @author John Was <[email protected]>
1463
     */
1464 1
    public function asShortLength($value, $decimals = null, $options = [], $textOptions = [])
1465
    {
1466 1
        return $this->formatUnit(self::UNIT_LENGTH, self::FORMAT_WIDTH_SHORT, $value, null, null, $decimals, $options, $textOptions);
1467
    }
1468
1469
    /**
1470
     * Formats the value as a weight in human readable form for example `12 kilograms`.
1471
     * Check properties [[baseUnits]] if you need to change unit of value as the multiplier
1472
     * of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
1473
     *
1474
     * @param float|int $value value to be formatted.
1475
     * @param int $decimals the number of digits after the decimal point.
1476
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1477
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1478
     * @return string the formatted result.
1479
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1480
     * @throws InvalidConfigException when INTL is not installed or does not contain required information.
1481
     * @since 2.0.13
1482
     * @author John Was <[email protected]>
1483
     */
1484 1
    public function asWeight($value, $decimals = null, $options = [], $textOptions = [])
1485
    {
1486 1
        return $this->formatUnit(self::UNIT_WEIGHT, self::FORMAT_WIDTH_LONG, $value, null, null, $decimals, $options, $textOptions);
1487
    }
1488
1489
    /**
1490
     * Formats the value as a weight in human readable form for example `12 kg`.
1491
     * This is the short form of [[asWeight]].
1492
     *
1493
     * Check properties [[baseUnits]] if you need to change unit of value as the multiplier
1494
     * of the smallest unit and [[systemOfUnits]] to switch between [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]].
1495
     *
1496
     * @param float|int $value value to be formatted.
1497
     * @param int $decimals the number of digits after the decimal point.
1498
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1499
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1500
     * @return string the formatted result.
1501
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1502
     * @throws InvalidConfigException when INTL is not installed or does not contain required information.
1503
     * @since 2.0.13
1504
     * @author John Was <[email protected]>
1505
     */
1506
    public function asShortWeight($value, $decimals = null, $options = [], $textOptions = [])
1507
    {
1508
        return $this->formatUnit(self::UNIT_WEIGHT, self::FORMAT_WIDTH_SHORT, $value, null, null, $decimals, $options, $textOptions);
1509
    }
1510
1511
    /**
1512
     * @param string $unitType one of [[UNIT_WEIGHT]], [[UNIT_LENGTH]]
1513
     * @param string $unitFormat one of [[FORMAT_WIDTH_SHORT]], [[FORMAT_WIDTH_LONG]]
1514
     * @param float|int $value to be formatted
1515
     * @param float $baseUnit unit of value as the multiplier of the smallest unit. When `null`, property [[baseUnits]]
1516
     * will be used to determine base unit using $unitType and $unitSystem.
1517
     * @param string $unitSystem either [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]]. When `null`, property [[systemOfUnits]] will be used.
1518
     * @param int $decimals the number of digits after the decimal point.
1519
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1520
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1521
     * @return string
1522
     * @throws InvalidConfigException when INTL is not installed or does not contain required information
1523
     */
1524 2
    private function formatUnit($unitType, $unitFormat, $value, $baseUnit, $unitSystem, $decimals, $options, $textOptions)
1525
    {
1526 2
        if ($value === null) {
1527
            return $this->nullDisplay;
1528
        }
1529 2
        if ($unitSystem === null) {
1530 2
            $unitSystem = $this->systemOfUnits;
1531
        }
1532 2
        if ($baseUnit === null) {
1533 2
            $baseUnit = $this->baseUnits[$unitType][$unitSystem];
1534
        }
1535
1536 2
        $multipliers = array_values($this->measureUnits[$unitType][$unitSystem]);
1537
1538 2
        list($params, $position) = $this->formatNumber($value * $baseUnit, $decimals, null, $multipliers, $options, $textOptions);
1539
1540 2
        $message = $this->getUnitMessage($unitType, $unitFormat, $unitSystem, $position);
1541
1542
        return (new \MessageFormatter($this->locale, $message))->format([
1543
            '0' => $params['nFormatted'],
1544
            'n' => $params['n'],
1545
        ]);
1546
    }
1547
1548
    /**
1549
     * @param string $unitType one of [[UNIT_WEIGHT]], [[UNIT_LENGTH]]
1550
     * @param string $unitFormat one of [[FORMAT_WIDTH_SHORT]], [[FORMAT_WIDTH_LONG]]
1551
     * @param string $system either [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]]. When `null`, property [[systemOfUnits]] will be used.
1552
     * @param int $position internal position of size unit
1553
     * @return string
1554
     * @throws InvalidConfigException when INTL is not installed or does not contain required information
1555
     */
1556 2
    private function getUnitMessage($unitType, $unitFormat, $system, $position)
1557
    {
1558 2
        if (isset($this->_unitMessages[$unitType][$system][$position])) {
1559
            return $this->_unitMessages[$unitType][$system][$position];
1560
        }
1561 2
        if (!$this->_intlLoaded) {
1562 2
            throw new InvalidConfigException('Format of ' . $unitType . ' is only supported when PHP intl extension is installed.');
1563
        }
1564
1565
        if ($this->_resourceBundle === null) {
1566
            try {
1567
                $this->_resourceBundle = new \ResourceBundle($this->locale, 'ICUDATA-unit');
1568
            } catch (\IntlException $e) {
0 ignored issues
show
Bug introduced by
The class IntlException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
1569
                throw new InvalidConfigException('Current ICU data does not contain information about measure units. Check system requirements.');
1570
            }
1571
        }
1572
        $unitNames = array_keys($this->measureUnits[$unitType][$system]);
1573
        $bundleKey = 'units' . ($unitFormat === self::FORMAT_WIDTH_SHORT ? 'Short' : '');
1574
1575
        $unitBundle = $this->_resourceBundle[$bundleKey][$unitType][$unitNames[$position]];
1576
        if ($unitBundle === null) {
1577
            throw new InvalidConfigException('Current ICU data version does not contain information about unit type "' . $unitType . '" and unit measure "' . $unitNames[$position] . '". Check system requirements.');
1578
        }
1579
1580
        $message = [];
1581
        foreach ($unitBundle as $key => $value) {
1582
            if ($key === 'dnam') {
1583
                continue;
1584
            }
1585
            $message[] = "$key{{$value}}";
1586
        }
1587
1588
        return $this->_unitMessages[$unitType][$system][$position] = '{n, plural, ' . implode(' ', $message) . '}';
1589
    }
1590
1591
    /**
1592
     * Given the value in bytes formats number part of the human readable form.
1593
     *
1594
     * @param string|int|float $value value in bytes to be formatted.
1595
     * @param int $decimals the number of digits after the decimal point
1596
     * @param int $maxPosition maximum internal position of size unit, ignored if $formatBase is an array
1597
     * @param array|int $formatBase the base at which each next unit is calculated, either 1000 or 1024, or an array
1598
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1599
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1600
     * @return array [parameters for Yii::t containing formatted number, internal position of size unit]
1601
     * @throws InvalidArgumentException if the input value is not numeric or the formatting failed.
1602
     */
1603 11
    private function formatNumber($value, $decimals, $maxPosition, $formatBase, $options, $textOptions)
1604
    {
1605 11
        $value = $this->normalizeNumericValue($value);
1606
1607 11
        $position = 0;
1608 11
        if (is_array($formatBase)) {
1609 2
            $maxPosition = count($formatBase) - 1;
1610
        }
1611
        do {
1612 11
            if (is_array($formatBase)) {
1613 2
                if (!isset($formatBase[$position + 1])) {
1614
                    break;
1615
                }
1616
1617 2
                if (abs($value) < $formatBase[$position + 1]) {
1618 2
                    break;
1619
                }
1620
            } else {
1621 9
                if (abs($value) < $formatBase) {
1622 9
                    break;
1623
                }
1624 7
                $value = $value / $formatBase;
1625
            }
1626 9
            $position++;
1627 9
        } while ($position < $maxPosition + 1);
1628
1629 11
        if (is_array($formatBase) && $position !== 0) {
1630 2
            $value /= $formatBase[$position];
1631
        }
1632
1633
        // no decimals for smallest unit
1634 11
        if ($position === 0) {
1635 9
            $decimals = 0;
1636 9
        } elseif ($decimals !== null) {
1637 6
            $value = round($value, $decimals);
1638
        }
1639
        // disable grouping for edge cases like 1023 to get 1023 B instead of 1,023 B
1640 11
        $oldThousandSeparator = $this->thousandSeparator;
1641 11
        $this->thousandSeparator = '';
1642 11
        if ($this->_intlLoaded && !isset($options[NumberFormatter::GROUPING_USED])) {
1643 5
            $options[NumberFormatter::GROUPING_USED] = false;
1644
        }
1645
        // format the size value
1646
        $params = [
1647
            // this is the unformatted number used for the plural rule
1648
            // abs() to make sure the plural rules work correctly on negative numbers, intl does not cover this
1649
            // http://english.stackexchange.com/questions/9735/is-1-singular-or-plural
1650 11
            'n' => abs($value),
1651
            // this is the formatted number used for display
1652 11
            'nFormatted' => $this->asDecimal($value, $decimals, $options, $textOptions),
1653
        ];
1654 11
        $this->thousandSeparator = $oldThousandSeparator;
1655
1656 11
        return [$params, $position];
1657
    }
1658
1659
    /**
1660
     * Normalizes a numeric input value.
1661
     *
1662
     * - everything [empty](http://php.net/manual/en/function.empty.php) will result in `0`
1663
     * - a [numeric](http://php.net/manual/en/function.is-numeric.php) string will be casted to float
1664
     * - everything else will be returned if it is [numeric](http://php.net/manual/en/function.is-numeric.php),
1665
     *   otherwise an exception is thrown.
1666
     *
1667
     * @param mixed $value the input value
1668
     * @return float|int the normalized number value
1669
     * @throws InvalidArgumentException if the input value is not numeric.
1670
     */
1671 31
    protected function normalizeNumericValue($value)
1672
    {
1673 31
        if (empty($value)) {
1674 16
            return 0;
1675
        }
1676 31
        if (is_string($value) && is_numeric($value)) {
1677 16
            $value = (float) $value;
1678
        }
1679 31
        if (!is_numeric($value)) {
1680 2
            throw new InvalidArgumentException("'$value' is not a numeric value.");
1681
        }
1682
1683 29
        return $value;
1684
    }
1685
1686
    /**
1687
     * Creates a number formatter based on the given type and format.
1688
     *
1689
     * You may override this method to create a number formatter based on patterns.
1690
     *
1691
     * @param int $style the type of the number formatter.
1692
     * Values: NumberFormatter::DECIMAL, ::CURRENCY, ::PERCENT, ::SCIENTIFIC, ::SPELLOUT, ::ORDINAL
1693
     * ::DURATION, ::PATTERN_RULEBASED, ::DEFAULT_STYLE, ::IGNORE
1694
     * @param int $decimals the number of digits after the decimal point.
1695
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1696
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1697
     * @return NumberFormatter the created formatter instance
1698
     */
1699 18
    protected function createNumberFormatter($style, $decimals = null, $options = [], $textOptions = [])
1700
    {
1701 18
        $formatter = new NumberFormatter($this->locale, $style);
1702
1703
        // set text attributes
1704 18
        foreach ($this->numberFormatterTextOptions as $name => $attribute) {
1705 1
            $formatter->setTextAttribute($name, $attribute);
1706
        }
1707 18
        foreach ($textOptions as $name => $attribute) {
1708 3
            $formatter->setTextAttribute($name, $attribute);
1709
        }
1710
1711
        // set attributes
1712 18
        foreach ($this->numberFormatterOptions as $name => $value) {
1713 9
            $formatter->setAttribute($name, $value);
1714
        }
1715 18
        foreach ($options as $name => $value) {
1716 7
            $formatter->setAttribute($name, $value);
1717
        }
1718 18
        if ($decimals !== null) {
1719 6
            $formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimals);
1720 6
            $formatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimals);
1721
        }
1722
1723
        // set symbols
1724 18
        if ($this->decimalSeparator !== null) {
1725 4
            $formatter->setSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL, $this->decimalSeparator);
1726
        }
1727 18
        if ($this->thousandSeparator !== null) {
1728 7
            $formatter->setSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL, $this->thousandSeparator);
1729 7
            $formatter->setSymbol(NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL, $this->thousandSeparator);
1730
        }
1731 18
        foreach ($this->numberFormatterSymbols as $name => $symbol) {
1732 2
            $formatter->setSymbol($name, $symbol);
1733
        }
1734
1735 18
        return $formatter;
1736
    }
1737
}
1738