Completed
Push — master ( 9315ce...ccf896 )
by Alexander
12:20
created

src/Locale.php (1 issue)

Check for unnecessary use of static:: in methods

Comprehensibility Minor

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
namespace Yii\I18n;
3
4
/**
5
 * Locale stores locale information created from BCP 47 formatted string
6
 * https://tools.ietf.org/html/bcp47
7
 */
8
final class Locale
9
{
10
    /**
11
     * @var string|null Two-letter ISO-639-2 language code
12
     * @see http://www.loc.gov/standards/iso639-2/
13
     */
14
    private $language;
15
16
    /**
17
     * @var string|null extended language subtags
18
     */
19
    private $extendedLanguage;
20
21
    /**
22
     * @var string|null
23
     */
24
    private $extension;
25
26
    /**
27
     * @var string|null Four-letter ISO 15924 script code
28
     * @see http://www.unicode.org/iso15924/iso15924-codes.html
29
     */
30
    private $script;
31
32
    /**
33
     * @var string|null Two-letter ISO 3166-1 country code
34
     * @see https://www.iso.org/iso-3166-country-codes.html
35
     */
36
    private $region;
37
38
    /**
39
     * @var string|null variant of language conventions to use
40
     */
41
    private $variant;
42
43
    /**
44
     * @var string|null ICU currency
45
     */
46
    private $currency;
47
48
    /**
49
     * @var string|null ICU calendar
50
     */
51
    private $calendar;
52
53
    /**
54
     * @var string ICU collation
55
     */
56
    private $collation;
57
58
    /**
59
     * @var string|null ICU numbers
60
     */
61
    private $numbers;
62
63
    /**
64
     * @var string|null
65
     */
66
    private $grandfathered;
67
68
    /**
69
     * @var string|null
70
     */
71
    private $private;
72
73
    /**
74
     * Locale constructor.
75
     * @param string $localeString BCP 47 formatted locale string
76
     * @see https://tools.ietf.org/html/bcp47
77
     * @throws \InvalidArgumentException
78
     */
79
    public function __construct(string $localeString)
80
    {
81
        if (!preg_match(static::getBCP47Regex(), $localeString, $matches)) {
0 ignored issues
show
Comprehensibility introduced by
Since Yii\I18n\Locale is declared final, using late-static binding will have no effect. You might want to replace static with self instead.

Late static binding only has effect in subclasses. A final class cannot be extended anymore so late static binding cannot occurr. Consider replacing static:: with self::.

To learn more about late static binding, please refer to the PHP core documentation.

Loading history...
82
            throw new \InvalidArgumentException($localeString . ' is not valid BCP 47 formatted locale string');
83
        }
84
85
        if (!empty($matches['language'])) {
86
            $this->language = strtolower($matches['language']);
87
        }
88
89
        if (!empty($matches['region'])) {
90
            $this->region = strtoupper($matches['region']);
91
        }
92
93
        if (!empty($matches['variant'])) {
94
            $this->variant = $matches['variant'];
95
        }
96
97
        if (!empty($matches['extendedLanguage'])) {
98
            $this->extendedLanguage = $matches['extendedLanguage'];
99
        }
100
101
        if (!empty($matches['extension'])) {
102
            $this->extension = $matches['extension'];
103
        }
104
105
        if (!empty($matches['script'])) {
106
            $this->script = ucfirst(strtolower($matches['script']));
107
        }
108
109
        if (!empty($matches['grandfathered'])) {
110
            $this->grandfathered = $matches['grandfathered'];
111
        }
112
113
        if (!empty($matches['private'])) {
114
            $this->private = $matches['private'];
115
        }
116
117
        if (!empty($matches['keywords'])) {
118
            foreach (explode(';', $matches['keywords']) as $pair) {
119
                [$key, $value] = explode('=', $pair);
120
121
                if ($key === 'calendar') {
122
                    $this->calendar = $value;
123
                }
124
125
                if ($key === 'collation') {
126
                    $this->collation = $value;
127
                }
128
129
                if ($key === 'currency') {
130
                    $this->currency = $value;
131
                }
132
133
                if ($key === 'numbers') {
134
                    $this->numbers = $value;
135
                }
136
            }
137
        }
138
    }
139
140
    /**
141
     * Returns Locale.
142
     * @param Locale|string $locale
143
     * @return self
144
     * @throws \InvalidArgumentException
145
     */
146
    public static function create($locale): self
147
    {
148
        if ($locale instanceof self) {
149
            return $locale;
150
        }
151
152
        return new self((string)$locale);
153
    }
154
155
    /**
156
     * @return string Four-letter ISO 15924 script code
157
     * @see http://www.unicode.org/iso15924/iso15924-codes.html
158
     */
159
    public function getScript(): ?string
160
    {
161
        return $this->script;
162
    }
163
164
    /**
165
     * @param null|string $script Four-letter ISO 15924 script code
166
     * @see http://www.unicode.org/iso15924/iso15924-codes.html
167
     * @return self
168
     */
169
    public function withScript(?string $script): self
170
    {
171
        $clone = clone $this;
172
        $clone->script = $script;
173
        return $clone;
174
    }
175
176
177
    /**
178
     * @return string variant of language conventions to use
179
     */
180
    public function getVariant(): ?string
181
    {
182
        return $this->variant;
183
    }
184
185
    /**
186
     * @param null|string $variant variant of language conventions to use
187
     * @return self
188
     */
189
    public function withVariant(?string $variant): self
190
    {
191
        $clone = clone $this;
192
        $clone->variant = $variant;
193
        return $clone;
194
    }
195
196
    /**
197
     * @return string|null Two-letter ISO-639-2 language code
198
     * @see http://www.loc.gov/standards/iso639-2/
199
     */
200
    public function getLanguage(): string
201
    {
202
        return $this->language;
203
    }
204
205
    /**
206
     * @param null|string $language Two-letter ISO-639-2 language code
207
     * @see http://www.loc.gov/standards/iso639-2/
208
     * @return self
209
     */
210
    public function withLanguage(?string $language): self
211
    {
212
        $clone = clone $this;
213
        $clone->language = $language;
214
        return $clone;
215
    }
216
217
    /**
218
     * @return null|string ICU calendar
219
     */
220
    public function getCalendar(): ?string
221
    {
222
        return $this->calendar;
223
    }
224
225
    /**
226
     * @param null|string $calendar ICU calendar
227
     * @return self
228
     */
229
    public function withCalendar(?string $calendar): self
230
    {
231
        $clone = clone $this;
232
        $clone->calendar = $calendar;
233
        return $clone;
234
    }
235
236
237
    /**
238
     * @return null|string ICU collation
239
     */
240
    public function getCollation(): ?string
241
    {
242
        return $this->collation;
243
    }
244
245
    /**
246
     * @param null|string $collation ICU collation
247
     * @return self
248
     */
249
    public function withCollation(?string $collation): self
250
    {
251
        $clone = clone $this;
252
        $clone->collation = $collation;
253
        return $clone;
254
    }
255
256
    /**
257
     * @return null|string ICU numbers
258
     */
259
    public function getNumbers(): ?string
260
    {
261
        return $this->numbers;
262
    }
263
264
    /**
265
     * @param null|string $numbers ICU numbers
266
     * @return self
267
     */
268
    public function withNumbers(?string $numbers): self
269
    {
270
        $clone = clone $this;
271
        $clone->numbers = $numbers;
272
        return $clone;
273
    }
274
275
    /**
276
     * @return string Two-letter ISO 3166-1 country code
277
     * @see https://www.iso.org/iso-3166-country-codes.html
278
     */
279
    public function getRegion(): ?string
280
    {
281
        return $this->region;
282
    }
283
284
    /**
285
     * @param null|string $region Two-letter ISO 3166-1 country code
286
     * @see https://www.iso.org/iso-3166-country-codes.html
287
     * @return self
288
     */
289
    public function withRegion(?string $region): self
290
    {
291
        $clone = clone $this;
292
        $clone->region = $region;
293
        return $clone;
294
    }
295
296
    /**
297
     * @return string ICU currency
298
     */
299
    public function getCurrency(): ?string
300
    {
301
        return $this->currency;
302
    }
303
304
    /**
305
     * @param null|string $currency ICU currency
306
     * @return self
307
     */
308
    public function withCurrency(?string $currency): self
309
    {
310
        $clone = clone $this;
311
        $clone->currency = $currency;
312
313
        return $clone;
314
    }
315
316
    /**
317
     * @return null|string extended language subtags
318
     */
319
    public function getExtendedLanguage(): ?string
320
    {
321
        return $this->extendedLanguage;
322
    }
323
324
    /**
325
     * @param null|string $extendedLanguage extended language subtags
326
     * @return self
327
     */
328
    public function withExtendedLanguage(?string $extendedLanguage): self
329
    {
330
        $clone = clone $this;
331
        $clone->extendedLanguage = $extendedLanguage;
332
333
        return $clone;
334
    }
335
336
337
    /**
338
     * @return null|string
339
     */
340
    public function getPrivate(): ?string
341
    {
342
        return $this->private;
343
    }
344
345
    /**
346
     * @param null|string $private
347
     * @return self
348
     */
349
    public function withPrivate(?string $private): self
350
    {
351
        $clone = clone $this;
352
        $clone->private = $private;
353
354
        return $clone;
355
    }
356
357
    /**
358
     * @return string regular expression for parsing BCP 47
359
     * @see https://tools.ietf.org/html/bcp47
360
     */
361
    private static function getBCP47Regex(): string
362
    {
363
        $regular = '(?:art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)';
364
        $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)';
365
        $grandfathered = '(?<grandfathered>' . $irregular . '|' . $regular . ')';
366
        $private = '(?<private>x(?:-[A-Za-z0-9]{1,8})+)';
367
        $singleton = '[0-9A-WY-Za-wy-z]';
368
        $extension = '(?<extension>' . $singleton . '(?:-[A-Za-z0-9]{2,8})+)';
369
        $variant = '(?<variant>[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3})';
370
        $region = '(?<region>[A-Za-z]{2}|[0-9]{3})';
371
        $script = '(?<script>[A-Za-z]{4})';
372
        $extendedLanguage = '(?<extendedLanguage>[A-Za-z]{3}(?:-[A-Za-z]{3}){0,2})';
373
        $language = '(?:(?<language>[A-Za-z]{4,8})|(?<language>[A-Za-z]{2,3})(?:-' . $extendedLanguage . ')?)';
374
        $icuKeywords = '(?:@(?<keywords>.*?))?';
375
        $languageTag = '(?:' . $language . '(?:-' . $script . ')?' . '(?:-' . $region . ')?' . '(?:-' . $variant . ')*' . '(?:-' . $extension . ')*' . '(?:-' . $private . ')?' . ')';
376
        return '/^(?J:' . $grandfathered . '|' . $languageTag . '|' . $private . ')' . $icuKeywords . '$/';
377
    }
378
379
    public function __toString(): string
380
    {
381
        return $this->asString();
382
    }
383
384
    /**
385
     * @return string
386
     */
387
    public function asString(): string
388
    {
389
        if ($this->grandfathered !== null) {
390
            return $this->grandfathered;
391
        }
392
393
        $result = [];
394
        if ($this->language !== null) {
395
            $result[] = $this->language;
396
397
            if ($this->extendedLanguage !== null) {
398
                $result[] = $this->extendedLanguage;
399
            }
400
401
            if ($this->script !== null) {
402
                $result[] = $this->script;
403
            }
404
405
            if ($this->region !== null) {
406
                $result[] = $this->region;
407
            }
408
409
            if ($this->variant !== null) {
410
                $result[] = $this->variant;
411
            }
412
413
            if ($this->extension !== null) {
414
                $result[] = $this->extension;
415
            }
416
        }
417
418
        if ($this->private !== null) {
419
            $result[] = $this->private;
420
        }
421
422
        $keywords = [];
423
        if ($this->currency !== null) {
424
            $keywords[] = 'currency=' . $this->currency;
425
        }
426
        if ($this->collation !== null) {
427
            $keywords[] = 'collation=' . $this->collation;
428
        }
429
        if ($this->calendar !== null) {
430
            $keywords[] = 'calendar=' . $this->calendar;
431
        }
432
        if ($this->numbers !== null) {
433
            $keywords[] = 'numbers=' . $this->numbers;
434
        }
435
436
        $string = implode('-', $result);
437
438
        if ($keywords !== []) {
439
            $string .= '@' . implode(';', $keywords);
440
        }
441
442
        return $string;
443
    }
444
445
    /**
446
     * Returns fallback locale
447
     *
448
     * @return self fallback locale
449
     */
450
    public function getFallbackLocale(): self
451
    {
452
        if ($this->variant !== null) {
453
            return $this->withVariant(null);
454
        }
455
456
        if ($this->region !== null) {
457
            return $this->withRegion(null);
458
        }
459
460
        if ($this->script !== null) {
461
            return $this->withScript(null);
462
        }
463
464
        return $this;
465
    }
466
}
467