-
Notifications
You must be signed in to change notification settings - Fork 0
/
Inflector.php
444 lines (399 loc) · 14.1 KB
/
Inflector.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
<?php
/**
* Qubus\Support
*
* @link https://github.com/QubusPHP/support
* @copyright 2022
* @author Joshua Parker <joshua@joshuaparker.dev>
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
declare(strict_types=1);
namespace Qubus\Support;
use Cocur\Slugify\Slugify;
use function html_entity_decode;
use function in_array;
use function is_array;
use function is_numeric;
use function preg_match;
use function preg_replace;
use function preg_replace_callback;
use function Qubus\Support\Helpers\remove_accents;
use function range;
use function str_replace;
use function strip_tags;
use function strrpos;
use function strtolower;
use function strtoupper;
use function strval;
use function substr;
use function trim;
use function ucfirst;
use function ucwords;
use const ENT_QUOTES;
/**
* Pluralize and singularize English words.
*/
class Inflector
{
/** @var array default list of uncountable words, in English */
protected static array $uncountableWords = [
'equipment',
'information',
'rice',
'money',
'species',
'series',
'fish',
'meta',
'feedback',
'people',
'stadia',
'chassis',
'clippers',
'debris',
'diabetes',
'gallows',
'graffiti',
'headquarters',
'innings',
'news',
'nexus',
'proceedings',
'research',
'weather',
];
/** @var array Default list of irregular plural words, in English */
protected static array $pluralRules = [
'/^(ox)$/i' => '\1\2en', // ox
'/([m|l])ouse$/i' => '\1ice', // mouse, louse
'/(matr|vert|ind)ix|ex$/i' => '\1ices', // matrix, vertex, index
'/(x|ch|ss|sh)$/i' => '\1es', // search, switch, fix, box, process, address
'/([^aeiouy]|qu)y$/i' => '\1ies', // query, ability, agency
'/(hive)$/i' => '\1s', // archive, hive
'/(chef)$/i' => '\1s', // chef
'/(?:([^f])fe|([lr])f)$/i' => '\1\2ves', // half, safe, wife
'/sis$/i' => 'ses', // basis, diagnosis
'/([ti])um$/i' => '\1a', // datum, medium
'/(p)erson$/i' => '\1eople', // person, salesperson
'/(m)an$/i' => '\1en', // man, woman, spokesman
'/(c)hild$/i' => '\1hildren', // child
'/(buffal|tomat)o$/i' => '\1\2oes', // buffalo, tomato
'/(bu|campu)s$/i' => '\1\2ses', // bus, campus
'/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin)us$/i' => '\1i', // alumnus, cactus, fungus
'/(alias|status|virus)$/i' => '\1es', // alias
'/(octop)us$/i' => '\1i', // octopus
'/(ax|cris|test)is$/i' => '\1es', // axis, crisis
'/(quiz)$/i' => '\1zes', // quiz
'/s$/' => 's', // no change (compatibility)
'/^$/' => '',
'/$/' => 's',
];
/** @var array default list of irregular singular words, in English */
protected static array $singularRules = [
'/(matr)ices$/i' => '\1ix',
'/(s)tatuses$/i' => '\1\2tatus',
'/^(.*)(menu)s$/i' => '\1\2',
'/(quiz)zes$/i' => '\\1',
'/(vert|ind)ices$/i' => '\1ex',
'/^(ox)en/i' => '\1',
'/(alias)es$/i' => '\1',
'/([octop|vir])i$/i' => '\1us',
'/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us',
'/([ftw]ax)es/i' => '\1',
'/(cris|ax|test)es$/i' => '\1is',
'/(shoe)s$/i' => '\1',
'/(o)es$/i' => '\1',
'/(bus|campus)es$/i' => '\1',
'/([^a])uses$/' => '\1us',
'/ouses$/' => 'ouse',
'/([m|l])ice$/i' => '\1ouse',
'/(x|ch|ss|sh)es$/i' => '\1',
'/(m)ovies$/i' => '\1\2ovie',
'/(s)eries$/i' => '\1\2eries',
'/([^aeiouy]|qu)ies$/i' => '\1y',
'/([lr])ves$/i' => '\1f',
'/(tive)s$/i' => '\1',
'/(hive)s$/i' => '\1',
'/(drive)s$/i' => '\1',
'/([^f])ves$/i' => '\1fe',
'/([le])ves$/i' => '\1f',
'/([^rfoa])ves$/i' => '\1fe',
'/(^analy)ses$/i' => '\1sis',
'/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
'/([ti])a$/i' => '\1um',
'/(p)eople$/i' => '\1\2erson',
'/(m)en$/i' => '\1an',
'/(s)tatuses$/i' => '\1\2tatus',
'/(c)hildren$/i' => '\1\2hild',
'/(n)ews$/i' => '\1\2ews',
'/eaus$/' => 'eau',
'/([^us])s$/i' => '\1',
'/^(.*us)$/' => '\\1',
'/s$/i' => '',
];
protected static bool $init = false;
/**
* Load any localized rulesets based on the current language configuration
* If not exists, the current rules remain active
*/
public static function initialize(): void
{
/** @todo */
static::$init = true;
}
/**
* Add order suffix to numbers ex. 1st 2nd 3rd 4th 5th.
*
* @link http://snipplr.com/view/4627/a-function-to-add-a-prefix-to-numbers-ex-1st-2nd-3rd-4th-5th/
*
* @param int $number the number to ordinalize
* @return string the ordinalized version of $number
*/
public static function ordinalize(int $number): string
{
if (! is_numeric($number)) {
return (string) $number;
}
if (in_array($number % 100, range(11, 13))) {
return $number . 'th';
} else {
return match ($number % 10) {
1 => $number . 'st',
2 => $number . 'nd',
3 => $number . 'rd',
default => $number . 'th',
};
}
}
/**
* Gets the plural version of the given word.
*
* @param string $word the word to pluralize
* @param int $count number of instances
* @return string the plural version of $word
*/
public static function pluralize(string $word, int $count = 0): string
{
static::$init || static::initialize();
$result = strval($word);
// If a counter is provided, and that equals 1
// return as singular.
if ($count === 1) {
return $result;
}
if (! static::isCountable($result)) {
return $result;
}
foreach (static::$pluralRules as $rule => $replacement) {
if (preg_match($rule, $result)) {
$result = preg_replace($rule, $replacement, $result);
break;
}
}
return $result;
}
/**
* Gets the singular version of the given word
*
* @param string $word the word to singularize
* @return string the singular version of $word
*/
public static function singularize(string $word): string
{
static::$init || static::initialize();
$result = strval($word);
if (! static::isCountable($result)) {
return $result;
}
foreach (static::$singularRules as $rule => $replacement) {
if (preg_match($rule, $result)) {
$result = preg_replace($rule, $replacement, $result);
break;
}
}
return $result;
}
/**
* Takes a string that has words separated by underscores and turns it into
* a CamelCased string.
*
* @param string $underscoredWord the underscored word
* @return string the CamelCased version of $underscoredWord
*/
public static function camelize(string $underscoredWord): string
{
return preg_replace_callback(
'/(^|_)(.)/',
function ($parm) {
return strtoupper($parm[2]);
},
strval($underscoredWord)
);
}
/**
* Takes a CamelCased string and returns an underscore separated version.
*
* @param string $camelCasedWord the CamelCased word
* @return string an underscore separated version of $camelCasedWord
*/
public static function underscore(string $camelCasedWord): string
{
return strtolower(
preg_replace(
'/([A-Z]+)([A-Z])/',
'\1_\2',
preg_replace(
'/([a-z\d])([A-Z])/',
'\1_\2',
strval($camelCasedWord)
)
)
);
}
/**
* Translate string to 7-bit ASCII.
*
* Only works with UTF-8.
*
* @param string $str String to translate
* @param bool $allowNonAscii Whether to remove non ascii
* @return string Translated string.
*/
public static function ascii(string $str, bool $allowNonAscii = false): string
{
// Translate unicode characters to their simpler counterparts
$str = remove_accents($str);
if (! $allowNonAscii) {
return preg_replace('/[^\x09\x0A\x0D\x20-\x7E]/', '', $str);
}
return $str;
}
/**
* Converts your text to a URL-friendly title so it can be used in the URL.
* Only works with UTF8 input and only outputs 7 bit ASCII characters.
*
* @param string $string The text to slugify.
* @param array $constructorOptions Options that can be passed to the constructor.
* @param string|array|null $onTheFlyOptions Override options that can be passed to slugify method.
* @return string The slugified text.
*/
public static function slugify(
string|array $string,
array $constructorOptions = [],
string|array|null $onTheFlyOptions = null
): string {
// Sanitize string.
if (! is_array($string)) {
$string = htmlspecialchars($string);
}
// Remove tags
if (is_array($string)) {
foreach ($string as $k => $v) {
$string[$k] = strip_tags($v);
}
}
// Decode all entities to their simpler forms
$string = html_entity_decode($string, ENT_QUOTES, 'UTF-8');
return (new Slugify($constructorOptions))->slugify($string, $onTheFlyOptions);
}
/**
* Turns an underscore or dash separated word and turns it into a human looking string.
*
* @param string $str the word
* @param string $sep the separator (either _ or -)
* @param bool $lowercase lowercase string and upper case first
* @return string the human version of given string
*/
public static function humanize(string $str, string $sep = '_', bool $lowercase = true): string
{
// Allow dash, otherwise default to underscore
$sep = $sep !== '-' ? '_' : $sep;
if ($lowercase === true) {
$str = ucfirst($str);
}
return str_replace($sep, " ", strval($str));
}
/**
* Takes the class name out of a modulized string.
*
* @param string $classNameInModule the modulized class
* @return string the string without the class name
*/
public static function demodulize(string $classNameInModule): string
{
return preg_replace('/^.*::/', '', strval($classNameInModule));
}
/**
* Takes the namespace off the given class name.
*
* @param string $className the class name
* @return string the string without the namespace
*/
public static function denamespace(string $className): string
{
$className = trim($className, '\\');
if ($lastSeparator = strrpos($className, '\\')) {
$className = substr($className, $lastSeparator + 1);
}
return $className;
}
/**
* Returns the namespace of the given class name.
*
* @param string $className the class name
* @return string the string without the namespace
*/
public static function getNamespace(string $className): string
{
$className = trim($className, '\\');
if ($lastSeparator = strrpos($className, '\\')) {
return substr($className, 0, $lastSeparator + 1);
}
return '';
}
/**
* Takes a class name and determines the table name. The table name is a
* pluralized version of the class name.
*
* @param string $className The table name.
* @return string The table name.
*/
public static function tableize(string $className): string
{
$className = static::denamespace($className);
return strtolower(static::pluralize(static::underscore($className)));
}
/**
* Takes an underscored classname and uppercases all letters after the underscores.
*
* @param string $class classname
* @param string $sep separator
*/
public static function wordsToUpper(string $class, string $sep = '_'): string
{
return str_replace(' ', $sep, ucwords(str_replace($sep, ' ', $class)));
}
/**
* Takes a table name and creates the class name.
*
* @param string $name the table name
* @param bool $forceSingular whether to singularize the table name or not
* @return string the class name
*/
public static function classify(string $name, bool $forceSingular = true): string
{
$class = $forceSingular ? static::singularize($name) : $name;
return static::wordsToUpper($class);
}
/**
* Checks if the given word has a plural version.
*
* @param string $word the word to check
* @return bool if the word is countable
*/
public static function isCountable(string $word): bool
{
static::$init || static::initialize();
return ! in_array(strtolower(strval($word)), static::$uncountableWords);
}
}