Passed
Push — master ( 9d4783...cd4f87 )
by Alexander
01:33
created

Inflector::pluralize()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 6
nc 4
nop 1
dl 0
loc 12
ccs 7
cts 7
cp 1
crap 4
rs 10
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Strings;
6
7
/**
8
 * Inflector provides concrete implementation for [[Inflector]].
9
 */
10
final class Inflector
11
{
12
    /**
13
     * @var array the rules for converting a word into its plural form.
14
     * The keys are the regular expressions and the values are the corresponding replacements.
15
     */
16
    private array $pluralizeRules = [
17
        '/([nrlm]ese|deer|fish|sheep|measles|ois|pox|media)$/i' => '\1',
18
        '/^(sea[- ]bass)$/i' => '\1',
19
        '/(m)ove$/i' => '\1oves',
20
        '/(f)oot$/i' => '\1eet',
21
        '/(h)uman$/i' => '\1umans',
22
        '/(s)tatus$/i' => '\1tatuses',
23
        '/(s)taff$/i' => '\1taff',
24
        '/(t)ooth$/i' => '\1eeth',
25
        '/(quiz)$/i' => '\1zes',
26
        '/^(ox)$/i' => '\1\2en',
27
        '/([m|l])ouse$/i' => '\1ice',
28
        '/(matr|vert|ind)(ix|ex)$/i' => '\1ices',
29
        '/(x|ch|ss|sh)$/i' => '\1es',
30
        '/([^aeiouy]|qu)y$/i' => '\1ies',
31
        '/(hive)$/i' => '\1s',
32
        '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves',
33
        '/sis$/i' => 'ses',
34
        '/([ti])um$/i' => '\1a',
35
        '/(p)erson$/i' => '\1eople',
36
        '/(m)an$/i' => '\1en',
37
        '/(c)hild$/i' => '\1hildren',
38
        '/(buffal|tomat|potat|ech|her|vet)o$/i' => '\1oes',
39
        '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i',
40
        '/us$/i' => 'uses',
41
        '/(alias)$/i' => '\1es',
42
        '/(ax|cris|test)is$/i' => '\1es',
43
        '/(currenc)y$/' => '\1ies',
44
        '/on$/i' => 'a',
45
        '/s$/' => 's',
46
        '/^$/' => '',
47
        '/$/' => 's',
48
    ];
49
    /**
50
     * @var array the rules for converting a word into its singular form.
51
     * The keys are the regular expressions and the values are the corresponding replacements.
52
     */
53
    private array $singularizeRules = [
54
        '/([nrlm]ese|deer|fish|sheep|measles|ois|pox|media|ss)$/i' => '\1',
55
        '/^(sea[- ]bass)$/i' => '\1',
56
        '/(s)tatuses$/i' => '\1tatus',
57
        '/(f)eet$/i' => '\1oot',
58
        '/(t)eeth$/i' => '\1ooth',
59
        '/^(.*)(menu)s$/i' => '\1\2',
60
        '/(quiz)zes$/i' => '\\1',
61
        '/(matr)ices$/i' => '\1ix',
62
        '/(vert|ind)ices$/i' => '\1ex',
63
        '/^(ox)en/i' => '\1',
64
        '/(alias)(es)*$/i' => '\1',
65
        '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us',
66
        '/([ftw]ax)es/i' => '\1',
67
        '/(cris|ax|test)es$/i' => '\1is',
68
        '/(shoe|slave)s$/i' => '\1',
69
        '/(o)es$/i' => '\1',
70
        '/ouses$/i' => 'ouse',
71
        '/([^a])uses$/i' => '\1us',
72
        '/([m|l])ice$/i' => '\1ouse',
73
        '/(x|ch|ss|sh)es$/i' => '\1',
74
        '/(m)ovies$/i' => '\1\2ovie',
75
        '/(s)eries$/i' => '\1\2eries',
76
        '/([^aeiouy]|qu)ies$/i' => '\1y',
77
        '/([lr])ves$/i' => '\1f',
78
        '/(tive)s$/i' => '\1',
79
        '/(hive)s$/i' => '\1',
80
        '/(drive)s$/i' => '\1',
81
        '/([^fo])ves$/i' => '\1fe',
82
        '/(^analy)ses$/i' => '\1sis',
83
        '/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
84
        '/criteria$/i' => 'criterion',
85
        '/([ti])a$/i' => '\1um',
86
        '/(p)eople$/i' => '\1\2erson',
87
        '/(m)en$/i' => '\1an',
88
        '/(c)hildren$/i' => '\1\2hild',
89
        '/(n)ews$/i' => '\1\2ews',
90
        '/(n)etherlands$/i' => '\1\2etherlands',
91
        '/eaus$/i' => 'eau',
92
        '/(currenc)ies$/i' => '\1y',
93
        '/^(.*us)$/i' => '\\1',
94
        '/s$/i' => '',
95
    ];
96
    /**
97
     * @var array the special rules for converting a word between its plural form and singular form.
98
     * The keys are the special words in singular form, and the values are the corresponding plural form.
99
     */
100
    private array $specialRules = [
101
        'atlas' => 'atlases',
102
        'beef' => 'beefs',
103
        'brother' => 'brothers',
104
        'cafe' => 'cafes',
105
        'child' => 'children',
106
        'cookie' => 'cookies',
107
        'corpus' => 'corpuses',
108
        'cow' => 'cows',
109
        'curve' => 'curves',
110
        'foe' => 'foes',
111
        'ganglion' => 'ganglions',
112
        'genie' => 'genies',
113
        'genus' => 'genera',
114
        'graffito' => 'graffiti',
115
        'hoof' => 'hoofs',
116
        'loaf' => 'loaves',
117
        'man' => 'men',
118
        'money' => 'monies',
119
        'mongoose' => 'mongooses',
120
        'move' => 'moves',
121
        'mythos' => 'mythoi',
122
        'niche' => 'niches',
123
        'numen' => 'numina',
124
        'occiput' => 'occiputs',
125
        'octopus' => 'octopuses',
126
        'opus' => 'opuses',
127
        'ox' => 'oxen',
128
        'pasta' => 'pasta',
129
        'penis' => 'penises',
130
        'sex' => 'sexes',
131
        'soliloquy' => 'soliloquies',
132
        'testis' => 'testes',
133
        'trilby' => 'trilbys',
134
        'turf' => 'turfs',
135
        'wave' => 'waves',
136
        'Amoyese' => 'Amoyese',
137
        'bison' => 'bison',
138
        'Borghese' => 'Borghese',
139
        'bream' => 'bream',
140
        'breeches' => 'breeches',
141
        'britches' => 'britches',
142
        'buffalo' => 'buffalo',
143
        'cantus' => 'cantus',
144
        'carp' => 'carp',
145
        'chassis' => 'chassis',
146
        'clippers' => 'clippers',
147
        'cod' => 'cod',
148
        'coitus' => 'coitus',
149
        'Congoese' => 'Congoese',
150
        'contretemps' => 'contretemps',
151
        'corps' => 'corps',
152
        'debris' => 'debris',
153
        'diabetes' => 'diabetes',
154
        'djinn' => 'djinn',
155
        'eland' => 'eland',
156
        'elk' => 'elk',
157
        'equipment' => 'equipment',
158
        'Faroese' => 'Faroese',
159
        'flounder' => 'flounder',
160
        'Foochowese' => 'Foochowese',
161
        'gallows' => 'gallows',
162
        'Genevese' => 'Genevese',
163
        'Genoese' => 'Genoese',
164
        'Gilbertese' => 'Gilbertese',
165
        'graffiti' => 'graffiti',
166
        'headquarters' => 'headquarters',
167
        'herpes' => 'herpes',
168
        'hijinks' => 'hijinks',
169
        'Hottentotese' => 'Hottentotese',
170
        'information' => 'information',
171
        'innings' => 'innings',
172
        'jackanapes' => 'jackanapes',
173
        'Kiplingese' => 'Kiplingese',
174
        'Kongoese' => 'Kongoese',
175
        'Lucchese' => 'Lucchese',
176
        'mackerel' => 'mackerel',
177
        'Maltese' => 'Maltese',
178
        'mews' => 'mews',
179
        'moose' => 'moose',
180
        'mumps' => 'mumps',
181
        'Nankingese' => 'Nankingese',
182
        'news' => 'news',
183
        'nexus' => 'nexus',
184
        'Niasese' => 'Niasese',
185
        'Pekingese' => 'Pekingese',
186
        'Piedmontese' => 'Piedmontese',
187
        'pincers' => 'pincers',
188
        'Pistoiese' => 'Pistoiese',
189
        'pliers' => 'pliers',
190
        'Portuguese' => 'Portuguese',
191
        'proceedings' => 'proceedings',
192
        'rabies' => 'rabies',
193
        'rice' => 'rice',
194
        'rhinoceros' => 'rhinoceros',
195
        'salmon' => 'salmon',
196
        'Sarawakese' => 'Sarawakese',
197
        'scissors' => 'scissors',
198
        'series' => 'series',
199
        'Shavese' => 'Shavese',
200
        'shears' => 'shears',
201
        'siemens' => 'siemens',
202
        'species' => 'species',
203
        'swine' => 'swine',
204
        'testes' => 'testes',
205
        'trousers' => 'trousers',
206
        'trout' => 'trout',
207
        'tuna' => 'tuna',
208
        'Vermontese' => 'Vermontese',
209
        'Wenchowese' => 'Wenchowese',
210
        'whiting' => 'whiting',
211
        'wildebeest' => 'wildebeest',
212
        'Yengeese' => 'Yengeese',
213
    ];
214
    /**
215
     * @var array fallback map for transliteration used by [[transliterate()]] when intl isn't available.
216
     */
217
    private array $transliterationMap = [
218
        'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'A', 'Å' => 'A', 'Æ' => 'AE', 'Ç' => 'C',
219
        'È' => 'E', 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E', 'Ì' => 'I', 'Í' => 'I', 'Î' => 'I', 'Ï' => 'I',
220
        'Ð' => 'D', 'Ñ' => 'N', 'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ö' => 'O', 'Ő' => 'O',
221
        'Ø' => 'O', 'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', 'Ü' => 'U', 'Ű' => 'U', 'Ý' => 'Y', 'Þ' => 'TH',
222
        'ß' => 'ss',
223
        'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a', 'ä' => 'a', 'å' => 'a', 'æ' => 'ae', 'ç' => 'c',
224
        'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e', 'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i',
225
        'ð' => 'd', 'ñ' => 'n', 'ò' => 'o', 'ó' => 'o', 'ô' => 'o', 'õ' => 'o', 'ö' => 'o', 'ő' => 'o',
226
        'ø' => 'o', 'ù' => 'u', 'ú' => 'u', 'û' => 'u', 'ü' => 'u', 'ű' => 'u', 'ý' => 'y', 'þ' => 'th',
227
        'ÿ' => 'y',
228
    ];
229
    /**
230
     * Shortcut for `Any-Latin; NFKD` transliteration rule.
231
     *
232
     * The rule is strict, letters will be transliterated with
233
     * the closest sound-representation chars. The result may contain any UTF-8 chars. For example:
234
     * `获取到 どちら Українська: ґ,є, Српска: ђ, њ, џ! ¿Español?` will be transliterated to
235
     * `huò qǔ dào dochira Ukraí̈nsʹka: g̀,ê, Srpska: đ, n̂, d̂! ¿Español?`.
236
     *
237
     * Used in [[transliterate()]].
238
     * For detailed information see [unicode normalization forms](http://unicode.org/reports/tr15/#Normalization_Forms_Table)
239
     * @see http://unicode.org/reports/tr15/#Normalization_Forms_Table
240
     * @see transliterate()
241
     */
242
    public const TRANSLITERATE_STRICT = 'Any-Latin; NFKD';
243
    /**
244
     * Shortcut for `Any-Latin; Latin-ASCII` transliteration rule.
245
     *
246
     * The rule is medium, letters will be
247
     * transliterated to characters of Latin-1 (ISO 8859-1) ASCII table. For example:
248
     * `获取到 どちら Українська: ґ,є, Српска: ђ, њ, џ! ¿Español?` will be transliterated to
249
     * `huo qu dao dochira Ukrainsʹka: g,e, Srpska: d, n, d! ¿Espanol?`.
250
     *
251
     * Used in [[transliterate()]].
252
     * For detailed information see [unicode normalization forms](http://unicode.org/reports/tr15/#Normalization_Forms_Table)
253
     * @see http://unicode.org/reports/tr15/#Normalization_Forms_Table
254
     * @see transliterate()
255
     */
256
    public const TRANSLITERATE_MEDIUM = 'Any-Latin; Latin-ASCII';
257
    /**
258
     * Shortcut for `Any-Latin; Latin-ASCII; [\u0080-\uffff] remove` transliteration rule.
259
     *
260
     * The rule is loose,
261
     * letters will be transliterated with the characters of Basic Latin Unicode Block.
262
     * For example:
263
     * `获取到 どちら Українська: ґ,є, Српска: ђ, њ, џ! ¿Español?` will be transliterated to
264
     * `huo qu dao dochira Ukrainska: g,e, Srpska: d, n, d! Espanol?`.
265
     *
266
     * Used in [[transliterate()]].
267
     * For detailed information see [unicode normalization forms](http://unicode.org/reports/tr15/#Normalization_Forms_Table)
268
     * @see http://unicode.org/reports/tr15/#Normalization_Forms_Table
269
     * @see transliterate()
270
     */
271
    public const TRANSLITERATE_LOOSE = 'Any-Latin; Latin-ASCII; [\u0080-\uffff] remove';
272
273
    /**
274
     * @var mixed Either a [[\Transliterator]], or a string from which a [[\Transliterator]] can be built
275
     * for transliteration. Used by [[transliterate()]] when intl is available. Defaults to [[TRANSLITERATE_LOOSE]]
276
     * @see https://secure.php.net/manual/en/transliterator.transliterate.php
277
     */
278
    private string $transliterator = self::TRANSLITERATE_LOOSE;
279
280
    private bool $withoutIntl = false;
281
282 2
    public function withPluralizeRules(array $rules): self
283
    {
284 2
        $new = clone $this;
285 2
        $new->pluralizeRules = $rules;
286 2
        return $new;
287
    }
288
289 1
    public function getPluralizeRules(): array
290
    {
291 1
        return $this->pluralizeRules;
292
    }
293
294 1
    public function withSingularizeRules(array $rules): self
295
    {
296 1
        $new = clone $this;
297 1
        $new->singularizeRules = $rules;
298 1
        return $new;
299
    }
300
301 1
    public function getSingularizeRules(): array
302
    {
303 1
        return $this->singularizeRules;
304
    }
305
306 2
    public function withSpecialRules(array $rules): self
307
    {
308 2
        $new = clone $this;
309 2
        $new->specialRules = $rules;
310 2
        return $new;
311
    }
312
313 2
    public function getSpecialRules(): array
314
    {
315 2
        return $this->specialRules;
316
    }
317
318 1
    public function withTransliterator(string $transliterator): self
319
    {
320 1
        $new = clone $this;
321 1
        $new->transliterator = $transliterator;
322 1
        return $new;
323
    }
324
325 1
    public function withTransliterationMap(array $transliterationMap): self
326
    {
327 1
        $new = clone $this;
328 1
        $new->transliterationMap = $transliterationMap;
329 1
        return $new;
330
    }
331
332 19
    public function withoutIntl(): self
333
    {
334 19
        $new = clone $this;
335 19
        $new->withoutIntl = true;
336 19
        return $new;
337
    }
338
339
    /**
340
     * Converts a word to its plural form.
341
     * Note that this is for English only!
342
     * For example, 'apple' will become 'apples', and 'child' will become 'children'.
343
     * @param string $word the word to be pluralized
344
     * @return string the pluralized word
345
     */
346 5
    public function pluralize(string $word): string
347
    {
348 5
        if (isset($this->specialRules[$word])) {
349 2
            return $this->specialRules[$word];
350
        }
351 4
        foreach ($this->pluralizeRules as $rule => $replacement) {
352 3
            if (preg_match($rule, $word)) {
353 3
                return preg_replace($rule, $replacement, $word);
354
            }
355
        }
356
357 1
        return $word;
358
    }
359
360
    /**
361
     * Returns the singular of the $word.
362
     * @param string $word the english word to singularize
363
     * @return string Singular noun.
364
     */
365 4
    public function singularize(string $word): string
366
    {
367 4
        $result = array_search($word, $this->specialRules, true);
368 4
        if ($result !== false) {
369 2
            return $result;
370
        }
371 3
        foreach ($this->singularizeRules as $rule => $replacement) {
372 3
            if (preg_match($rule, $word)) {
373 3
                return preg_replace($rule, $replacement, $word);
374
            }
375
        }
376
377 1
        return $word;
378
    }
379
380
    /**
381
     * Converts an underscored or CamelCase word into a English
382
     * sentence.
383
     * @param string $words
384
     * @param bool $uppercaseAll whether to set all words to uppercase
385
     * @return string
386
     */
387 1
    public function titleize(string $words, bool $uppercaseAll = false): string
388
    {
389 1
        $words = $this->humanize($this->underscore($words), $uppercaseAll);
390
391 1
        return $uppercaseAll ? StringHelper::ucwords($words) : StringHelper::ucfirst($words);
392
    }
393
394
    /**
395
     * Returns given word as CamelCased.
396
     *
397
     * Converts a word like "send_email" to "SendEmail". It
398
     * will remove non alphanumeric character from the word, so
399
     * "who's online" will be converted to "WhoSOnline".
400
     * @param string $word the word to CamelCase
401
     * @return string
402
     * @see variablize()
403
     */
404 3
    public function camelize(string $word): string
405
    {
406 3
        return str_replace(' ', '', StringHelper::ucwords(preg_replace('/[^\pL\pN]+/u', ' ', $word)));
407
    }
408
409
    /**
410
     * Converts a CamelCase name into space-separated words.
411
     * For example, 'PostTag' will be converted to 'Post Tag'.
412
     * @param string $name the string to be converted
413
     * @param bool $ucwords whether to capitalize the first letter in each word
414
     * @return string the resulting words
415
     */
416 1
    public function camel2words(string $name, bool $ucwords = true): string
417
    {
418 1
        $label = mb_strtolower(trim(str_replace([
419 1
            '-',
420
            '_',
421
            '.',
422 1
        ], ' ', preg_replace('/(?<!\p{Lu})(\p{Lu})|(\p{Lu})(?=\p{Ll})/u', ' \0', $name))));
423
424 1
        return $ucwords ? StringHelper::ucwords($label) : $label;
425
    }
426
427
    /**
428
     * Converts a CamelCase name into an ID in lowercase.
429
     * Words in the ID may be concatenated using the specified character (defaults to '-').
430
     * For example, 'PostTag' will be converted to 'post-tag'.
431
     * @param string $name the string to be converted
432
     * @param string $separator the character used to concatenate the words in the ID
433
     * @param bool $strict whether to insert a separator between two consecutive uppercase chars, defaults to false
434
     * @return string the resulting ID
435
     */
436 27
    public function camel2id(string $name, string $separator = '-', bool $strict = false): string
437
    {
438 27
        $regex = $strict
439 11
            ? '/(?<=\p{L})(\p{Lu})/u'
440 27
            : '/(?<=\p{L})(?<!\p{Lu})(\p{Lu})/u';
441 27
        $result = preg_replace($regex, addslashes($separator) . '\1', $name);
442
443 27
        if ($separator !== '_') {
444 14
            $result = str_replace('_', $separator, $result);
445
        }
446
447 27
        return mb_strtolower(trim($result, $separator));
448
    }
449
450
    /**
451
     * Converts an ID into a CamelCase name.
452
     * Words in the ID separated by `$separator` (defaults to '-') will be concatenated into a CamelCase name.
453
     * For example, 'post-tag' is converted to 'PostTag'.
454
     * @param string $id the ID to be converted
455
     * @param string $separator the character used to separate the words in the ID
456
     * @return string the resulting CamelCase name
457
     */
458 9
    public function id2camel(string $id, string $separator = '-'): string
459
    {
460 9
        return str_replace(' ', '', StringHelper::ucwords(str_replace($separator, ' ', $id)));
461
    }
462
463
    /**
464
     * Converts any "CamelCased" into an "underscored_word".
465
     * @param string $words the word(s) to underscore
466
     * @return string
467
     */
468 3
    public function underscore(string $words): string
469
    {
470 3
        return mb_strtolower(preg_replace('/(?<=\\pL)(\\p{Lu})/u', '_\\1', $words));
471
    }
472
473
    /**
474
     * Returns a human-readable string from $word.
475
     * @param string $word the string to humanize
476
     * @param bool $ucAll whether to set all words to uppercase or not
477
     * @return string
478
     */
479 2
    public function humanize(string $word, bool $ucAll = false): string
480
    {
481 2
        $word = str_replace('_', ' ', preg_replace('/_id$/', '', $word));
482
483 2
        return $ucAll ? StringHelper::ucwords($word) : StringHelper::ucfirst($word);
484
    }
485
486
    /**
487
     * Same as camelize but first char is in lowercase.
488
     *
489
     * Converts a word like "send_email" to "sendEmail". It
490
     * will remove non alphanumeric character from the word, so
491
     * "who's online" will be converted to "whoSOnline".
492
     * @param string $word to lowerCamelCase
493
     * @return string
494
     */
495 1
    public function variablize(string $word): string
496
    {
497 1
        $word = $this->camelize($word);
498
499 1
        return mb_strtolower(mb_substr($word, 0, 1)) . mb_substr($word, 1, null);
500
    }
501
502
    /**
503
     * Converts a class name to its table name (pluralized) naming conventions.
504
     *
505
     * For example, converts "Person" to "people".
506
     * @param string $className the class name for getting related table_name
507
     * @return string
508
     */
509 1
    public function tableize(string $className): string
510
    {
511 1
        return $this->pluralize($this->underscore($className));
512
    }
513
514
    /**
515
     * Returns a string with all spaces converted to given replacement,
516
     * non word characters removed and the rest of characters transliterated.
517
     *
518
     * If intl extension isn't available uses fallback that converts latin characters only
519
     * and removes the rest. You may customize characters map via $transliteration property
520
     * of the helper.
521
     *
522
     * @param string $string An arbitrary string to convert
523
     * @param string $replacement The replacement to use for spaces
524
     * @param bool $lowercase whether to return the string in lowercase or not. Defaults to `true`.
525
     * @return string The converted string.
526
     */
527 19
    public function slug(string $string, string $replacement = '-', bool $lowercase = true): string
528
    {
529
        // replace all non words character
530 19
        $string = preg_replace('/[^a-zA-Z0-9]++/u', $replacement, $this->transliterate($string));
531
        // remove first and last replacements
532 19
        $string = preg_replace('/^(?:' . preg_quote($replacement) . ')++|(?:' . preg_quote($replacement) . ')++$/u' . ($lowercase ? 'i' : ''), '', $string);
533
534 19
        return $lowercase ? strtolower($string) : $string;
535
    }
536
537
    /**
538
     * Returns transliterated version of a string.
539
     *
540
     * If intl extension isn't available uses fallback that converts latin characters only
541
     * and removes the rest. You may customize characters map via $transliteration property
542
     * of the helper.
543
     *
544
     * @noinspection PhpComposerExtensionStubsInspection
545
     *
546
     * @param string $string input string
547
     * @param string|\Transliterator $transliterator either a [[\Transliterator]] or a string
548
     * from which a [[\Transliterator]] can be built.
549
     * @return string
550
     */
551 24
    public function transliterate(string $string, $transliterator = null): string
552
    {
553 24
        if ($this->useIntl()) {
554 22
            if ($transliterator === null) {
555 19
                $transliterator = $this->transliterator;
556
            }
557
558
            /* @noinspection PhpComposerExtensionStubsInspection */
559 22
            return transliterator_transliterate($transliterator, $string);
560
        }
561
562 19
        return strtr($string, $this->transliterationMap);
563
    }
564
565
    /**
566
     * @return bool if intl extension should be used
567
     */
568 24
    private function useIntl(): bool
569
    {
570 24
        return $this->withoutIntl === false && extension_loaded('intl');
571
    }
572
573
    /**
574
     * Converts a table name to its class name.
575
     *
576
     * For example, converts "people" to "Person".
577
     * @param string $tableName
578
     * @return string
579
     */
580 1
    public function classify(string $tableName): string
581
    {
582 1
        return $this->camelize($this->singularize($tableName));
583
    }
584
585
    /**
586
     * Converts number to its ordinal English form. For example, converts 13 to 13th, 2 to 2nd ...
587
     * @param int $number the number to get its ordinal value
588
     * @return string
589
     */
590 1
    public function ordinalize(int $number): ?string
591
    {
592 1
        if (in_array($number % 100, range(11, 13), true)) {
593 1
            return $number . 'th';
594
        }
595 1
        switch ($number % 10) {
596 1
            case 1:
597 1
                return $number . 'st';
598 1
            case 2:
599 1
                return $number . 'nd';
600 1
            case 3:
601 1
                return $number . 'rd';
602
            default:
603 1
                return $number . 'th';
604
        }
605
    }
606
607
    /**
608
     * Converts a list of words into a sentence.
609
     *
610
     * Special treatment is done for the last few words. For example,
611
     *
612
     * ```php
613
     * $words = ['Spain', 'France'];
614
     * echo Inflector::sentence($words);
615
     * // output: Spain and France
616
     *
617
     * $words = ['Spain', 'France', 'Italy'];
618
     * echo Inflector::sentence($words);
619
     * // output: Spain, France and Italy
620
     *
621
     * $words = ['Spain', 'France', 'Italy'];
622
     * echo Inflector::sentence($words, ' & ');
623
     * // output: Spain, France & Italy
624
     * ```
625
     *
626
     * @param array $words the words to be converted into an string
627
     * @param string $twoWordsConnector the string connecting words when there are only two. Default to " and "
628
     * @param string $lastWordConnector the string connecting the last two words. If this is null, it will
629
     * take the value of `$twoWordsConnector`.
630
     * @param string $connector the string connecting words other than those connected by
631
     * $lastWordConnector and $twoWordsConnector
632
     * @return string the generated sentence
633
     */
634 1
    public function sentence(array $words, ?string $twoWordsConnector = ' and ', ?string $lastWordConnector = null, string $connector = ', '): ?string
635
    {
636 1
        if ($lastWordConnector === null) {
637 1
            $lastWordConnector = $twoWordsConnector;
638
        }
639 1
        switch (count($words)) {
640 1
            case 0:
641 1
                return '';
642 1
            case 1:
643 1
                return reset($words);
644 1
            case 2:
645 1
                return implode($twoWordsConnector, $words);
0 ignored issues
show
Bug introduced by
It seems like $twoWordsConnector can also be of type null; however, parameter $glue of implode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

645
                return implode(/** @scrutinizer ignore-type */ $twoWordsConnector, $words);
Loading history...
646
            default:
647 1
                return implode($connector, array_slice($words, 0, -1)) . $lastWordConnector . end($words);
648
        }
649
    }
650
}
651