Locale::asString()   F
last analyzed

Complexity

Conditions 17
Paths 16897

Size

Total Lines 65
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 37
CRAP Score 17

Importance

Changes 0
Metric Value
cc 17
eloc 36
nc 16897
nop 0
dl 0
loc 65
ccs 37
cts 37
cp 1
crap 17
rs 1.0499
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\I18n;
6
7
use InvalidArgumentException;
8
9
/**
10
 * Locale stores locale information created from BCP 47 formatted string.
11
 *
12
 * @link https://www.rfc-editor.org/info/bcp47
13
 */
14
final class Locale implements \Stringable
15
{
16
    /**
17
     * @var string|null Two-letter ISO-639-2 language code.
18
     *
19
     * @link http://www.loc.gov/standards/iso639-2/
20
     */
21
    private ?string $language = null;
22
23
    /**
24
     * @var string|null Extended language subtags.
25
     */
26
    private ?string $extendedLanguage = null;
27
28
    /**
29
     * @var string|null
30
     */
31
    private ?string $extension = null;
32
33
    /**
34
     * @var string|null Four-letter ISO 15924 script code.
35
     *
36
     * @link http://www.unicode.org/iso15924/iso15924-codes.html
37
     */
38
    private ?string $script = null;
39
40
    /**
41
     * @var string|null Two-letter ISO 3166-1 country code.
42
     *
43
     * @link https://www.iso.org/iso-3166-country-codes.html
44
     */
45
    private ?string $region = null;
46
47
    /**
48
     * @var string|null Variant of language conventions to use.
49
     */
50
    private ?string $variant = null;
51
52
    /**
53
     * @var string|null ICU currency.
54
     */
55
    private ?string $currency = null;
56
57
    /**
58
     * @var string|null ICU calendar.
59
     */
60
    private ?string $calendar = null;
61
62
    /**
63
     * @var string|null ICU case-first collation.
64
     *
65
     * @link https://unicode-org.github.io/icu/userguide/collation/customization/#casefirst
66
     * @link https://www.unicode.org/reports/tr35/tr35-61/tr35-collation.html#Collation_Settings
67
     */
68
    private ?string $colcasefirst = null;
69
70
    /**
71
     * @var string|null ICU collation.
72
     */
73
    private ?string $collation = null;
74
75
    /**
76
     * @var string|null ICU numeric collation.
77
     *
78
     * @link https://unicode-org.github.io/icu/userguide/collation/customization/#numericordering
79
     * @link https://www.unicode.org/reports/tr35/tr35-61/tr35-collation.html#Collation_Settings
80
     */
81
    private ?string $colnumeric = null;
82
83
    /**
84
     * @var string|null ICU numbers.
85
     */
86
    private ?string $numbers = null;
87
88
    /**
89
     * @var string|null Unicode hour cycle identifier.
90
     *
91
     * @link https://www.unicode.org/reports/tr35/#UnicodeHourCycleIdentifier
92
     */
93
    private ?string $hours = null;
94
95
    /**
96
     * @var string|null
97
     */
98
    private ?string $grandfathered = null;
99
100
    /**
101
     * @var string|null
102
     */
103
    private ?string $private = null;
104
105
    /**
106
     * Locale constructor.
107
     *
108
     * @param string $localeString BCP 47 formatted locale string.
109
     *
110
     * @link https://www.rfc-editor.org/info/bcp47
111
     *
112
     * @throws InvalidArgumentException
113
     */
114 39
    public function __construct(string $localeString)
115
    {
116 39
        if (!preg_match(self::getBCP47Regex(), $localeString, $matches)) {
117 1
            throw new InvalidArgumentException($localeString . ' is not valid BCP 47 formatted locale string.');
118
        }
119
120 38
        if (!empty($matches['language'])) {
121 35
            $this->language = strtolower($matches['language']);
122
        }
123
124 38
        if (!empty($matches['region'])) {
125 19
            $this->region = strtoupper($matches['region']);
126
        }
127
128 38
        if (!empty($matches['variant'])) {
129 5
            $this->variant = $matches['variant'];
130
        }
131
132 38
        if (!empty($matches['extendedLanguage'])) {
133 2
            $this->extendedLanguage = $matches['extendedLanguage'];
134
        }
135
136 38
        if (!empty($matches['extension'])) {
137 1
            $this->extension = $matches['extension'];
138
        }
139
140 38
        if (!empty($matches['script'])) {
141 7
            $this->script = ucfirst(strtolower($matches['script']));
142
        }
143
144 38
        if (!empty($matches['grandfathered'])) {
145 3
            $this->grandfathered = $matches['grandfathered'];
146
        }
147
148 38
        if (!empty($matches['private'])) {
149 5
            $this->private = preg_replace('~^x-~', '', $matches['private']);
150
        }
151
152 38
        if (!empty($matches['keywords'])) {
153 8
            foreach (explode(';', $matches['keywords']) as $pair) {
154 8
                [$key, $value] = explode('=', $pair);
155
156 8
                if ($key === 'calendar') {
157 2
                    $this->calendar = $value;
158
                }
159
160 8
                if ($key === 'colcasefirst') {
161 2
                    $this->colcasefirst = $value;
162
                }
163
164 8
                if ($key === 'collation') {
165 2
                    $this->collation = $value;
166
                }
167
168 8
                if ($key === 'colnumeric') {
169 2
                    $this->colnumeric = $value;
170
                }
171
172 8
                if ($key === 'currency') {
173 2
                    $this->currency = $value;
174
                }
175
176 8
                if ($key === 'numbers') {
177 2
                    $this->numbers = $value;
178
                }
179
180 8
                if ($key === 'hours') {
181 2
                    $this->hours = $value;
182
                }
183
            }
184
        }
185
    }
186
187
    /**
188
     * @return string Four-letter ISO 15924 script code.
189
     *
190
     * @link http://www.unicode.org/iso15924/iso15924-codes.html
191
     */
192 5
    public function script(): ?string
193
    {
194 5
        return $this->script;
195
    }
196
197
    /**
198
     * @param string|null $script Four-letter ISO 15924 script code.
199
     *
200
     * @link http://www.unicode.org/iso15924/iso15924-codes.html
201
     */
202 2
    public function withScript(?string $script): self
203
    {
204 2
        $new = clone $this;
205 2
        $new->script = $script;
206 2
        return $new;
207
    }
208
209
    /**
210
     * @return string Variant of language conventions to use.
211
     */
212 4
    public function variant(): ?string
213
    {
214 4
        return $this->variant;
215
    }
216
217
    /**
218
     * @param string|null $variant Variant of language conventions to use.
219
     */
220 2
    public function withVariant(?string $variant): self
221
    {
222 2
        $new = clone $this;
223 2
        $new->variant = $variant;
224 2
        return $new;
225
    }
226
227
    /**
228
     * @return string|null Two-letter ISO-639-2 language code.
229
     *
230
     * @link http://www.loc.gov/standards/iso639-2/
231
     */
232 5
    public function language(): ?string
233
    {
234 5
        return $this->language;
235
    }
236
237
    /**
238
     * @param string|null $language Two-letter ISO-639-2 language code.
239
     *
240
     * @link http://www.loc.gov/standards/iso639-2/
241
     */
242 1
    public function withLanguage(?string $language): self
243
    {
244 1
        $new = clone $this;
245 1
        $new->language = $language;
246 1
        return $new;
247
    }
248
249
    /**
250
     * @return string|null ICU calendar.
251
     */
252 2
    public function calendar(): ?string
253
    {
254 2
        return $this->calendar;
255
    }
256
257
    /**
258
     * @param string|null $calendar ICU calendar.
259
     */
260 3
    public function withCalendar(?string $calendar): self
261
    {
262 3
        $new = clone $this;
263 3
        $new->calendar = $calendar;
264 3
        return $new;
265
    }
266
267
    /**
268
     * @return string|null ICU case-first collation.
269
     *
270
     * @link https://unicode-org.github.io/icu/userguide/collation/customization/#casefirst
271
     * @link https://www.unicode.org/reports/tr35/tr35-61/tr35-collation.html#Collation_Settings
272
     */
273 2
    public function colcasefirst(): ?string
274
    {
275 2
        return $this->colcasefirst;
276
    }
277
278
    /**
279
     * @param string|null $colcasefirst ICU case-first collation.
280
     *
281
     * @link https://unicode-org.github.io/icu/userguide/collation/customization/#casefirst
282
     * @link https://www.unicode.org/reports/tr35/tr35-61/tr35-collation.html#Collation_Settings
283
     */
284 3
    public function withColcasefirst(?string $colcasefirst): self
285
    {
286 3
        $new = clone $this;
287 3
        $new->colcasefirst = $colcasefirst;
288 3
        return $new;
289
    }
290
291
    /**
292
     * @return string|null ICU collation.
293
     */
294 2
    public function collation(): ?string
295
    {
296 2
        return $this->collation;
297
    }
298
299
    /**
300
     * @param string|null $collation ICU collation.
301
     */
302 3
    public function withCollation(?string $collation): self
303
    {
304 3
        $new = clone $this;
305 3
        $new->collation = $collation;
306 3
        return $new;
307
    }
308
309
    /**
310
     * @return string|null ICU numeric collation.
311
     *
312
     * @link https://unicode-org.github.io/icu/userguide/collation/customization/#numericordering
313
     * @link https://www.unicode.org/reports/tr35/tr35-61/tr35-collation.html#Collation_Settings
314
     */
315 2
    public function colnumeric(): ?string
316
    {
317 2
        return $this->colnumeric;
318
    }
319
320
    /**
321
     * @param string|null $colnumeric ICU numeric collation.
322
     *
323
     * @link https://unicode-org.github.io/icu/userguide/collation/customization/#numericordering
324
     * @link https://www.unicode.org/reports/tr35/tr35-61/tr35-collation.html#Collation_Settings
325
     */
326 3
    public function withColnumeric(?string $colnumeric): self
327
    {
328 3
        $new = clone $this;
329 3
        $new->colnumeric = $colnumeric;
330 3
        return $new;
331
    }
332
333
    /**
334
     * @return string|null ICU numbers.
335
     */
336 2
    public function numbers(): ?string
337
    {
338 2
        return $this->numbers;
339
    }
340
341
    /**
342
     * @param string|null $numbers ICU numbers.
343
     */
344 3
    public function withNumbers(?string $numbers): self
345
    {
346 3
        $new = clone $this;
347 3
        $new->numbers = $numbers;
348 3
        return $new;
349
    }
350
351
    /**
352
     * @return string|null Unicode hour cycle identifier.
353
     *
354
     * @link https://www.unicode.org/reports/tr35/#UnicodeHourCycleIdentifier
355
     */
356 2
    public function hours(): ?string
357
    {
358 2
        return $this->hours;
359
    }
360
361
    /**
362
     * @param string|null $hours Unicode hour cycle identifier.
363
     *
364
     * @link https://www.unicode.org/reports/tr35/#UnicodeHourCycleIdentifier
365
     */
366 3
    public function withHours(?string $hours): self
367
    {
368 3
        $new = clone $this;
369 3
        $new->hours = $hours;
370 3
        return $new;
371
    }
372
373
    /**
374
     * @return string Two-letter ISO 3166-1 country code.
375
     *
376
     * @link https://www.iso.org/iso-3166-country-codes.html
377
     */
378 5
    public function region(): ?string
379
    {
380 5
        return $this->region;
381
    }
382
383
    /**
384
     * @param string|null $region Two-letter ISO 3166-1 country code.
385
     *
386
     * @link https://www.iso.org/iso-3166-country-codes.html
387
     */
388 2
    public function withRegion(?string $region): self
389
    {
390 2
        $new = clone $this;
391 2
        $new->region = $region;
392 2
        return $new;
393
    }
394
395
    /**
396
     * @return string ICU currency.
397
     */
398 2
    public function currency(): ?string
399
    {
400 2
        return $this->currency;
401
    }
402
403
    /**
404
     * @param string|null $currency ICU currency.
405
     */
406 3
    public function withCurrency(?string $currency): self
407
    {
408 3
        $new = clone $this;
409 3
        $new->currency = $currency;
410
411 3
        return $new;
412
    }
413
414
    /**
415
     * @return string|null Extended language subtags.
416
     */
417 2
    public function extendedLanguage(): ?string
418
    {
419 2
        return $this->extendedLanguage;
420
    }
421
422
    /**
423
     * @param string|null $extendedLanguage Extended language subtags.
424
     */
425 3
    public function withExtendedLanguage(?string $extendedLanguage): self
426
    {
427 3
        $new = clone $this;
428 3
        $new->extendedLanguage = $extendedLanguage;
429
430 3
        return $new;
431
    }
432
433 3
    public function private(): ?string
434
    {
435 3
        return $this->private;
436
    }
437
438 3
    public function withPrivate(?string $private): self
439
    {
440 3
        $new = clone $this;
441 3
        $new->private = $private;
442
443 3
        return $new;
444
    }
445
446
    /**
447
     * @link https://www.rfc-editor.org/info/bcp47
448
     *
449
     * @return string Regular expression for parsing BCP 47.
450
     * @psalm-return non-empty-string
451
     */
452 39
    private static function getBCP47Regex(): string
453
    {
454 39
        $regular = '(?:art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)';
455 39
        $irregular = '(?:en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)';
456 39
        $grandfathered = '(?<grandfathered>' . $irregular . '|' . $regular . ')';
457 39
        $private = '(?<private>x(?:-[A-Za-z0-9]{1,8})+)';
458 39
        $singleton = '[0-9A-WY-Za-wy-z]';
459 39
        $extension = '(?<extension>' . $singleton . '(?:-[A-Za-z0-9]{2,8})+)';
460 39
        $variant = '(?<variant>[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3})';
461 39
        $region = '(?<region>[A-Za-z]{2}|[0-9]{3})';
462 39
        $script = '(?<script>[A-Za-z]{4})';
463 39
        $extendedLanguage = '(?<extendedLanguage>[A-Za-z]{3}(?:-[A-Za-z]{3}){0,2})';
464 39
        $language = '(?<language>[A-Za-z]{4,8})|(?<language>[A-Za-z]{2,3})(?:-' . $extendedLanguage . ')?';
465 39
        $icuKeywords = '(?:@(?<keywords>.*?))?';
466 39
        $languageTag = '(?:' . $language . '(?:-' . $script . ')?' . '(?:-' . $region . ')?' . '(?:-' . $variant . ')*' . '(?:-' . $extension . ')*' . '(?:-' . $private . ')?' . ')';
467 39
        return '/^(?J:' . $grandfathered . '|' . $languageTag . '|' . $private . ')' . $icuKeywords . '$/';
468
    }
469
470 1
    public function __toString(): string
471
    {
472 1
        return $this->asString();
473
    }
474
475
    /**
476
     * @return string Locale string.
477
     */
478 7
    public function asString(): string
479
    {
480 7
        if ($this->grandfathered !== null) {
481 3
            return $this->grandfathered;
482
        }
483
484 5
        $result = [];
485 5
        if ($this->language !== null) {
486 5
            $result[] = $this->language;
487
488 5
            if ($this->extendedLanguage !== null) {
489 1
                $result[] = $this->extendedLanguage;
490
            }
491
492 5
            if ($this->script !== null) {
493 1
                $result[] = $this->script;
494
            }
495
496 5
            if ($this->region !== null) {
497 2
                $result[] = $this->region;
498
            }
499
500 5
            if ($this->variant !== null) {
501 1
                $result[] = $this->variant;
502
            }
503
504 5
            if ($this->extension !== null) {
505 1
                $result[] = $this->extension;
506
            }
507
        }
508
509 5
        if ($this->private !== null) {
510 1
            $result[] = 'x-' . $this->private;
511
        }
512
513 5
        $keywords = [];
514 5
        if ($this->currency !== null) {
515 1
            $keywords[] = 'currency=' . $this->currency;
516
        }
517 5
        if ($this->colcasefirst !== null) {
518 1
            $keywords[] = 'colcasefirst=' . $this->colcasefirst;
519
        }
520 5
        if ($this->collation !== null) {
521 1
            $keywords[] = 'collation=' . $this->collation;
522
        }
523 5
        if ($this->colnumeric !== null) {
524 1
            $keywords[] = 'colnumeric=' . $this->colnumeric;
525
        }
526 5
        if ($this->calendar !== null) {
527 1
            $keywords[] = 'calendar=' . $this->calendar;
528
        }
529 5
        if ($this->numbers !== null) {
530 1
            $keywords[] = 'numbers=' . $this->numbers;
531
        }
532 5
        if ($this->hours !== null) {
533 1
            $keywords[] = 'hours=' . $this->hours;
534
        }
535
536 5
        $string = implode('-', $result);
537
538 5
        if ($keywords !== []) {
539 1
            $string .= '@' . implode(';', $keywords);
540
        }
541
542 5
        return $string;
543
    }
544
545
    /**
546
     * Returns fallback locale.
547
     *
548
     * @return self Fallback locale.
549
     */
550 2
    public function fallbackLocale(): self
551
    {
552 2
        $fallback = $this
553 2
            ->withCalendar(null)
554 2
            ->withColcasefirst(null)
555 2
            ->withCollation(null)
556 2
            ->withColnumeric(null)
557 2
            ->withCurrency(null)
558 2
            ->withExtendedLanguage(null)
559 2
            ->withNumbers(null)
560 2
            ->withHours(null)
561 2
            ->withPrivate(null);
562
563 2
        if ($fallback->variant() !== null) {
564 1
            return $fallback->withVariant(null);
565
        }
566
567 2
        if ($fallback->region() !== null) {
568 1
            return $fallback->withRegion(null);
569
        }
570
571 2
        if ($fallback->script() !== null) {
572 1
            return $fallback->withScript(null);
573
        }
574
575 1
        return $fallback;
576
    }
577
}
578