1<?php
2/**
3 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
5 *
6 * Licensed under The MIT License
7 * For full copyright and license information, please see the LICENSE.txt
8 * Redistributions of files must retain the above copyright notice.
9 *
10 * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
11 * @link          https://cakephp.org CakePHP(tm) Project
12 * @since         0.2.9
13 * @license       https://opensource.org/licenses/mit-license.php MIT License
14 */
15namespace Cake\Utility;
16
17/**
18 * Pluralize and singularize English words.
19 *
20 * Inflector pluralizes and singularizes English nouns.
21 * Used by CakePHP's naming conventions throughout the framework.
22 *
23 * @link https://book.cakephp.org/3/en/core-libraries/inflector.html
24 */
25class Inflector
26{
27    /**
28     * Plural inflector rules
29     *
30     * @var array
31     */
32    protected static $_plural = [
33        '/(s)tatus$/i' => '\1tatuses',
34        '/(quiz)$/i' => '\1zes',
35        '/^(ox)$/i' => '\1\2en',
36        '/([m|l])ouse$/i' => '\1ice',
37        '/(matr|vert|ind)(ix|ex)$/i' => '\1ices',
38        '/(x|ch|ss|sh)$/i' => '\1es',
39        '/([^aeiouy]|qu)y$/i' => '\1ies',
40        '/(hive)$/i' => '\1s',
41        '/(chef)$/i' => '\1s',
42        '/(?:([^f])fe|([lre])f)$/i' => '\1\2ves',
43        '/sis$/i' => 'ses',
44        '/([ti])um$/i' => '\1a',
45        '/(p)erson$/i' => '\1eople',
46        '/(?<!u)(m)an$/i' => '\1en',
47        '/(c)hild$/i' => '\1hildren',
48        '/(buffal|tomat)o$/i' => '\1\2oes',
49        '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin)us$/i' => '\1i',
50        '/us$/i' => 'uses',
51        '/(alias)$/i' => '\1es',
52        '/(ax|cris|test)is$/i' => '\1es',
53        '/s$/' => 's',
54        '/^$/' => '',
55        '/$/' => 's',
56    ];
57
58    /**
59     * Singular inflector rules
60     *
61     * @var array
62     */
63    protected static $_singular = [
64        '/(s)tatuses$/i' => '\1\2tatus',
65        '/^(.*)(menu)s$/i' => '\1\2',
66        '/(quiz)zes$/i' => '\\1',
67        '/(matr)ices$/i' => '\1ix',
68        '/(vert|ind)ices$/i' => '\1ex',
69        '/^(ox)en/i' => '\1',
70        '/(alias)(es)*$/i' => '\1',
71        '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us',
72        '/([ftw]ax)es/i' => '\1',
73        '/(cris|ax|test)es$/i' => '\1is',
74        '/(shoe)s$/i' => '\1',
75        '/(o)es$/i' => '\1',
76        '/ouses$/' => 'ouse',
77        '/([^a])uses$/' => '\1us',
78        '/([m|l])ice$/i' => '\1ouse',
79        '/(x|ch|ss|sh)es$/i' => '\1',
80        '/(m)ovies$/i' => '\1\2ovie',
81        '/(s)eries$/i' => '\1\2eries',
82        '/([^aeiouy]|qu)ies$/i' => '\1y',
83        '/(tive)s$/i' => '\1',
84        '/(hive)s$/i' => '\1',
85        '/(drive)s$/i' => '\1',
86        '/([le])ves$/i' => '\1f',
87        '/([^rfoa])ves$/i' => '\1fe',
88        '/(^analy)ses$/i' => '\1sis',
89        '/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
90        '/([ti])a$/i' => '\1um',
91        '/(p)eople$/i' => '\1\2erson',
92        '/(m)en$/i' => '\1an',
93        '/(c)hildren$/i' => '\1\2hild',
94        '/(n)ews$/i' => '\1\2ews',
95        '/eaus$/' => 'eau',
96        '/^(.*us)$/' => '\\1',
97        '/s$/i' => '',
98    ];
99
100    /**
101     * Irregular rules
102     *
103     * @var array
104     */
105    protected static $_irregular = [
106        'atlas' => 'atlases',
107        'beef' => 'beefs',
108        'brief' => 'briefs',
109        'brother' => 'brothers',
110        'cafe' => 'cafes',
111        'child' => 'children',
112        'cookie' => 'cookies',
113        'corpus' => 'corpuses',
114        'cow' => 'cows',
115        'criterion' => 'criteria',
116        'ganglion' => 'ganglions',
117        'genie' => 'genies',
118        'genus' => 'genera',
119        'graffito' => 'graffiti',
120        'hoof' => 'hoofs',
121        'loaf' => 'loaves',
122        'man' => 'men',
123        'money' => 'monies',
124        'mongoose' => 'mongooses',
125        'move' => 'moves',
126        'mythos' => 'mythoi',
127        'niche' => 'niches',
128        'numen' => 'numina',
129        'occiput' => 'occiputs',
130        'octopus' => 'octopuses',
131        'opus' => 'opuses',
132        'ox' => 'oxen',
133        'penis' => 'penises',
134        'person' => 'people',
135        'sex' => 'sexes',
136        'soliloquy' => 'soliloquies',
137        'testis' => 'testes',
138        'trilby' => 'trilbys',
139        'turf' => 'turfs',
140        'potato' => 'potatoes',
141        'hero' => 'heroes',
142        'tooth' => 'teeth',
143        'goose' => 'geese',
144        'foot' => 'feet',
145        'foe' => 'foes',
146        'sieve' => 'sieves',
147        'cache' => 'caches',
148    ];
149
150    /**
151     * Words that should not be inflected
152     *
153     * @var array
154     */
155    protected static $_uninflected = [
156        '.*[nrlm]ese', '.*data', '.*deer', '.*fish', '.*measles', '.*ois',
157        '.*pox', '.*sheep', 'people', 'feedback', 'stadia', '.*?media',
158        'chassis', 'clippers', 'debris', 'diabetes', 'equipment', 'gallows',
159        'graffiti', 'headquarters', 'information', 'innings', 'news', 'nexus',
160        'pokemon', 'proceedings', 'research', 'sea[- ]bass', 'series', 'species', 'weather',
161    ];
162
163    /**
164     * Default map of accented and special characters to ASCII characters
165     *
166     * @var array
167     */
168    protected static $_transliteration = [
169        'ä' => 'ae',
170        'æ' => 'ae',
171        'ǽ' => 'ae',
172        'ö' => 'oe',
173        'œ' => 'oe',
174        'ü' => 'ue',
175        'Ä' => 'Ae',
176        'Ü' => 'Ue',
177        'Ö' => 'Oe',
178        'À' => 'A',
179        'Á' => 'A',
180        'Â' => 'A',
181        'Ã' => 'A',
182        'Å' => 'A',
183        'Ǻ' => 'A',
184        'Ā' => 'A',
185        'Ă' => 'A',
186        'Ą' => 'A',
187        'Ǎ' => 'A',
188        'à' => 'a',
189        'á' => 'a',
190        'â' => 'a',
191        'ã' => 'a',
192        'å' => 'a',
193        'ǻ' => 'a',
194        'ā' => 'a',
195        'ă' => 'a',
196        'ą' => 'a',
197        'ǎ' => 'a',
198        'ª' => 'a',
199        'Ç' => 'C',
200        'Ć' => 'C',
201        'Ĉ' => 'C',
202        'Ċ' => 'C',
203        'Č' => 'C',
204        'ç' => 'c',
205        'ć' => 'c',
206        'ĉ' => 'c',
207        'ċ' => 'c',
208        'č' => 'c',
209        'Ð' => 'D',
210        'Ď' => 'D',
211        'Đ' => 'D',
212        'ð' => 'd',
213        'ď' => 'd',
214        'đ' => 'd',
215        'È' => 'E',
216        'É' => 'E',
217        'Ê' => 'E',
218        'Ë' => 'E',
219        'Ē' => 'E',
220        'Ĕ' => 'E',
221        'Ė' => 'E',
222        'Ę' => 'E',
223        'Ě' => 'E',
224        'è' => 'e',
225        'é' => 'e',
226        'ê' => 'e',
227        'ë' => 'e',
228        'ē' => 'e',
229        'ĕ' => 'e',
230        'ė' => 'e',
231        'ę' => 'e',
232        'ě' => 'e',
233        'Ĝ' => 'G',
234        'Ğ' => 'G',
235        'Ġ' => 'G',
236        'Ģ' => 'G',
237        'Ґ' => 'G',
238        'ĝ' => 'g',
239        'ğ' => 'g',
240        'ġ' => 'g',
241        'ģ' => 'g',
242        'ґ' => 'g',
243        'Ĥ' => 'H',
244        'Ħ' => 'H',
245        'ĥ' => 'h',
246        'ħ' => 'h',
247        'І' => 'I',
248        'Ì' => 'I',
249        'Í' => 'I',
250        'Î' => 'I',
251        'Ї' => 'Yi',
252        'Ï' => 'I',
253        'Ĩ' => 'I',
254        'Ī' => 'I',
255        'Ĭ' => 'I',
256        'Ǐ' => 'I',
257        'Į' => 'I',
258        'İ' => 'I',
259        'і' => 'i',
260        'ì' => 'i',
261        'í' => 'i',
262        'î' => 'i',
263        'ï' => 'i',
264        'ї' => 'yi',
265        'ĩ' => 'i',
266        'ī' => 'i',
267        'ĭ' => 'i',
268        'ǐ' => 'i',
269        'į' => 'i',
270        'ı' => 'i',
271        'Ĵ' => 'J',
272        'ĵ' => 'j',
273        'Ķ' => 'K',
274        'ķ' => 'k',
275        'Ĺ' => 'L',
276        'Ļ' => 'L',
277        'Ľ' => 'L',
278        'Ŀ' => 'L',
279        'Ł' => 'L',
280        'ĺ' => 'l',
281        'ļ' => 'l',
282        'ľ' => 'l',
283        'ŀ' => 'l',
284        'ł' => 'l',
285        'Ñ' => 'N',
286        'Ń' => 'N',
287        'Ņ' => 'N',
288        'Ň' => 'N',
289        'ñ' => 'n',
290        'ń' => 'n',
291        'ņ' => 'n',
292        'ň' => 'n',
293        'ʼn' => 'n',
294        'Ò' => 'O',
295        'Ó' => 'O',
296        'Ô' => 'O',
297        'Õ' => 'O',
298        'Ō' => 'O',
299        'Ŏ' => 'O',
300        'Ǒ' => 'O',
301        'Ő' => 'O',
302        'Ơ' => 'O',
303        'Ø' => 'O',
304        'Ǿ' => 'O',
305        'ò' => 'o',
306        'ó' => 'o',
307        'ô' => 'o',
308        'õ' => 'o',
309        'ō' => 'o',
310        'ŏ' => 'o',
311        'ǒ' => 'o',
312        'ő' => 'o',
313        'ơ' => 'o',
314        'ø' => 'o',
315        'ǿ' => 'o',
316        'º' => 'o',
317        'Ŕ' => 'R',
318        'Ŗ' => 'R',
319        'Ř' => 'R',
320        'ŕ' => 'r',
321        'ŗ' => 'r',
322        'ř' => 'r',
323        'Ś' => 'S',
324        'Ŝ' => 'S',
325        'Ş' => 'S',
326        'Ș' => 'S',
327        'Š' => 'S',
328        'ẞ' => 'SS',
329        'ś' => 's',
330        'ŝ' => 's',
331        'ş' => 's',
332        'ș' => 's',
333        'š' => 's',
334        'ſ' => 's',
335        'Ţ' => 'T',
336        'Ț' => 'T',
337        'Ť' => 'T',
338        'Ŧ' => 'T',
339        'ţ' => 't',
340        'ț' => 't',
341        'ť' => 't',
342        'ŧ' => 't',
343        'Ù' => 'U',
344        'Ú' => 'U',
345        'Û' => 'U',
346        'Ũ' => 'U',
347        'Ū' => 'U',
348        'Ŭ' => 'U',
349        'Ů' => 'U',
350        'Ű' => 'U',
351        'Ų' => 'U',
352        'Ư' => 'U',
353        'Ǔ' => 'U',
354        'Ǖ' => 'U',
355        'Ǘ' => 'U',
356        'Ǚ' => 'U',
357        'Ǜ' => 'U',
358        'ù' => 'u',
359        'ú' => 'u',
360        'û' => 'u',
361        'ũ' => 'u',
362        'ū' => 'u',
363        'ŭ' => 'u',
364        'ů' => 'u',
365        'ű' => 'u',
366        'ų' => 'u',
367        'ư' => 'u',
368        'ǔ' => 'u',
369        'ǖ' => 'u',
370        'ǘ' => 'u',
371        'ǚ' => 'u',
372        'ǜ' => 'u',
373        'Ý' => 'Y',
374        'Ÿ' => 'Y',
375        'Ŷ' => 'Y',
376        'ý' => 'y',
377        'ÿ' => 'y',
378        'ŷ' => 'y',
379        'Ŵ' => 'W',
380        'ŵ' => 'w',
381        'Ź' => 'Z',
382        'Ż' => 'Z',
383        'Ž' => 'Z',
384        'ź' => 'z',
385        'ż' => 'z',
386        'ž' => 'z',
387        'Æ' => 'AE',
388        'Ǽ' => 'AE',
389        'ß' => 'ss',
390        'IJ' => 'IJ',
391        'ij' => 'ij',
392        'Œ' => 'OE',
393        'ƒ' => 'f',
394        'Þ' => 'TH',
395        'þ' => 'th',
396        'Є' => 'Ye',
397        'є' => 'ye',
398    ];
399
400    /**
401     * Method cache array.
402     *
403     * @var array
404     */
405    protected static $_cache = [];
406
407    /**
408     * The initial state of Inflector so reset() works.
409     *
410     * @var array
411     */
412    protected static $_initialState = [];
413
414    /**
415     * Cache inflected values, and return if already available
416     *
417     * @param string $type Inflection type
418     * @param string $key Original value
419     * @param string|false $value Inflected value
420     * @return string|false Inflected value on cache hit or false on cache miss.
421     */
422    protected static function _cache($type, $key, $value = false)
423    {
424        $key = '_' . $key;
425        $type = '_' . $type;
426        if ($value !== false) {
427            static::$_cache[$type][$key] = $value;
428
429            return $value;
430        }
431        if (!isset(static::$_cache[$type][$key])) {
432            return false;
433        }
434
435        return static::$_cache[$type][$key];
436    }
437
438    /**
439     * Clears Inflectors inflected value caches. And resets the inflection
440     * rules to the initial values.
441     *
442     * @return void
443     */
444    public static function reset()
445    {
446        if (empty(static::$_initialState)) {
447            static::$_initialState = get_class_vars(__CLASS__);
448
449            return;
450        }
451        foreach (static::$_initialState as $key => $val) {
452            if ($key !== '_initialState') {
453                static::${$key} = $val;
454            }
455        }
456    }
457
458    /**
459     * Adds custom inflection $rules, of either 'plural', 'singular',
460     * 'uninflected', 'irregular' or 'transliteration' $type.
461     *
462     * ### Usage:
463     *
464     * ```
465     * Inflector::rules('plural', ['/^(inflect)or$/i' => '\1ables']);
466     * Inflector::rules('irregular', ['red' => 'redlings']);
467     * Inflector::rules('uninflected', ['dontinflectme']);
468     * Inflector::rules('transliteration', ['/å/' => 'aa']);
469     * ```
470     *
471     * @param string $type The type of inflection, either 'plural', 'singular',
472     *   'uninflected' or 'transliteration'.
473     * @param array $rules Array of rules to be added.
474     * @param bool $reset If true, will unset default inflections for all
475     *        new rules that are being defined in $rules.
476     * @return void
477     */
478    public static function rules($type, $rules, $reset = false)
479    {
480        $var = '_' . $type;
481
482        if ($reset) {
483            static::${$var} = $rules;
484        } elseif ($type === 'uninflected') {
485            static::$_uninflected = array_merge(
486                $rules,
487                static::$_uninflected
488            );
489        } else {
490            static::${$var} = $rules + static::${$var};
491        }
492
493        static::$_cache = [];
494    }
495
496    /**
497     * Return $word in plural form.
498     *
499     * @param string $word Word in singular
500     * @return string Word in plural
501     * @link https://book.cakephp.org/3/en/core-libraries/inflector.html#creating-plural-singular-forms
502     */
503    public static function pluralize($word)
504    {
505        if (isset(static::$_cache['pluralize'][$word])) {
506            return static::$_cache['pluralize'][$word];
507        }
508
509        if (!isset(static::$_cache['irregular']['pluralize'])) {
510            $words = array_keys(static::$_irregular);
511            static::$_cache['irregular']['pluralize'] = '/(.*?(?:\\b|_))(' . implode('|', $words) . ')$/i';
512
513            $upperWords = array_map('ucfirst', $words);
514            static::$_cache['irregular']['upperPluralize'] = '/(.*?(?:\\b|[a-z]))(' . implode('|', $upperWords) . ')$/';
515        }
516
517        if (
518            preg_match(static::$_cache['irregular']['pluralize'], $word, $regs) ||
519            preg_match(static::$_cache['irregular']['upperPluralize'], $word, $regs)
520        ) {
521            static::$_cache['pluralize'][$word] = $regs[1] . substr($regs[2], 0, 1) .
522                substr(static::$_irregular[strtolower($regs[2])], 1);
523
524            return static::$_cache['pluralize'][$word];
525        }
526
527        if (!isset(static::$_cache['uninflected'])) {
528            static::$_cache['uninflected'] = '/^(' . implode('|', static::$_uninflected) . ')$/i';
529        }
530
531        if (preg_match(static::$_cache['uninflected'], $word, $regs)) {
532            static::$_cache['pluralize'][$word] = $word;
533
534            return $word;
535        }
536
537        foreach (static::$_plural as $rule => $replacement) {
538            if (preg_match($rule, $word)) {
539                static::$_cache['pluralize'][$word] = preg_replace($rule, $replacement, $word);
540
541                return static::$_cache['pluralize'][$word];
542            }
543        }
544    }
545
546    /**
547     * Return $word in singular form.
548     *
549     * @param string $word Word in plural
550     * @return string Word in singular
551     * @link https://book.cakephp.org/3/en/core-libraries/inflector.html#creating-plural-singular-forms
552     */
553    public static function singularize($word)
554    {
555        if (isset(static::$_cache['singularize'][$word])) {
556            return static::$_cache['singularize'][$word];
557        }
558
559        if (!isset(static::$_cache['irregular']['singular'])) {
560            $wordList = array_values(static::$_irregular);
561            static::$_cache['irregular']['singular'] = '/(.*?(?:\\b|_))(' . implode('|', $wordList) . ')$/i';
562
563            $upperWordList = array_map('ucfirst', $wordList);
564            static::$_cache['irregular']['singularUpper'] = '/(.*?(?:\\b|[a-z]))(' .
565                implode('|', $upperWordList) .
566                ')$/';
567        }
568
569        if (
570            preg_match(static::$_cache['irregular']['singular'], $word, $regs) ||
571            preg_match(static::$_cache['irregular']['singularUpper'], $word, $regs)
572        ) {
573            static::$_cache['singularize'][$word] = $regs[1] . substr($regs[2], 0, 1) .
574                substr(array_search(strtolower($regs[2]), static::$_irregular, true), 1);
575
576            return static::$_cache['singularize'][$word];
577        }
578
579        if (!isset(static::$_cache['uninflected'])) {
580            static::$_cache['uninflected'] = '/^(' . implode('|', static::$_uninflected) . ')$/i';
581        }
582
583        if (preg_match(static::$_cache['uninflected'], $word, $regs)) {
584            static::$_cache['pluralize'][$word] = $word;
585
586            return $word;
587        }
588
589        foreach (static::$_singular as $rule => $replacement) {
590            if (preg_match($rule, $word)) {
591                static::$_cache['singularize'][$word] = preg_replace($rule, $replacement, $word);
592
593                return static::$_cache['singularize'][$word];
594            }
595        }
596        static::$_cache['singularize'][$word] = $word;
597
598        return $word;
599    }
600
601    /**
602     * Returns the input lower_case_delimited_string as a CamelCasedString.
603     *
604     * @param string $string String to camelize
605     * @param string $delimiter the delimiter in the input string
606     * @return string CamelizedStringLikeThis.
607     * @link https://book.cakephp.org/3/en/core-libraries/inflector.html#creating-camelcase-and-under-scored-forms
608     */
609    public static function camelize($string, $delimiter = '_')
610    {
611        $cacheKey = __FUNCTION__ . $delimiter;
612
613        $result = static::_cache($cacheKey, $string);
614
615        if ($result === false) {
616            $result = str_replace(' ', '', static::humanize($string, $delimiter));
617            static::_cache($cacheKey, $string, $result);
618        }
619
620        return $result;
621    }
622
623    /**
624     * Returns the input CamelCasedString as an underscored_string.
625     *
626     * Also replaces dashes with underscores
627     *
628     * @param string $string CamelCasedString to be "underscorized"
629     * @return string underscore_version of the input string
630     * @link https://book.cakephp.org/3/en/core-libraries/inflector.html#creating-camelcase-and-under-scored-forms
631     */
632    public static function underscore($string)
633    {
634        return static::delimit(str_replace('-', '_', $string), '_');
635    }
636
637    /**
638     * Returns the input CamelCasedString as an dashed-string.
639     *
640     * Also replaces underscores with dashes
641     *
642     * @param string $string The string to dasherize.
643     * @return string Dashed version of the input string
644     */
645    public static function dasherize($string)
646    {
647        return static::delimit(str_replace('_', '-', $string), '-');
648    }
649
650    /**
651     * Returns the input lower_case_delimited_string as 'A Human Readable String'.
652     * (Underscores are replaced by spaces and capitalized following words.)
653     *
654     * @param string $string String to be humanized
655     * @param string $delimiter the character to replace with a space
656     * @return string Human-readable string
657     * @link https://book.cakephp.org/3/en/core-libraries/inflector.html#creating-human-readable-forms
658     */
659    public static function humanize($string, $delimiter = '_')
660    {
661        $cacheKey = __FUNCTION__ . $delimiter;
662
663        $result = static::_cache($cacheKey, $string);
664
665        if ($result === false) {
666            $result = explode(' ', str_replace($delimiter, ' ', $string));
667            foreach ($result as &$word) {
668                $word = mb_strtoupper(mb_substr($word, 0, 1)) . mb_substr($word, 1);
669            }
670            $result = implode(' ', $result);
671            static::_cache($cacheKey, $string, $result);
672        }
673
674        return $result;
675    }
676
677    /**
678     * Expects a CamelCasedInputString, and produces a lower_case_delimited_string
679     *
680     * @param string $string String to delimit
681     * @param string $delimiter the character to use as a delimiter
682     * @return string delimited string
683     */
684    public static function delimit($string, $delimiter = '_')
685    {
686        $cacheKey = __FUNCTION__ . $delimiter;
687
688        $result = static::_cache($cacheKey, $string);
689
690        if ($result === false) {
691            $result = mb_strtolower(preg_replace('/(?<=\\w)([A-Z])/', $delimiter . '\\1', $string));
692            static::_cache($cacheKey, $string, $result);
693        }
694
695        return $result;
696    }
697
698    /**
699     * Returns corresponding table name for given model $className. ("people" for the model class "Person").
700     *
701     * @param string $className Name of class to get database table name for
702     * @return string Name of the database table for given class
703     * @link https://book.cakephp.org/3/en/core-libraries/inflector.html#creating-table-and-class-name-forms
704     */
705    public static function tableize($className)
706    {
707        $result = static::_cache(__FUNCTION__, $className);
708
709        if ($result === false) {
710            $result = static::pluralize(static::underscore($className));
711            static::_cache(__FUNCTION__, $className, $result);
712        }
713
714        return $result;
715    }
716
717    /**
718     * Returns Cake model class name ("Person" for the database table "people".) for given database table.
719     *
720     * @param string $tableName Name of database table to get class name for
721     * @return string Class name
722     * @link https://book.cakephp.org/3/en/core-libraries/inflector.html#creating-table-and-class-name-forms
723     */
724    public static function classify($tableName)
725    {
726        $result = static::_cache(__FUNCTION__, $tableName);
727
728        if ($result === false) {
729            $result = static::camelize(static::singularize($tableName));
730            static::_cache(__FUNCTION__, $tableName, $result);
731        }
732
733        return $result;
734    }
735
736    /**
737     * Returns camelBacked version of an underscored string.
738     *
739     * @param string $string String to convert.
740     * @return string in variable form
741     * @link https://book.cakephp.org/3/en/core-libraries/inflector.html#creating-variable-names
742     */
743    public static function variable($string)
744    {
745        $result = static::_cache(__FUNCTION__, $string);
746
747        if ($result === false) {
748            $camelized = static::camelize(static::underscore($string));
749            $replace = strtolower(substr($camelized, 0, 1));
750            $result = $replace . substr($camelized, 1);
751            static::_cache(__FUNCTION__, $string, $result);
752        }
753
754        return $result;
755    }
756
757    /**
758     * Returns a string with all spaces converted to dashes (by default), accented
759     * characters converted to non-accented characters, and non word characters removed.
760     *
761     * @deprecated 3.2.7 Use Text::slug() instead.
762     * @param string $string the string you want to slug
763     * @param string $replacement will replace keys in map
764     * @return string
765     * @link https://book.cakephp.org/3/en/core-libraries/inflector.html#creating-url-safe-strings
766     */
767    public static function slug($string, $replacement = '-')
768    {
769        deprecationWarning(
770            'Inflector::slug() is deprecated. ' .
771            'Use Text::slug() instead.'
772        );
773        $quotedReplacement = preg_quote($replacement, '/');
774
775        $map = [
776            '/[^\s\p{Zs}\p{Ll}\p{Lm}\p{Lo}\p{Lt}\p{Lu}\p{Nd}]/mu' => ' ',
777            '/[\s\p{Zs}]+/mu' => $replacement,
778            sprintf('/^[%s]+|[%s]+$/', $quotedReplacement, $quotedReplacement) => '',
779        ];
780
781        $string = str_replace(
782            array_keys(static::$_transliteration),
783            static::$_transliteration,
784            $string
785        );
786
787        return preg_replace(array_keys($map), array_values($map), $string);
788    }
789}
790