Completed
Push — master ( 3e253d...b9f582 )
by Dmitry
12:44
created

Formatter::init()   C

Complexity

Conditions 8
Paths 80

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 8

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 16
cts 16
cp 1
rs 5.7377
c 0
b 0
f 0
cc 8
eloc 15
nc 80
nop 0
crap 8
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\InvalidParamException;
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
     * @since 2.0.13
288
     */
289
    public $systemOfUnits = self::UNIT_SYSTEM_METRIC;
290
    /**
291
     * @var array configuration of weight and length measurement units.
292
     * This array contains the most usable measurement units, but you can change it
293
     * in case you have some special requirements.
294
     *
295
     * For example, you can add smaller measure unit:
296
     *
297
     * ```php
298
     * $this->measureUnits[self::UNIT_LENGTH][self::UNIT_SYSTEM_METRIC] = [
299
     *     'nanometer' => 0.000001
300
     * ]
301
     * ```
302
     * @since 2.0.13
303
     */
304
    public $measureUnits = [
305
        self::UNIT_LENGTH => [
306
            self::UNIT_SYSTEM_IMPERIAL => [
307
                'inch' => 1,
308
                'foot' => 12,
309
                'yard' => 36,
310
                'chain' => 792,
311
                'furlong' => 7920,
312
                'mile' => 63360,
313
            ],
314
            self::UNIT_SYSTEM_METRIC => [
315
                'millimeter' => 1,
316
                'centimeter' => 10,
317
                'meter' => 1000,
318
                'kilometer' => 1000000,
319
            ],
320
        ],
321
        self::UNIT_WEIGHT => [
322
            self::UNIT_SYSTEM_IMPERIAL => [
323
                'grain' => 1,
324
                'drachm' => 27.34375,
325
                'ounce' => 437.5,
326
                'pound' => 7000,
327
                'stone' => 98000,
328
                'quarter' => 196000,
329
                'hundredweight' => 784000,
330
                'ton' => 15680000,
331
            ],
332
            self::UNIT_SYSTEM_METRIC => [
333
                'gram' => 1,
334
                'kilogram' => 1000,
335
                'ton' => 1000000,
336
            ],
337
        ],
338
    ];
339
    /**
340
     * @var array The base units that are used as multipliers for smallest possible unit from [[measureUnits]]
341
     * @since 2.0.13
342
     */
343
    public $baseUnits = [
344
        self::UNIT_LENGTH => [
345
            self::UNIT_SYSTEM_IMPERIAL => 12, // 1 feet = 12 inches
346
            self::UNIT_SYSTEM_METRIC => 1000, // 1 meter = 1000 millimeters
347
        ],
348
        self::UNIT_WEIGHT => [
349
            self::UNIT_SYSTEM_IMPERIAL => 7000, // 1 pound = 7000 grains
350
            self::UNIT_SYSTEM_METRIC => 1000, // 1 kilogram = 1000 grams
351
        ],
352
    ];
353
    /**
354
     * @var bool whether the [PHP intl extension](http://php.net/manual/en/book.intl.php) is loaded.
355
     */
356
    private $_intlLoaded = false;
357
    /**
358
     * @var \ResourceBundle cached ResourceBundle object used to read unit translations
359
     */
360
    private $_resourceBundle;
361
    /**
362
     * @var array cached unit translation patterns
363
     */
364
    private $_unitMessages = [];
365
366
367
    /**
368
     * @inheritdoc
369
     */
370 249
    public function init()
371
    {
372 249
        if ($this->timeZone === null) {
373 249
            $this->timeZone = Yii::$app->timeZone;
374
        }
375 249
        if ($this->locale === null) {
376 30
            $this->locale = Yii::$app->language;
377
        }
378 249
        if ($this->booleanFormat === null) {
379 249
            $this->booleanFormat = [Yii::t('yii', 'No', [], $this->locale), Yii::t('yii', 'Yes', [], $this->locale)];
380
        }
381 249
        if ($this->nullDisplay === null) {
382 249
            $this->nullDisplay = '<span class="not-set">' . Yii::t('yii', '(not set)', [], $this->locale) . '</span>';
383
        }
384 249
        $this->_intlLoaded = extension_loaded('intl');
385 249
        if (!$this->_intlLoaded) {
386 117
            if ($this->decimalSeparator === null) {
387 117
                $this->decimalSeparator = '.';
388
            }
389 117
            if ($this->thousandSeparator === null) {
390 117
                $this->thousandSeparator = ',';
391
            }
392
        }
393 249
    }
394
395
    /**
396
     * Formats the value based on the given format type.
397
     * This method will call one of the "as" methods available in this class to do the formatting.
398
     * For type "xyz", the method "asXyz" will be used. For example, if the format is "html",
399
     * then [[asHtml()]] will be used. Format names are case insensitive.
400
     * @param mixed $value the value to be formatted.
401
     * @param string|array|Closure $format the format of the value, e.g., "html", "text" or an anonymous function
402
     * returning the formatted value.
403
     *
404
     * To specify additional parameters of the formatting method, you may use an array.
405
     * The first element of the array specifies the format name, while the rest of the elements will be used as the
406
     * parameters to the formatting method. For example, a format of `['date', 'Y-m-d']` will cause the invocation
407
     * of `asDate($value, 'Y-m-d')`.
408
     *
409
     * The anonymous function signature should be: `function($value, $formatter)`,
410
     * where `$value` is the value that should be formatted and `$formatter` is an instance of the Formatter class,
411
     * which can be used to call other formatting functions.
412
     * The possibility to use an anonymous function is available since version 2.0.13.
413
     * @return string the formatting result.
414
     * @throws InvalidParamException if the format type is not supported by this class.
415
     */
416 11
    public function format($value, $format)
417
    {
418 11
        if ($format instanceof Closure) {
419
            return call_user_func($format, $value, $this);
420 11
        } elseif (is_array($format)) {
421 7
            if (!isset($format[0])) {
422
                throw new InvalidParamException('The $format array must contain at least one element.');
423
            }
424 7
            $f = $format[0];
425 7
            $format[0] = $value;
426 7
            $params = $format;
427 7
            $format = $f;
428
        } else {
429 6
            $params = [$value];
430
        }
431 11
        $method = 'as' . $format;
432 11
        if ($this->hasMethod($method)) {
433 11
            return call_user_func_array([$this, $method], $params);
434
        }
435
436 2
        throw new InvalidParamException("Unknown format type: $format");
437
    }
438
439
440
    // simple formats
441
442
443
    /**
444
     * Formats the value as is without any formatting.
445
     * This method simply returns back the parameter without any format.
446
     * The only exception is a `null` value which will be formatted using [[nullDisplay]].
447
     * @param mixed $value the value to be formatted.
448
     * @return string the formatted result.
449
     */
450 1
    public function asRaw($value)
451
    {
452 1
        if ($value === null) {
453 1
            return $this->nullDisplay;
454
        }
455 1
        return $value;
456
    }
457
458
    /**
459
     * Formats the value as an HTML-encoded plain text.
460
     * @param string $value the value to be formatted.
461
     * @return string the formatted result.
462
     */
463 5
    public function asText($value)
464
    {
465 5
        if ($value === null) {
466 2
            return $this->nullDisplay;
467
        }
468 5
        return Html::encode($value);
469
    }
470
471
    /**
472
     * Formats the value as an HTML-encoded plain text with newlines converted into breaks.
473
     * @param string $value the value to be formatted.
474
     * @return string the formatted result.
475
     */
476 1
    public function asNtext($value)
477
    {
478 1
        if ($value === null) {
479 1
            return $this->nullDisplay;
480
        }
481 1
        return nl2br(Html::encode($value));
482
    }
483
484
    /**
485
     * Formats the value as HTML-encoded text paragraphs.
486
     * Each text paragraph is enclosed within a `<p>` tag.
487
     * One or multiple consecutive empty lines divide two paragraphs.
488
     * @param string $value the value to be formatted.
489
     * @return string the formatted result.
490
     */
491 1
    public function asParagraphs($value)
492
    {
493 1
        if ($value === null) {
494 1
            return $this->nullDisplay;
495
        }
496 1
        return str_replace('<p></p>', '', '<p>' . preg_replace('/\R{2,}/u', "</p>\n<p>", Html::encode($value)) . '</p>');
497
    }
498
499
    /**
500
     * Formats the value as HTML text.
501
     * The value will be purified using [[HtmlPurifier]] to avoid XSS attacks.
502
     * Use [[asRaw()]] if you do not want any purification of the value.
503
     * @param string $value the value to be formatted.
504
     * @param array|null $config the configuration for the HTMLPurifier class.
505
     * @return string the formatted result.
506
     */
507
    public function asHtml($value, $config = null)
508
    {
509
        if ($value === null) {
510
            return $this->nullDisplay;
511
        }
512
        return HtmlPurifier::process($value, $config);
513
    }
514
515
    /**
516
     * Formats the value as a mailto link.
517
     * @param string $value the value to be formatted.
518
     * @param array $options the tag options in terms of name-value pairs. See [[Html::mailto()]].
519
     * @return string the formatted result.
520
     */
521 1
    public function asEmail($value, $options = [])
522
    {
523 1
        if ($value === null) {
524 1
            return $this->nullDisplay;
525
        }
526 1
        return Html::mailto(Html::encode($value), $value, $options);
527
    }
528
529
    /**
530
     * Formats the value as an image tag.
531
     * @param mixed $value the value to be formatted.
532
     * @param array $options the tag options in terms of name-value pairs. See [[Html::img()]].
533
     * @return string the formatted result.
534
     */
535 1
    public function asImage($value, $options = [])
536
    {
537 1
        if ($value === null) {
538 1
            return $this->nullDisplay;
539
        }
540 1
        return Html::img($value, $options);
541
    }
542
543
    /**
544
     * Formats the value as a hyperlink.
545
     * @param mixed $value the value to be formatted.
546
     * @param array $options the tag options in terms of name-value pairs. See [[Html::a()]].
547
     * @return string the formatted result.
548
     */
549 1
    public function asUrl($value, $options = [])
550
    {
551 1
        if ($value === null) {
552 1
            return $this->nullDisplay;
553
        }
554 1
        $url = $value;
555 1
        if (strpos($url, '://') === false) {
556 1
            $url = 'http://' . $url;
557
        }
558
559 1
        return Html::a(Html::encode($value), $url, $options);
560
    }
561
562
    /**
563
     * Formats the value as a boolean.
564
     * @param mixed $value the value to be formatted.
565
     * @return string the formatted result.
566
     * @see booleanFormat
567
     */
568 1
    public function asBoolean($value)
569
    {
570 1
        if ($value === null) {
571 1
            return $this->nullDisplay;
572
        }
573
574 1
        return $value ? $this->booleanFormat[1] : $this->booleanFormat[0];
575
    }
576
577
578
    // date and time formats
579
580
581
    /**
582
     * Formats the value as a date.
583
     * @param int|string|DateTime $value the value to be formatted. The following
584
     * types of value are supported:
585
     *
586
     * - an integer representing a UNIX timestamp. A UNIX timestamp is always in UTC by its definition.
587
     * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
588
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
589
     * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object. You may set the time zone
590
     *   for the DateTime object to specify the source time zone.
591
     *
592
     * The formatter will convert date values according to [[timeZone]] before formatting it.
593
     * If no timezone conversion should be performed, you need to set [[defaultTimeZone]] and [[timeZone]] to the same value.
594
     * Also no conversion will be performed on values that have no time information, e.g. `"2017-06-05"`.
595
     *
596
     * @param string $format the format used to convert the value into a date string.
597
     * If null, [[dateFormat]] will be used.
598
     *
599
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
600
     * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
601
     *
602
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
603
     * PHP [date()](http://php.net/manual/en/function.date.php)-function.
604
     *
605
     * @return string the formatted result.
606
     * @throws InvalidParamException if the input value can not be evaluated as a date value.
607
     * @throws InvalidConfigException if the date format is invalid.
608
     * @see dateFormat
609
     */
610 168
    public function asDate($value, $format = null)
611
    {
612 168
        if ($format === null) {
613 146
            $format = $this->dateFormat;
614
        }
615 168
        return $this->formatDateTimeValue($value, $format, 'date');
616
    }
617
618
    /**
619
     * Formats the value as a time.
620
     * @param int|string|DateTime $value the value to be formatted. The following
621
     * types of value are supported:
622
     *
623
     * - an integer representing a UNIX timestamp. A UNIX timestamp is always in UTC by its definition.
624
     * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
625
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
626
     * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object. You may set the time zone
627
     *   for the DateTime object to specify the source time zone.
628
     *
629
     * The formatter will convert date values according to [[timeZone]] before formatting it.
630
     * If no timezone conversion should be performed, you need to set [[defaultTimeZone]] and [[timeZone]] to the same value.
631
     *
632
     * @param string $format the format used to convert the value into a date string.
633
     * If null, [[timeFormat]] will be used.
634
     *
635
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
636
     * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
637
     *
638
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
639
     * PHP [date()](http://php.net/manual/en/function.date.php)-function.
640
     *
641
     * @return string the formatted result.
642
     * @throws InvalidParamException if the input value can not be evaluated as a date value.
643
     * @throws InvalidConfigException if the date format is invalid.
644
     * @see timeFormat
645
     */
646 148
    public function asTime($value, $format = null)
647
    {
648 148
        if ($format === null) {
649 144
            $format = $this->timeFormat;
650
        }
651 148
        return $this->formatDateTimeValue($value, $format, 'time');
652
    }
653
654
    /**
655
     * Formats the value as a datetime.
656
     * @param int|string|DateTime $value the value to be formatted. The following
657
     * types of value are supported:
658
     *
659
     * - an integer representing a UNIX timestamp. A UNIX timestamp is always in UTC by its definition.
660
     * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
661
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
662
     * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object. You may set the time zone
663
     *   for the DateTime object to specify the source time zone.
664
     *
665
     * The formatter will convert date values according to [[timeZone]] before formatting it.
666
     * If no timezone conversion should be performed, you need to set [[defaultTimeZone]] and [[timeZone]] to the same value.
667
     *
668
     * @param string $format the format used to convert the value into a date string.
669
     * If null, [[dateFormat]] will be used.
670
     *
671
     * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths.
672
     * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime).
673
     *
674
     * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the
675
     * PHP [date()](http://php.net/manual/en/function.date.php)-function.
676
     *
677
     * @return string the formatted result.
678
     * @throws InvalidParamException if the input value can not be evaluated as a date value.
679
     * @throws InvalidConfigException if the date format is invalid.
680
     * @see datetimeFormat
681
     */
682 152
    public function asDatetime($value, $format = null)
683
    {
684 152
        if ($format === null) {
685 144
            $format = $this->datetimeFormat;
686
        }
687 152
        return $this->formatDateTimeValue($value, $format, 'datetime');
688
    }
689
690
    /**
691
     * @var array map of short format names to IntlDateFormatter constant values.
692
     */
693
    private $_dateFormats = [
694
        'short' => 3, // IntlDateFormatter::SHORT,
695
        'medium' => 2, // IntlDateFormatter::MEDIUM,
696
        'long' => 1, // IntlDateFormatter::LONG,
697
        'full' => 0, // IntlDateFormatter::FULL,
698
    ];
699
700
    /**
701
     * @param int|string|DateTime $value the value to be formatted. The following
702
     * types of value are supported:
703
     *
704
     * - an integer representing a UNIX timestamp
705
     * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
706
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
707
     * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object
708
     *
709
     * @param string $format the format used to convert the value into a date string.
710
     * @param string $type 'date', 'time', or 'datetime'.
711
     * @throws InvalidConfigException if the date format is invalid.
712
     * @return string the formatted result.
713
     */
714 176
    private function formatDateTimeValue($value, $format, $type)
715
    {
716 176
        $timeZone = $this->timeZone;
717
        // avoid time zone conversion for date-only and time-only values
718 176
        if ($type === 'date' || $type === 'time') {
719 170
            list($timestamp, $hasTimeInfo, $hasDateInfo) = $this->normalizeDatetimeValue($value, true);
720 168
            if ($type === 'date' && !$hasTimeInfo || $type === 'time' && !$hasDateInfo) {
721 168
                $timeZone = $this->defaultTimeZone;
722
            }
723
        } else {
724 152
            $timestamp = $this->normalizeDatetimeValue($value);
725
        }
726 174
        if ($timestamp === null) {
727 6
            return $this->nullDisplay;
728
        }
729
730
        // intl does not work with dates >=2038 or <=1901 on 32bit machines, fall back to PHP
731 174
        $year = $timestamp->format('Y');
732 174
        if ($this->_intlLoaded && !(PHP_INT_SIZE === 4 && ($year <= 1901 || $year >= 2038))) {
733 85
            if (strncmp($format, 'php:', 4) === 0) {
734 6
                $format = FormatConverter::convertDatePhpToIcu(substr($format, 4));
735
            }
736 85
            if (isset($this->_dateFormats[$format])) {
737 3
                if ($type === 'date') {
738 1
                    $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], IntlDateFormatter::NONE, $timeZone, $this->calendar);
739 2
                } elseif ($type === 'time') {
740 1
                    $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, $this->_dateFormats[$format], $timeZone, $this->calendar);
741
                } else {
742 3
                    $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], $this->_dateFormats[$format], $timeZone, $this->calendar);
743
                }
744
            } else {
745 85
                $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE, $timeZone, $this->calendar, $format);
746
            }
747 85
            if ($formatter === null) {
748
                throw new InvalidConfigException(intl_get_error_message());
749
            }
750
            // make IntlDateFormatter work with DateTimeImmutable
751 85
            if ($timestamp instanceof \DateTimeImmutable) {
0 ignored issues
show
Bug introduced by
The class DateTimeImmutable does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
752 14
                $timestamp = new DateTime($timestamp->format(DateTime::ISO8601), $timestamp->getTimezone());
753
            }
754 85
            return $formatter->format($timestamp);
755
        }
756
757 89
        if (strncmp($format, 'php:', 4) === 0) {
758 11
            $format = substr($format, 4);
759
        } else {
760 84
            $format = FormatConverter::convertDateIcuToPhp($format, $type, $this->locale);
761
        }
762 89
        if ($timeZone != null) {
763 89
            if ($timestamp instanceof \DateTimeImmutable) {
0 ignored issues
show
Bug introduced by
The class DateTimeImmutable does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
764 13
                $timestamp = $timestamp->setTimezone(new DateTimeZone($timeZone));
765
            } else {
766 79
                $timestamp->setTimezone(new DateTimeZone($timeZone));
767
            }
768
        }
769
770 89
        return $timestamp->format($format);
771
    }
772
773
    /**
774
     * Normalizes the given datetime value as a DateTime object that can be taken by various date/time formatting methods.
775
     *
776
     * @param int|string|DateTime $value the datetime value to be normalized. The following
777
     * types of value are supported:
778
     *
779
     * - an integer representing a UNIX timestamp
780
     * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
781
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
782
     * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object
783
     *
784
     * @param bool $checkDateTimeInfo whether to also check if the date/time value has some time and date information attached.
785
     * Defaults to `false`. If `true`, the method will then return an array with the first element being the normalized
786
     * timestamp, the second a boolean indicating whether the timestamp has time information and third a boolean indicating
787
     * whether the timestamp has date information.
788
     * This parameter is available since version 2.0.1.
789
     * @return DateTime|array the normalized datetime value.
790
     * Since version 2.0.1 this may also return an array if `$checkDateTimeInfo` is true.
791
     * The first element of the array is the normalized timestamp and the second is a boolean indicating whether
792
     * the timestamp has time information or it is just a date value.
793
     * Since version 2.0.12 the array has third boolean element indicating whether the timestamp has date information
794
     * or it is just a time value.
795
     * @throws InvalidParamException if the input value can not be evaluated as a date value.
796
     */
797 181
    protected function normalizeDatetimeValue($value, $checkDateTimeInfo = false)
798
    {
799
        // checking for DateTime and DateTimeInterface is not redundant, DateTimeInterface is only in PHP>5.5
800 181
        if ($value === null || $value instanceof DateTime || $value instanceof DateTimeInterface) {
0 ignored issues
show
Bug introduced by
The class DateTimeInterface does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
801
            // skip any processing
802 50
            return $checkDateTimeInfo ? [$value, true, true] : $value;
803
        }
804 141
        if (empty($value)) {
805 10
            $value = 0;
806
        }
807
        try {
808 141
            if (is_numeric($value)) { // process as unix timestamp, which is always in UTC
809 31
                $timestamp = new DateTime('@' . (int) $value, new DateTimeZone('UTC'));
810 31
                return $checkDateTimeInfo ? [$timestamp, true, true] : $timestamp;
811 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)
812 12
                return $checkDateTimeInfo ? [$timestamp, false, true] : $timestamp;
813 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)
814 19
                return $checkDateTimeInfo ? [$timestamp, true, true] : $timestamp;
815
            }
816
            // finally try to create a DateTime object with the value
817 88
            if ($checkDateTimeInfo) {
818 86
                $timestamp = new DateTime($value, new DateTimeZone($this->defaultTimeZone));
819 84
                $info = date_parse($value);
820
                return [
821 84
                    $timestamp,
822 84
                    !($info['hour'] === false && $info['minute'] === false && $info['second'] === false),
823 84
                    !($info['year'] === false && $info['month'] === false && $info['day'] === false),
824
                ];
825
            }
826
827 86
            return new DateTime($value, new DateTimeZone($this->defaultTimeZone));
828 2
        } catch (\Exception $e) {
829 2
            throw new InvalidParamException("'$value' is not a valid date time value: " . $e->getMessage()
830 2
                . "\n" . print_r(DateTime::getLastErrors(), true), $e->getCode(), $e);
831
        }
832
    }
833
834
    /**
835
     * Formats a date, time or datetime in a float number as UNIX timestamp (seconds since 01-01-1970).
836
     * @param int|string|DateTime $value the value to be formatted. The following
837
     * types of value are supported:
838
     *
839
     * - an integer representing a UNIX timestamp
840
     * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
841
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
842
     * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object
843
     *
844
     * @return string the formatted result.
845
     */
846 145
    public function asTimestamp($value)
847
    {
848 145
        if ($value === null) {
849 2
            return $this->nullDisplay;
850
        }
851 145
        $timestamp = $this->normalizeDatetimeValue($value);
852 145
        return number_format($timestamp->format('U'), 0, '.', '');
853
    }
854
855
    /**
856
     * Formats the value as the time interval between a date and now in human readable form.
857
     *
858
     * This method can be used in three different ways:
859
     *
860
     * 1. Using a timestamp that is relative to `now`.
861
     * 2. Using a timestamp that is relative to the `$referenceTime`.
862
     * 3. Using a `DateInterval` object.
863
     *
864
     * @param int|string|DateTime|DateInterval $value the value to be formatted. The following
865
     * types of value are supported:
866
     *
867
     * - an integer representing a UNIX timestamp
868
     * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php).
869
     *   The timestamp is assumed to be in [[defaultTimeZone]] unless a time zone is explicitly given.
870
     * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object
871
     * - a PHP DateInterval object (a positive time interval will refer to the past, a negative one to the future)
872
     *
873
     * @param int|string|DateTime $referenceTime if specified the value is used as a reference time instead of `now`
874
     * when `$value` is not a `DateInterval` object.
875
     * @return string the formatted result.
876
     * @throws InvalidParamException if the input value can not be evaluated as a date value.
877
     */
878 92
    public function asRelativeTime($value, $referenceTime = null)
879
    {
880 92
        if ($value === null) {
881 2
            return $this->nullDisplay;
882
        }
883
884 92
        if ($value instanceof DateInterval) {
885 2
            $interval = $value;
886
        } else {
887 92
            $timestamp = $this->normalizeDatetimeValue($value);
888
889 92
            if ($timestamp === false) {
890
                // $value is not a valid date/time value, so we try
891
                // to create a DateInterval with it
892
                try {
893
                    $interval = new DateInterval($value);
894
                } catch (\Exception $e) {
895
                    // invalid date/time and invalid interval
896
                    return $this->nullDisplay;
897
                }
898
            } else {
899 92
                $timeZone = new DateTimeZone($this->timeZone);
900
901 92
                if ($referenceTime === null) {
902
                    $dateNow = new DateTime('now', $timeZone);
903
                } else {
904 92
                    $dateNow = $this->normalizeDatetimeValue($referenceTime);
905 92
                    $dateNow->setTimezone($timeZone);
906
                }
907
908 92
                $dateThen = $timestamp->setTimezone($timeZone);
909
910 92
                $interval = $dateThen->diff($dateNow);
911
            }
912
        }
913
914 92
        if ($interval->invert) {
915 92
            if ($interval->y >= 1) {
916 2
                return Yii::t('yii', 'in {delta, plural, =1{a year} other{# years}}', ['delta' => $interval->y], $this->locale);
917
            }
918 92
            if ($interval->m >= 1) {
919 2
                return Yii::t('yii', 'in {delta, plural, =1{a month} other{# months}}', ['delta' => $interval->m], $this->locale);
920
            }
921 92
            if ($interval->d >= 1) {
922 2
                return Yii::t('yii', 'in {delta, plural, =1{a day} other{# days}}', ['delta' => $interval->d], $this->locale);
923
            }
924 92
            if ($interval->h >= 1) {
925 92
                return Yii::t('yii', 'in {delta, plural, =1{an hour} other{# hours}}', ['delta' => $interval->h], $this->locale);
926
            }
927 2
            if ($interval->i >= 1) {
928 2
                return Yii::t('yii', 'in {delta, plural, =1{a minute} other{# minutes}}', ['delta' => $interval->i], $this->locale);
929
            }
930 2
            if ($interval->s == 0) {
931
                return Yii::t('yii', 'just now', [], $this->locale);
932
            }
933
934 2
            return Yii::t('yii', 'in {delta, plural, =1{a second} other{# seconds}}', ['delta' => $interval->s], $this->locale);
935
        }
936
937 92
        if ($interval->y >= 1) {
938 2
            return Yii::t('yii', '{delta, plural, =1{a year} other{# years}} ago', ['delta' => $interval->y], $this->locale);
939
        }
940 92
        if ($interval->m >= 1) {
941 2
            return Yii::t('yii', '{delta, plural, =1{a month} other{# months}} ago', ['delta' => $interval->m], $this->locale);
942
        }
943 92
        if ($interval->d >= 1) {
944 2
            return Yii::t('yii', '{delta, plural, =1{a day} other{# days}} ago', ['delta' => $interval->d], $this->locale);
945
        }
946 92
        if ($interval->h >= 1) {
947 92
            return Yii::t('yii', '{delta, plural, =1{an hour} other{# hours}} ago', ['delta' => $interval->h], $this->locale);
948
        }
949 2
        if ($interval->i >= 1) {
950 2
            return Yii::t('yii', '{delta, plural, =1{a minute} other{# minutes}} ago', ['delta' => $interval->i], $this->locale);
951
        }
952 2
        if ($interval->s == 0) {
953 2
            return Yii::t('yii', 'just now', [], $this->locale);
954
        }
955
956 2
        return Yii::t('yii', '{delta, plural, =1{a second} other{# seconds}} ago', ['delta' => $interval->s], $this->locale);
957
    }
958
959
    /**
960
     * Represents the value as duration in human readable format.
961
     *
962
     * @param DateInterval|string|int $value the value to be formatted. Acceptable formats:
963
     *  - [DateInterval object](http://php.net/manual/ru/class.dateinterval.php)
964
     *  - integer - number of seconds. For example: value `131` represents `2 minutes, 11 seconds`
965
     *  - ISO8601 duration format. For example, all of these values represent `1 day, 2 hours, 30 minutes` duration:
966
     *    `2015-01-01T13:00:00Z/2015-01-02T13:30:00Z` - between two datetime values
967
     *    `2015-01-01T13:00:00Z/P1D2H30M` - time interval after datetime value
968
     *    `P1D2H30M/2015-01-02T13:30:00Z` - time interval before datetime value
969
     *    `P1D2H30M` - simply a date interval
970
     *    `P-1D2H30M` - a negative date interval (`-1 day, 2 hours, 30 minutes`)
971
     *
972
     * @param string $implodeString will be used to concatenate duration parts. Defaults to `, `.
973
     * @param string $negativeSign will be prefixed to the formatted duration, when it is negative. Defaults to `-`.
974
     * @return string the formatted duration.
975
     * @since 2.0.7
976
     */
977 2
    public function asDuration($value, $implodeString = ', ', $negativeSign = '-')
978
    {
979 2
        if ($value === null) {
980 2
            return $this->nullDisplay;
981
        }
982
983 2
        if ($value instanceof DateInterval) {
984 2
            $isNegative = $value->invert;
985 2
            $interval = $value;
986 2
        } elseif (is_numeric($value)) {
987 2
            $isNegative = $value < 0;
988 2
            $zeroDateTime = (new DateTime())->setTimestamp(0);
989 2
            $valueDateTime = (new DateTime())->setTimestamp(abs($value));
990 2
            $interval = $valueDateTime->diff($zeroDateTime);
991 2
        } elseif (strpos($value, 'P-') === 0) {
992 2
            $interval = new DateInterval('P' . substr($value, 2));
993 2
            $isNegative = true;
994
        } else {
995 2
            $interval = new DateInterval($value);
996 2
            $isNegative = $interval->invert;
997
        }
998
999 2
        if ($interval->y > 0) {
1000 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...
1001
        }
1002 2
        if ($interval->m > 0) {
1003 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...
1004
        }
1005 2
        if ($interval->d > 0) {
1006 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 day} other{# days}}', ['delta' => $interval->d], $this->locale);
1007
        }
1008 2
        if ($interval->h > 0) {
1009 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 hour} other{# hours}}', ['delta' => $interval->h], $this->locale);
1010
        }
1011 2
        if ($interval->i > 0) {
1012 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 minute} other{# minutes}}', ['delta' => $interval->i], $this->locale);
1013
        }
1014 2
        if ($interval->s > 0) {
1015 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 second} other{# seconds}}', ['delta' => $interval->s], $this->locale);
1016
        }
1017 2
        if ($interval->s === 0 && empty($parts)) {
1018 2
            $parts[] = Yii::t('yii', '{delta, plural, =1{1 second} other{# seconds}}', ['delta' => $interval->s], $this->locale);
1019 2
            $isNegative = false;
1020
        }
1021
1022 2
        return empty($parts) ? $this->nullDisplay : (($isNegative ? $negativeSign : '') . implode($implodeString, $parts));
1023
    }
1024
1025
1026
    // number formats
1027
1028
1029
    /**
1030
     * Formats the value as an integer number by removing any decimal digits without rounding.
1031
     *
1032
     * @param mixed $value the value to be formatted.
1033
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1034
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1035
     * @return string the formatted result.
1036
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1037
     */
1038 7
    public function asInteger($value, $options = [], $textOptions = [])
1039
    {
1040 7
        if ($value === null) {
1041 5
            return $this->nullDisplay;
1042
        }
1043 7
        $value = $this->normalizeNumericValue($value);
1044 5
        if ($this->_intlLoaded) {
1045 4
            $f = $this->createNumberFormatter(NumberFormatter::DECIMAL, null, $options, $textOptions);
1046 4
            $f->setAttribute(NumberFormatter::FRACTION_DIGITS, 0);
1047 4
            if (($result = $f->format($value, NumberFormatter::TYPE_INT64)) === false) {
1048
                throw new InvalidParamException('Formatting integer value failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1049
            }
1050
1051 4
            return $result;
1052
        }
1053
1054 1
        return number_format((int) $value, 0, $this->decimalSeparator, $this->thousandSeparator);
1055
    }
1056
1057
    /**
1058
     * Formats the value as a decimal number.
1059
     *
1060
     * Property [[decimalSeparator]] will be used to represent the decimal point. The
1061
     * value is rounded automatically to the defined decimal digits.
1062
     *
1063
     * @param mixed $value the value to be formatted.
1064
     * @param int $decimals the number of digits after the decimal point.
1065
     * If not given, the number of digits depends in the input value and is determined based on
1066
     * `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
1067
     * using [[$numberFormatterOptions]].
1068
     * If the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value is `2`.
1069
     * If you want consistent behavior between environments where intl is available and not, you should explicitly
1070
     * specify a value here.
1071
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1072
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1073
     * @return string the formatted result.
1074
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1075
     * @see decimalSeparator
1076
     * @see thousandSeparator
1077
     */
1078 14
    public function asDecimal($value, $decimals = null, $options = [], $textOptions = [])
1079
    {
1080 14
        if ($value === null) {
1081 2
            return $this->nullDisplay;
1082
        }
1083 14
        $value = $this->normalizeNumericValue($value);
1084
1085 14
        if ($this->_intlLoaded) {
1086 6
            $f = $this->createNumberFormatter(NumberFormatter::DECIMAL, $decimals, $options, $textOptions);
1087 6
            if (($result = $f->format($value)) === false) {
1088
                throw new InvalidParamException('Formatting decimal value failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1089
            }
1090
1091 6
            return $result;
1092
        }
1093
1094 8
        if ($decimals === null) {
1095 6
            $decimals = 2;
1096
        }
1097
1098 8
        return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator);
1099
    }
1100
1101
1102
    /**
1103
     * Formats the value as a percent number with "%" sign.
1104
     *
1105
     * @param mixed $value the value to be formatted. It must be a factor e.g. `0.75` will result in `75%`.
1106
     * @param int $decimals the number of digits after the decimal point.
1107
     * If not given, the number of digits depends in the input value and is determined based on
1108
     * `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
1109
     * using [[$numberFormatterOptions]].
1110
     * If the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value is `0`.
1111
     * If you want consistent behavior between environments where intl is available and not, you should explicitly
1112
     * specify a value here.
1113
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1114
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1115
     * @return string the formatted result.
1116
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1117
     */
1118 2
    public function asPercent($value, $decimals = null, $options = [], $textOptions = [])
1119
    {
1120 2
        if ($value === null) {
1121 2
            return $this->nullDisplay;
1122
        }
1123 2
        $value = $this->normalizeNumericValue($value);
1124
1125 2
        if ($this->_intlLoaded) {
1126 1
            $f = $this->createNumberFormatter(NumberFormatter::PERCENT, $decimals, $options, $textOptions);
1127 1
            if (($result = $f->format($value)) === false) {
1128
                throw new InvalidParamException('Formatting percent value failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1129
            }
1130 1
            return $result;
1131
        }
1132
1133 1
        if ($decimals === null) {
1134 1
            $decimals = 0;
1135
        }
1136
1137 1
        $value *= 100;
1138 1
        return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator) . '%';
1139
    }
1140
1141
    /**
1142
     * Formats the value as a scientific number.
1143
     *
1144
     * @param mixed $value the value to be formatted.
1145
     * @param int $decimals the number of digits after the decimal point.
1146
     * If not given, the number of digits depends in the input value and is determined based on
1147
     * `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
1148
     * using [[$numberFormatterOptions]].
1149
     * If the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value depends on your PHP configuration.
1150
     * If you want consistent behavior between environments where intl is available and not, you should explicitly
1151
     * specify a value here.
1152
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1153
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1154
     * @return string the formatted result.
1155
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1156
     */
1157 2
    public function asScientific($value, $decimals = null, $options = [], $textOptions = [])
1158
    {
1159 2
        if ($value === null) {
1160 2
            return $this->nullDisplay;
1161
        }
1162 2
        $value = $this->normalizeNumericValue($value);
1163
1164 2
        if ($this->_intlLoaded) {
1165 1
            $f = $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $decimals, $options, $textOptions);
1166 1
            if (($result = $f->format($value)) === false) {
1167
                throw new InvalidParamException('Formatting scientific number value failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1168
            }
1169
1170 1
            return $result;
1171
        }
1172
1173 1
        if ($decimals !== null) {
1174 1
            return sprintf("%.{$decimals}E", $value);
1175
        }
1176
1177 1
        return sprintf('%.E', $value);
1178
    }
1179
1180
    /**
1181
     * Formats the value as a currency number.
1182
     *
1183
     * This function does not require the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed
1184
     * to work, but it is highly recommended to install it to get good formatting results.
1185
     *
1186
     * @param mixed $value the value to be formatted.
1187
     * @param string $currency the 3-letter ISO 4217 currency code indicating the currency to use.
1188
     * If null, [[currencyCode]] will be used.
1189
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1190
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1191
     * @return string the formatted result.
1192
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1193
     * @throws InvalidConfigException if no currency is given and [[currencyCode]] is not defined.
1194
     */
1195 4
    public function asCurrency($value, $currency = null, $options = [], $textOptions = [])
1196
    {
1197 4
        if ($value === null) {
1198 2
            return $this->nullDisplay;
1199
        }
1200 4
        $value = $this->normalizeNumericValue($value);
1201
1202 4
        if ($this->_intlLoaded) {
1203 3
            $currency = $currency ?: $this->currencyCode;
1204
            // currency code must be set before fraction digits
1205
            // http://php.net/manual/en/numberformatter.formatcurrency.php#114376
1206 3
            if ($currency && !isset($textOptions[NumberFormatter::CURRENCY_CODE])) {
1207 3
                $textOptions[NumberFormatter::CURRENCY_CODE] = $currency;
1208
            }
1209 3
            $formatter = $this->createNumberFormatter(NumberFormatter::CURRENCY, null, $options, $textOptions);
1210 3
            if ($currency === null) {
1211 2
                $result = $formatter->format($value);
1212
            } else {
1213 3
                $result = $formatter->formatCurrency($value, $currency);
1214
            }
1215 3
            if ($result === false) {
1216
                throw new InvalidParamException('Formatting currency value failed: ' . $formatter->getErrorCode() . ' ' . $formatter->getErrorMessage());
1217
            }
1218
1219 3
            return $result;
1220
        }
1221
1222 1
        if ($currency === null) {
1223 1
            if ($this->currencyCode === null) {
1224
                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.');
1225
            }
1226 1
            $currency = $this->currencyCode;
1227
        }
1228
1229 1
        return $currency . ' ' . $this->asDecimal($value, 2, $options, $textOptions);
1230
    }
1231
1232
    /**
1233
     * Formats the value as a number spellout.
1234
     *
1235
     * This function requires the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed.
1236
     *
1237
     * @param mixed $value the value to be formatted
1238
     * @return string the formatted result.
1239
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1240
     * @throws InvalidConfigException when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available.
1241
     */
1242 1
    public function asSpellout($value)
1243
    {
1244 1
        if ($value === null) {
1245 1
            return $this->nullDisplay;
1246
        }
1247 1
        $value = $this->normalizeNumericValue($value);
1248 1
        if ($this->_intlLoaded) {
1249 1
            $f = $this->createNumberFormatter(NumberFormatter::SPELLOUT);
1250 1
            if (($result = $f->format($value)) === false) {
1251
                throw new InvalidParamException('Formatting number as spellout failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1252
            }
1253
1254 1
            return $result;
1255
        }
1256
1257
        throw new InvalidConfigException('Format as Spellout is only supported when PHP intl extension is installed.');
1258
    }
1259
1260
    /**
1261
     * Formats the value as a ordinal value of a number.
1262
     *
1263
     * This function requires the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed.
1264
     *
1265
     * @param mixed $value the value to be formatted
1266
     * @return string the formatted result.
1267
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1268
     * @throws InvalidConfigException when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available.
1269
     */
1270 2
    public function asOrdinal($value)
1271
    {
1272 2
        if ($value === null) {
1273 1
            return $this->nullDisplay;
1274
        }
1275 2
        $value = $this->normalizeNumericValue($value);
1276 2
        if ($this->_intlLoaded) {
1277 2
            $f = $this->createNumberFormatter(NumberFormatter::ORDINAL);
1278 2
            if (($result = $f->format($value)) === false) {
1279
                throw new InvalidParamException('Formatting number as ordinal failed: ' . $f->getErrorCode() . ' ' . $f->getErrorMessage());
1280
            }
1281
1282 2
            return $result;
1283
        }
1284
1285
        throw new InvalidConfigException('Format as Ordinal is only supported when PHP intl extension is installed.');
1286
    }
1287
1288
    /**
1289
     * Formats the value in bytes as a size in human readable form for example `12 KB`.
1290
     *
1291
     * This is the short form of [[asSize]].
1292
     *
1293
     * If [[sizeFormatBase]] is 1024, [binary prefixes](http://en.wikipedia.org/wiki/Binary_prefix) (e.g. kibibyte/KiB, mebibyte/MiB, ...)
1294
     * are used in the formatting result.
1295
     *
1296
     * @param string|int|float $value value in bytes to be formatted.
1297
     * @param int $decimals the number of digits after the decimal point.
1298
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1299
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1300
     * @return string the formatted result.
1301
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1302
     * @see sizeFormatBase
1303
     * @see asSize
1304
     */
1305 5
    public function asShortSize($value, $decimals = null, $options = [], $textOptions = [])
1306
    {
1307 5
        if ($value === null) {
1308 2
            return $this->nullDisplay;
1309
        }
1310
1311 5
        list($params, $position) = $this->formatNumber($value, $decimals, 4, $this->sizeFormatBase, $options, $textOptions);
1312
1313 5
        if ($this->sizeFormatBase == 1024) {
1314
            switch ($position) {
1315 5
                case 0:
1316 5
                    return Yii::t('yii', '{nFormatted} B', $params, $this->locale);
1317 3
                case 1:
1318 3
                    return Yii::t('yii', '{nFormatted} KiB', $params, $this->locale);
1319 3
                case 2:
1320 3
                    return Yii::t('yii', '{nFormatted} MiB', $params, $this->locale);
1321 2
                case 3:
1322 2
                    return Yii::t('yii', '{nFormatted} GiB', $params, $this->locale);
1323 2
                case 4:
1324
                    return Yii::t('yii', '{nFormatted} TiB', $params, $this->locale);
1325
                default:
1326 2
                    return Yii::t('yii', '{nFormatted} PiB', $params, $this->locale);
1327
            }
1328
        } else {
1329
            switch ($position) {
1330 2
                case 0:
1331 2
                    return Yii::t('yii', '{nFormatted} B', $params, $this->locale);
1332 2
                case 1:
1333 2
                    return Yii::t('yii', '{nFormatted} KB', $params, $this->locale);
1334 2
                case 2:
1335 2
                    return Yii::t('yii', '{nFormatted} MB', $params, $this->locale);
1336 2
                case 3:
1337 2
                    return Yii::t('yii', '{nFormatted} GB', $params, $this->locale);
1338 2
                case 4:
1339
                    return Yii::t('yii', '{nFormatted} TB', $params, $this->locale);
1340
                default:
1341 2
                    return Yii::t('yii', '{nFormatted} PB', $params, $this->locale);
1342
            }
1343
        }
1344
    }
1345
1346
    /**
1347
     * Formats the value in bytes as a size in human readable form, for example `12 kilobytes`.
1348
     *
1349
     * If [[sizeFormatBase]] is 1024, [binary prefixes](http://en.wikipedia.org/wiki/Binary_prefix) (e.g. kibibyte/KiB, mebibyte/MiB, ...)
1350
     * are used in the formatting result.
1351
     *
1352
     * @param string|int|float $value value in bytes to be formatted.
1353
     * @param int $decimals the number of digits after the decimal point.
1354
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1355
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1356
     * @return string the formatted result.
1357
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1358
     * @see sizeFormatBase
1359
     * @see asShortSize
1360
     */
1361 6
    public function asSize($value, $decimals = null, $options = [], $textOptions = [])
1362
    {
1363 6
        if ($value === null) {
1364 2
            return $this->nullDisplay;
1365
        }
1366
1367 6
        list($params, $position) = $this->formatNumber($value, $decimals, 4, $this->sizeFormatBase, $options, $textOptions);
1368
1369 6
        if ($this->sizeFormatBase == 1024) {
1370
            switch ($position) {
1371 6
                case 0:
1372 6
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->locale);
1373 4
                case 1:
1374 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}', $params, $this->locale);
1375 4
                case 2:
1376 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}', $params, $this->locale);
1377 4
                case 3:
1378 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}', $params, $this->locale);
1379 4
                case 4:
1380
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}', $params, $this->locale);
1381
                default:
1382 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}', $params, $this->locale);
1383
            }
1384
        } else {
1385
            switch ($position) {
1386 4
                case 0:
1387 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->locale);
1388 4
                case 1:
1389 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}', $params, $this->locale);
1390 4
                case 2:
1391 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}', $params, $this->locale);
1392 4
                case 3:
1393 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}', $params, $this->locale);
1394 4
                case 4:
1395
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}', $params, $this->locale);
1396
                default:
1397 4
                    return Yii::t('yii', '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}', $params, $this->locale);
1398
            }
1399
        }
1400
    }
1401
1402
    /**
1403
     * Formats the value as a length in human readable form for example `12 meters`.
1404
     *
1405
     * @param double|int $value value to be formatted.
1406
     * @param double $baseUnit unit of value as the multiplier of the smallest unit.
1407
     * @param string $unitSystem either [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]]. When `null`, property [[systemOfUnits]] will be used.
1408
     * @param int $decimals the number of digits after the decimal point.
1409
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1410
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1411
     * @return string the formatted result.
1412
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1413
     * @throws InvalidConfigException when INTL is not installed or does not contain required information.
1414
     * @see asLength
1415
     * @since 2.0.13
1416
     * @author John Was <[email protected]>
1417
     */
1418
    public function asLength($value, $baseUnit = null, $unitSystem = null, $decimals = null, $options = [], $textOptions = [])
1419
    {
1420
        return $this->formatUnit(self::UNIT_LENGTH, self::FORMAT_WIDTH_LONG, $value, $baseUnit, $unitSystem, $decimals, $options, $textOptions);
1421
    }
1422
1423
    /**
1424
     * Formats the value as a length in human readable form for example `12 m`.
1425
     *
1426
     * This is the short form of [[asLength]].
1427
     *
1428
     * @param double|int $value value to be formatted.
1429
     * @param double $baseUnit unit of value as the multiplier of the smallest unit
1430
     * @param string $unitSystem either [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]]. When `null`, property [[systemOfUnits]] will be used.
1431
     * @param int $decimals the number of digits after the decimal point.
1432
     * @param array $options 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 1
    public function asShortLength($value, $baseUnit = null, $unitSystem = null, $decimals = null, $options = [], $textOptions = [])
1442
    {
1443 1
        return $this->formatUnit(self::UNIT_LENGTH, self::FORMAT_WIDTH_SHORT, $value, $baseUnit, $unitSystem, $decimals, $options, $textOptions);
1444
    }
1445
1446
    /**
1447
     * Formats the value as a weight in human readable form for example `12 kilograms`.
1448
     *
1449
     * @param double|int $value value to be formatted.
1450
     * @param double $baseUnit unit of value as the multiplier of the smallest unit
1451
     * @param string $unitSystem either [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]]. When `null`, property [[systemOfUnits]] will be used.
1452
     * @param int $decimals the number of digits after the decimal point.
1453
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1454
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1455
     * @return string the formatted result.
1456
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1457
     * @throws InvalidConfigException when INTL is not installed or does not contain required information.
1458
     * @since 2.0.13
1459
     * @author John Was <[email protected]>
1460
     */
1461 1
    public function asWeight($value, $baseUnit = null, $unitSystem = null, $decimals = null, $options = [], $textOptions = [])
1462
    {
1463 1
        return $this->formatUnit(self::UNIT_WEIGHT, self::FORMAT_WIDTH_LONG, $value, $baseUnit, $unitSystem, $decimals, $options, $textOptions);
1464
    }
1465
1466
    /**
1467
     * Formats the value as a weight in human readable form for example `12 kg`.
1468
     *
1469
     * This is the short form of [[asWeight]].
1470
     *
1471
     * @param double|int $value value to be formatted.
1472
     * @param double $baseUnit unit of value as the multiplier of the smallest unit
1473
     * @param string $unitSystem either [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]]. When `null`, property [[systemOfUnits]] will be used.
1474
     * @param int $decimals the number of digits after the decimal point.
1475
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1476
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1477
     * @return string the formatted result.
1478
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1479
     * @throws InvalidConfigException when INTL is not installed or does not contain required information.
1480
     * @since 2.0.13
1481
     * @author John Was <[email protected]>
1482
     */
1483
    public function asShortWeight($value, $baseUnit = null, $unitSystem = null, $decimals = null, $options = [], $textOptions = [])
1484
    {
1485
        return $this->formatUnit(self::UNIT_WEIGHT, self::FORMAT_WIDTH_SHORT, $value, $baseUnit, $unitSystem, $decimals, $options, $textOptions);
1486
    }
1487
1488
    /**
1489
     * @param string $unitType one of [[UNIT_WEIGHT]], [[UNIT_LENGTH]]
1490
     * @param string $unitFormat one of [[FORMAT_WIDTH_SHORT]], [[FORMAT_WIDTH_LONG]]
1491
     * @param double|int $value to be formatted
1492
     * @param double $baseUnit unit of value as the multiplier of the smallest unit
1493
     * @param string $unitSystem either [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]]. When `null`, property [[systemOfUnits]] will be used.
1494
     * @param int $decimals the number of digits after the decimal point.
1495
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1496
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1497
     * @return string
1498
     * @throws InvalidConfigException when INTL is not installed or does not contain required information
1499
     */
1500 2
    private function formatUnit($unitType, $unitFormat, $value, $baseUnit, $unitSystem, $decimals, $options, $textOptions)
1501
    {
1502 2
        if ($value === null) {
1503
            return $this->nullDisplay;
1504
        }
1505 2
        if ($unitSystem === null) {
1506 2
            $unitSystem = $this->systemOfUnits;
1507
        }
1508 2
        if ($baseUnit === null) {
1509 2
            $baseUnit = $this->baseUnits[$unitType][$unitSystem];
1510
        }
1511
1512 2
        $multipliers = array_values($this->measureUnits[$unitType][$unitSystem]);
1513
1514 2
        list($params, $position) = $this->formatNumber($value * $baseUnit, $decimals, null, $multipliers, $options, $textOptions);
1515
1516 2
        $message = $this->getUnitMessage($unitType, $unitFormat, $unitSystem, $position);
1517
1518
        return (new \MessageFormatter($this->locale, $message))->format([
1519
            '0' => $params['nFormatted'],
1520
            'n' => $params['n'],
1521
        ]);
1522
    }
1523
1524
    /**
1525
     * @param string $unitType one of [[UNIT_WEIGHT]], [[UNIT_LENGTH]]
1526
     * @param string $unitFormat one of [[FORMAT_WIDTH_SHORT]], [[FORMAT_WIDTH_LONG]]
1527
     * @param string $system either [[UNIT_SYSTEM_METRIC]] or [[UNIT_SYSTEM_IMPERIAL]]. When `null`, property [[systemOfUnits]] will be used.
1528
     * @param int $position internal position of size unit
1529
     * @return string
1530
     * @throws InvalidConfigException when INTL is not installed or does not contain required information
1531
     */
1532 2
    private function getUnitMessage($unitType, $unitFormat, $system, $position)
1533
    {
1534 2
        if (isset($this->_unitMessages[$unitType][$system][$position])) {
1535
            return $this->_unitMessages[$unitType][$system][$position];
1536
        }
1537 2
        if (!$this->_intlLoaded) {
1538 2
            throw new InvalidConfigException('Format of ' . $unitType . ' is only supported when PHP intl extension is installed.');
1539
        }
1540
1541
        if ($this->_resourceBundle === null) {
1542
            try {
1543
                $this->_resourceBundle = new \ResourceBundle($this->locale, 'ICUDATA-unit');
1544
            } 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...
1545
                throw new InvalidConfigException('Current ICU data does not contain information about measure units. Check system requirements.');
1546
            }
1547
        }
1548
        $unitNames = array_keys($this->measureUnits[$unitType][$system]);
1549
        $bundleKey = 'units' . ($unitFormat === self::FORMAT_WIDTH_SHORT ? 'Short' : '');
1550
1551
        $unitBundle = $this->_resourceBundle[$bundleKey][$unitType][$unitNames[$position]];
1552
        if ($unitBundle === null) {
1553
            throw new InvalidConfigException('Current ICU data version does not contain information about unit type "' . $unitType . '" and unit measure "' . $unitNames[$position] . '". Check system requirements.');
1554
        }
1555
1556
        $message = [];
1557
        foreach ($unitBundle as $key => $value) {
1558
            if ($key === 'dnam') {
1559
                continue;
1560
            }
1561
            $message[] = "$key{{$value}}";
1562
        }
1563
        return $this->_unitMessages[$unitType][$system][$position] = '{n, plural, '.implode(' ', $message).'}';
1564
    }
1565
1566
    /**
1567
     * Given the value in bytes formats number part of the human readable form.
1568
     *
1569
     * @param string|int|float $value value in bytes to be formatted.
1570
     * @param int $decimals the number of digits after the decimal point
1571
     * @param int $maxPosition maximum internal position of size unit, ignored if $formatBase is an array
1572
     * @param array|int $formatBase the base at which each next unit is calculated, either 1000 or 1024, or an array
1573
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1574
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1575
     * @return array [parameters for Yii::t containing formatted number, internal position of size unit]
1576
     * @throws InvalidParamException if the input value is not numeric or the formatting failed.
1577
     */
1578 11
    private function formatNumber($value, $decimals, $maxPosition, $formatBase, $options, $textOptions)
1579
    {
1580 11
        $value = $this->normalizeNumericValue($value);
1581
1582 11
        $position = 0;
1583 11
        if (is_array($formatBase)) {
1584 2
            $maxPosition = count($formatBase) - 1;
1585
        }
1586
        do {
1587 11
            if (is_array($formatBase)) {
1588 2
                if (!isset($formatBase[$position + 1])) {
1589
                    break;
1590
                }
1591
1592 2
                if (abs($value) < $formatBase[$position + 1]) {
1593 2
                    break;
1594
                }
1595
            } else {
1596 9
                if (abs($value) < $formatBase) {
1597 9
                    break;
1598
                }
1599 7
                $value = $value / $formatBase;
1600
            }
1601 9
            $position++;
1602 9
        } while ($position < $maxPosition + 1);
1603
1604 11
        if (is_array($formatBase) && $position !== 0) {
1605 2
            $value /= $formatBase[$position];
1606
        }
1607
1608
        // no decimals for smallest unit
1609 11
        if ($position === 0) {
1610 9
            $decimals = 0;
1611 9
        } elseif ($decimals !== null) {
1612 6
            $value = round($value, $decimals);
1613
        }
1614
        // disable grouping for edge cases like 1023 to get 1023 B instead of 1,023 B
1615 11
        $oldThousandSeparator = $this->thousandSeparator;
1616 11
        $this->thousandSeparator = '';
1617 11
        if ($this->_intlLoaded) {
1618 5
            $options[NumberFormatter::GROUPING_USED] = false;
1619
        }
1620
        // format the size value
1621
        $params = [
1622
            // this is the unformatted number used for the plural rule
1623
            // abs() to make sure the plural rules work correctly on negative numbers, intl does not cover this
1624
            // http://english.stackexchange.com/questions/9735/is-1-singular-or-plural
1625 11
            'n' => abs($value),
1626
            // this is the formatted number used for display
1627 11
            'nFormatted' => $this->asDecimal($value, $decimals, $options, $textOptions),
1628
        ];
1629 11
        $this->thousandSeparator = $oldThousandSeparator;
1630
1631 11
        return [$params, $position];
1632
    }
1633
1634
    /**
1635
     * Normalizes a numeric input value
1636
     *
1637
     * - everything [empty](http://php.net/manual/en/function.empty.php) will result in `0`
1638
     * - a [numeric](http://php.net/manual/en/function.is-numeric.php) string will be casted to float
1639
     * - everything else will be returned if it is [numeric](http://php.net/manual/en/function.is-numeric.php),
1640
     *   otherwise an exception is thrown.
1641
     *
1642
     * @param mixed $value the input value
1643
     * @return float|int the normalized number value
1644
     * @throws InvalidParamException if the input value is not numeric.
1645
     */
1646 31
    protected function normalizeNumericValue($value)
1647
    {
1648 31
        if (empty($value)) {
1649 16
            return 0;
1650
        }
1651 31
        if (is_string($value) && is_numeric($value)) {
1652 16
            $value = (float) $value;
1653
        }
1654 31
        if (!is_numeric($value)) {
1655 2
            throw new InvalidParamException("'$value' is not a numeric value.");
1656
        }
1657 29
        return $value;
1658
    }
1659
1660
    /**
1661
     * Creates a number formatter based on the given type and format.
1662
     *
1663
     * You may override this method to create a number formatter based on patterns.
1664
     *
1665
     * @param int $style the type of the number formatter.
1666
     * Values: NumberFormatter::DECIMAL, ::CURRENCY, ::PERCENT, ::SCIENTIFIC, ::SPELLOUT, ::ORDINAL
1667
     * ::DURATION, ::PATTERN_RULEBASED, ::DEFAULT_STYLE, ::IGNORE
1668
     * @param int $decimals the number of digits after the decimal point.
1669
     * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
1670
     * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
1671
     * @return NumberFormatter the created formatter instance
1672
     */
1673 18
    protected function createNumberFormatter($style, $decimals = null, $options = [], $textOptions = [])
1674
    {
1675 18
        $formatter = new NumberFormatter($this->locale, $style);
1676
1677
        // set text attributes
1678 18
        foreach ($this->numberFormatterTextOptions as $name => $attribute) {
1679 1
            $formatter->setTextAttribute($name, $attribute);
1680
        }
1681 18
        foreach ($textOptions as $name => $attribute) {
1682 3
            $formatter->setTextAttribute($name, $attribute);
1683
        }
1684
1685
        // set attributes
1686 18
        foreach ($this->numberFormatterOptions as $name => $value) {
1687 9
            $formatter->setAttribute($name, $value);
1688
        }
1689 18
        foreach ($options as $name => $value) {
1690 7
            $formatter->setAttribute($name, $value);
1691
        }
1692 18
        if ($decimals !== null) {
1693 6
            $formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimals);
1694 6
            $formatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimals);
1695
        }
1696
1697
        // set symbols
1698 18
        if ($this->decimalSeparator !== null) {
1699 4
            $formatter->setSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL, $this->decimalSeparator);
1700
        }
1701 18
        if ($this->thousandSeparator !== null) {
1702 7
            $formatter->setSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL, $this->thousandSeparator);
1703 7
            $formatter->setSymbol(NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL, $this->thousandSeparator);
1704
        }
1705 18
        foreach ($this->numberFormatterSymbols as $name => $symbol) {
1706 2
            $formatter->setSymbol($name, $symbol);
1707
        }
1708
1709 18
        return $formatter;
1710
    }
1711
}
1712