Rules
1 month ago
CachedWordInflector.php
1 month ago
GenericLanguageInflectorFactory.php
1 month ago
Inflector.php
1 month ago
InflectorFactory.php
1 month ago
Language.php
1 month ago
LanguageInflectorFactory.php
1 month ago
NoopWordInflector.php
1 month ago
RulesetInflector.php
1 month ago
WordInflector.php
1 month ago
Inflector.php
209 lines
| 1 | <?php |
| 2 | |
| 3 | declare (strict_types=1); |
| 4 | namespace IAWPSCOPED\Doctrine\Inflector; |
| 5 | |
| 6 | use RuntimeException; |
| 7 | use function chr; |
| 8 | use function function_exists; |
| 9 | use function lcfirst; |
| 10 | use function mb_strtolower; |
| 11 | use function ord; |
| 12 | use function preg_match; |
| 13 | use function preg_replace; |
| 14 | use function sprintf; |
| 15 | use function str_replace; |
| 16 | use function strlen; |
| 17 | use function strtolower; |
| 18 | use function strtr; |
| 19 | use function trim; |
| 20 | use function ucwords; |
| 21 | /** @internal */ |
| 22 | class Inflector |
| 23 | { |
| 24 | private const ACCENTED_CHARACTERS = ['À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'Ae', 'Æ' => 'Ae', '� |
| 25 | ' => 'Aa', 'æ' => 'a', 'Ç' => 'C', 'È' => 'E', 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E', 'Ì' => 'I', 'Í' => 'I', 'Î' => 'I', 'Ï' => 'I', 'Ñ' => 'N', 'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ö' => 'Oe', 'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', 'Ü' => 'Ue', 'Ý' => 'Y', 'ß' => 'ss', 'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a', 'ä' => 'ae', 'å' => 'aa', 'ç' => 'c', 'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e', 'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i', 'ñ' => 'n', 'ò' => 'o', 'ó' => 'o', 'ô' => 'o', 'õ' => 'o', 'ö' => 'oe', 'ù' => 'u', 'ú' => 'u', 'û' => 'u', 'ü' => 'ue', 'ý' => 'y', 'ÿ' => 'y', 'Ā' => 'A', 'ā' => 'a', 'Ă' => 'A', 'ă' => 'a', 'Ą' => 'A', '� |
| 26 | ' => 'a', 'Ć' => 'C', 'ć' => 'c', 'Ĉ' => 'C', 'ĉ' => 'c', 'Ċ' => 'C', 'ċ' => 'c', 'Č' => 'C', 'č' => 'c', 'Ď' => 'D', 'ď' => 'd', 'Đ' => 'D', 'đ' => 'd', 'Ē' => 'E', 'ē' => 'e', 'Ĕ' => 'E', 'ĕ' => 'e', 'Ė' => 'E', 'ė' => 'e', 'Ę' => 'E', 'ę' => 'e', 'Ě' => 'E', 'ě' => 'e', 'Ĝ' => 'G', 'ĝ' => 'g', 'Ğ' => 'G', 'ğ' => 'g', 'Ġ' => 'G', 'ġ' => 'g', 'Ģ' => 'G', 'ģ' => 'g', 'Ĥ' => 'H', 'ĥ' => 'h', 'Ħ' => 'H', 'ħ' => 'h', 'Ĩ' => 'I', 'ĩ' => 'i', 'Ī' => 'I', 'ī' => 'i', 'Ĭ' => 'I', 'ĭ' => 'i', 'Į' => 'I', 'į' => 'i', 'İ' => 'I', 'ı' => 'i', 'IJ' => 'IJ', 'ij' => 'ij', 'Ĵ' => 'J', 'ĵ' => 'j', 'Ķ' => 'K', 'ķ' => 'k', 'ĸ' => 'k', 'Ĺ' => 'L', 'ĺ' => 'l', 'Ļ' => 'L', 'ļ' => 'l', 'Ľ' => 'L', 'ľ' => 'l', 'Ŀ' => 'L', 'ŀ' => 'l', 'Ł' => 'L', 'ł' => 'l', 'Ń' => 'N', 'ń' => 'n', '� |
| 27 | ' => 'N', 'ņ' => 'n', 'Ň' => 'N', 'ň' => 'n', 'ʼn' => 'N', 'Ŋ' => 'n', 'ŋ' => 'N', 'Ō' => 'O', 'ō' => 'o', 'Ŏ' => 'O', 'ŏ' => 'o', 'Ő' => 'O', 'ő' => 'o', 'Œ' => 'OE', 'œ' => 'oe', 'Ø' => 'O', 'ø' => 'o', 'Ŕ' => 'R', 'ŕ' => 'r', 'Ŗ' => 'R', 'ŗ' => 'r', 'Ř' => 'R', 'ř' => 'r', 'Ś' => 'S', 'ś' => 's', 'Ŝ' => 'S', 'ŝ' => 's', 'Ş' => 'S', 'ş' => 's', 'Š' => 'S', 'š' => 's', 'Ţ' => 'T', 'ţ' => 't', 'Ť' => 'T', 'ť' => 't', 'Ŧ' => 'T', 'ŧ' => 't', 'Ũ' => 'U', 'ũ' => 'u', 'Ū' => 'U', 'ū' => 'u', 'Ŭ' => 'U', 'ŭ' => 'u', 'Ů' => 'U', 'ů' => 'u', 'Ű' => 'U', 'ű' => 'u', 'Ų' => 'U', 'ų' => 'u', 'Ŵ' => 'W', 'ŵ' => 'w', 'Ŷ' => 'Y', 'ŷ' => 'y', 'Ÿ' => 'Y', 'Ź' => 'Z', 'ź' => 'z', 'Ż' => 'Z', 'ż' => 'z', 'Ž' => 'Z', 'ž' => 'z', 'ſ' => 's', '€' => 'E', '£' => '']; |
| 28 | /** @var WordInflector */ |
| 29 | private $singularizer; |
| 30 | /** @var WordInflector */ |
| 31 | private $pluralizer; |
| 32 | public function __construct(WordInflector $singularizer, WordInflector $pluralizer) |
| 33 | { |
| 34 | $this->singularizer = $singularizer; |
| 35 | $this->pluralizer = $pluralizer; |
| 36 | } |
| 37 | /** |
| 38 | * Converts a word into the format for a Doctrine table name. Converts 'ModelName' to 'model_name'. |
| 39 | */ |
| 40 | public function tableize(string $word) : string |
| 41 | { |
| 42 | $tableized = preg_replace('~(?<=\\w)([A-Z])~u', '_$1', $word); |
| 43 | if ($tableized === null) { |
| 44 | throw new RuntimeException(sprintf('preg_replace returned null for value "%s"', $word)); |
| 45 | } |
| 46 | return mb_strtolower($tableized); |
| 47 | } |
| 48 | /** |
| 49 | * Converts a word into the format for a Doctrine class name. Converts 'table_name' to 'TableName'. |
| 50 | */ |
| 51 | public function classify(string $word) : string |
| 52 | { |
| 53 | return str_replace([' ', '_', '-'], '', ucwords($word, ' _-')); |
| 54 | } |
| 55 | /** |
| 56 | * Camelizes a word. This uses the classify() method and turns the first character to lowercase. |
| 57 | */ |
| 58 | public function camelize(string $word) : string |
| 59 | { |
| 60 | return lcfirst($this->classify($word)); |
| 61 | } |
| 62 | /** |
| 63 | * Uppercases words with configurable delimiters between words. |
| 64 | * |
| 65 | * Takes a string and capitalizes all of the words, like PHP's built-in |
| 66 | * ucwords function. This extends that behavior, however, by allowing the |
| 67 | * word delimiters to be configured, rather than only separating on |
| 68 | * whitespace. |
| 69 | * |
| 70 | * Here is an example: |
| 71 | * <code> |
| 72 | * <?php |
| 73 | * $string = 'top-o-the-morning to all_of_you!'; |
| 74 | * echo $inflector->capitalize($string); |
| 75 | * // Top-O-The-Morning To All_of_you! |
| 76 | * |
| 77 | * echo $inflector->capitalize($string, '-_ '); |
| 78 | * // Top-O-The-Morning To All_Of_You! |
| 79 | * ?> |
| 80 | * </code> |
| 81 | * |
| 82 | * @param string $string The string to operate on. |
| 83 | * @param string $delimiters A list of word separators. |
| 84 | * |
| 85 | * @return string The string with all delimiter-separated words capitalized. |
| 86 | */ |
| 87 | public function capitalize(string $string, string $delimiters = " \n\t\r\x00\v-") : string |
| 88 | { |
| 89 | return ucwords($string, $delimiters); |
| 90 | } |
| 91 | /** |
| 92 | * Checks if the given string seems like it has utf8 characters in it. |
| 93 | * |
| 94 | * @param string $string The string to check for utf8 characters in. |
| 95 | */ |
| 96 | public function seemsUtf8(string $string) : bool |
| 97 | { |
| 98 | for ($i = 0; $i < strlen($string); $i++) { |
| 99 | if (ord($string[$i]) < 0x80) { |
| 100 | continue; |
| 101 | // 0bbbbbbb |
| 102 | } |
| 103 | if ((ord($string[$i]) & 0xe0) === 0xc0) { |
| 104 | $n = 1; |
| 105 | // 110bbbbb |
| 106 | } elseif ((ord($string[$i]) & 0xf0) === 0xe0) { |
| 107 | $n = 2; |
| 108 | // 1110bbbb |
| 109 | } elseif ((ord($string[$i]) & 0xf8) === 0xf0) { |
| 110 | $n = 3; |
| 111 | // 11110bbb |
| 112 | } elseif ((ord($string[$i]) & 0xfc) === 0xf8) { |
| 113 | $n = 4; |
| 114 | // 111110bb |
| 115 | } elseif ((ord($string[$i]) & 0xfe) === 0xfc) { |
| 116 | $n = 5; |
| 117 | // 1111110b |
| 118 | } else { |
| 119 | return \false; |
| 120 | // Does not match any model |
| 121 | } |
| 122 | for ($j = 0; $j < $n; $j++) { |
| 123 | // n bytes matching 10bbbbbb follow ? |
| 124 | if (++$i === strlen($string) || (ord($string[$i]) & 0xc0) !== 0x80) { |
| 125 | return \false; |
| 126 | } |
| 127 | } |
| 128 | } |
| 129 | return \true; |
| 130 | } |
| 131 | /** |
| 132 | * Remove any illegal characters, accents, etc. |
| 133 | * |
| 134 | * @param string $string String to unaccent |
| 135 | * |
| 136 | * @return string Unaccented string |
| 137 | */ |
| 138 | public function unaccent(string $string) : string |
| 139 | { |
| 140 | if (preg_match('/[\\x80-\\xff]/', $string) === \false) { |
| 141 | return $string; |
| 142 | } |
| 143 | if ($this->seemsUtf8($string)) { |
| 144 | $string = strtr($string, self::ACCENTED_CHARACTERS); |
| 145 | } else { |
| 146 | $characters = []; |
| 147 | // Assume ISO-8859-1 if not UTF-8 |
| 148 | $characters['in'] = chr(128) . chr(131) . chr(138) . chr(142) . chr(154) . chr(158) . chr(159) . chr(162) . chr(165) . chr(181) . chr(192) . chr(193) . chr(194) . chr(195) . chr(196) . chr(197) . chr(199) . chr(200) . chr(201) . chr(202) . chr(203) . chr(204) . chr(205) . chr(206) . chr(207) . chr(209) . chr(210) . chr(211) . chr(212) . chr(213) . chr(214) . chr(216) . chr(217) . chr(218) . chr(219) . chr(220) . chr(221) . chr(224) . chr(225) . chr(226) . chr(227) . chr(228) . chr(229) . chr(231) . chr(232) . chr(233) . chr(234) . chr(235) . chr(236) . chr(237) . chr(238) . chr(239) . chr(241) . chr(242) . chr(243) . chr(244) . chr(245) . chr(246) . chr(248) . chr(249) . chr(250) . chr(251) . chr(252) . chr(253) . chr(255); |
| 149 | $characters['out'] = 'EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy'; |
| 150 | $string = strtr($string, $characters['in'], $characters['out']); |
| 151 | $doubleChars = []; |
| 152 | $doubleChars['in'] = [chr(140), chr(156), chr(198), chr(208), chr(222), chr(223), chr(230), chr(240), chr(254)]; |
| 153 | $doubleChars['out'] = ['OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th']; |
| 154 | $string = str_replace($doubleChars['in'], $doubleChars['out'], $string); |
| 155 | } |
| 156 | return $string; |
| 157 | } |
| 158 | /** |
| 159 | * Convert any passed string to a url friendly string. |
| 160 | * Converts 'My first blog post' to 'my-first-blog-post' |
| 161 | * |
| 162 | * @param string $string String to urlize. |
| 163 | * |
| 164 | * @return string Urlized string. |
| 165 | */ |
| 166 | public function urlize(string $string) : string |
| 167 | { |
| 168 | // Remove all non url friendly characters with the unaccent function |
| 169 | $unaccented = $this->unaccent($string); |
| 170 | if (function_exists('mb_strtolower')) { |
| 171 | $lowered = mb_strtolower($unaccented); |
| 172 | } else { |
| 173 | $lowered = strtolower($unaccented); |
| 174 | } |
| 175 | $replacements = ['/\\W/' => ' ', '/([A-Z]+)([A-Z][a-z])/' => 'IAWPSCOPED\\1_\\2', '/([a-z\\d])([A-Z])/' => 'IAWPSCOPED\\1_\\2', '/[^A-Z^a-z^0-9^\\/]+/' => '-']; |
| 176 | $urlized = $lowered; |
| 177 | foreach ($replacements as $pattern => $replacement) { |
| 178 | $replaced = preg_replace($pattern, $replacement, $urlized); |
| 179 | if ($replaced === null) { |
| 180 | throw new RuntimeException(sprintf('preg_replace returned null for value "%s"', $urlized)); |
| 181 | } |
| 182 | $urlized = $replaced; |
| 183 | } |
| 184 | return trim($urlized, '-'); |
| 185 | } |
| 186 | /** |
| 187 | * Returns a word in singular form. |
| 188 | * |
| 189 | * @param string $word The word in plural form. |
| 190 | * |
| 191 | * @return string The word in singular form. |
| 192 | */ |
| 193 | public function singularize(string $word) : string |
| 194 | { |
| 195 | return $this->singularizer->inflect($word); |
| 196 | } |
| 197 | /** |
| 198 | * Returns a word in plural form. |
| 199 | * |
| 200 | * @param string $word The word in singular form. |
| 201 | * |
| 202 | * @return string The word in plural form. |
| 203 | */ |
| 204 | public function pluralize(string $word) : string |
| 205 | { |
| 206 | return $this->pluralizer->inflect($word); |
| 207 | } |
| 208 | } |
| 209 |