3 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15 * This software consists of voluntary contributions made by many individuals
16 * and is licensed under the MIT license. For more information, see
17 * <http://www.doctrine-project.org>.
20 namespace Doctrine\Common\Inflector;
23 * Doctrine inflector has static methods for inflecting text.
25 * The methods in these classes are from several different sources collected
26 * across several different php projects and several different authors. The
27 * original author names and emails are not known.
29 * Pluralize & Singularize implementation are borrowed from CakePHP with some modifications.
31 * @link www.doctrine-project.org
33 * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
34 * @author Jonathan H. Wage <jonwage@gmail.com>
39 * Plural inflector rules.
43 private static $plural = array(
45 '/(s)tatus$/i' => '\1\2tatuses',
46 '/(quiz)$/i' => '\1zes',
47 '/^(ox)$/i' => '\1\2en',
48 '/([m|l])ouse$/i' => '\1ice',
49 '/(matr|vert|ind)(ix|ex)$/i' => '\1ices',
50 '/(x|ch|ss|sh)$/i' => '\1es',
51 '/([^aeiouy]|qu)y$/i' => '\1ies',
52 '/(hive)$/i' => '\1s',
53 '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves',
55 '/([ti])um$/i' => '\1a',
56 '/(p)erson$/i' => '\1eople',
57 '/(m)an$/i' => '\1en',
58 '/(c)hild$/i' => '\1hildren',
59 '/(f)oot$/i' => '\1eet',
60 '/(buffal|her|potat|tomat|volcan)o$/i' => '\1\2oes',
61 '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i',
63 '/(alias)$/i' => '\1es',
64 '/(analys|ax|cris|test|thes)is$/i' => '\1es',
69 'uninflected' => array(
70 '.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox', '.*sheep', 'people', 'cookie'
76 'brother' => 'brothers',
78 'chateau' => 'chateaux',
79 'child' => 'children',
80 'cookie' => 'cookies',
81 'corpus' => 'corpuses',
83 'criterion' => 'criteria',
84 'curriculum' => 'curricula',
86 'domino' => 'dominoes',
90 'ganglion' => 'ganglions',
93 'graffito' => 'graffiti',
94 'hippopotamus' => 'hippopotami',
102 'memorandum' => 'memoranda',
104 'mongoose' => 'mongooses',
105 'motto' => 'mottoes',
107 'mythos' => 'mythoi',
109 'nucleus' => 'nuclei',
111 'occiput' => 'occiputs',
112 'octopus' => 'octopuses',
115 'penis' => 'penises',
116 'person' => 'people',
117 'plateau' => 'plateaux',
118 'runner-up' => 'runners-up',
120 'soliloquy' => 'soliloquies',
121 'son-in-law' => 'sons-in-law',
122 'syllabus' => 'syllabi',
123 'testis' => 'testes',
124 'thief' => 'thieves',
126 'tornado' => 'tornadoes',
127 'trilby' => 'trilbys',
129 'volcano' => 'volcanoes',
134 * Singular inflector rules.
138 private static $singular = array(
140 '/(s)tatuses$/i' => '\1\2tatus',
141 '/^(.*)(menu)s$/i' => '\1\2',
142 '/(quiz)zes$/i' => '\\1',
143 '/(matr)ices$/i' => '\1ix',
144 '/(vert|ind)ices$/i' => '\1ex',
145 '/^(ox)en/i' => '\1',
146 '/(alias)(es)*$/i' => '\1',
147 '/(buffal|her|potat|tomat|volcan)oes$/i' => '\1o',
148 '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us',
149 '/([ftw]ax)es/i' => '\1',
150 '/(analys|ax|cris|test|thes)es$/i' => '\1is',
151 '/(shoe|slave)s$/i' => '\1',
153 '/ouses$/' => 'ouse',
154 '/([^a])uses$/' => '\1us',
155 '/([m|l])ice$/i' => '\1ouse',
156 '/(x|ch|ss|sh)es$/i' => '\1',
157 '/(m)ovies$/i' => '\1\2ovie',
158 '/(s)eries$/i' => '\1\2eries',
159 '/([^aeiouy]|qu)ies$/i' => '\1y',
160 '/([lr])ves$/i' => '\1f',
161 '/(tive)s$/i' => '\1',
162 '/(hive)s$/i' => '\1',
163 '/(drive)s$/i' => '\1',
164 '/([^fo])ves$/i' => '\1fe',
165 '/(^analy)ses$/i' => '\1sis',
166 '/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
167 '/([ti])a$/i' => '\1um',
168 '/(p)eople$/i' => '\1\2erson',
169 '/(m)en$/i' => '\1an',
170 '/(c)hildren$/i' => '\1\2hild',
171 '/(f)eet$/i' => '\1oot',
172 '/(n)ews$/i' => '\1\2ews',
174 '/^(.*us)$/' => '\\1',
177 'uninflected' => array(
187 'irregular' => array(
188 'criteria' => 'criterion',
190 'emphases' => 'emphasis',
194 'neuroses' => 'neurosis',
201 * Words that should not be inflected.
205 private static $uninflected = array(
206 'Amoyese', 'bison', 'Borghese', 'bream', 'breeches', 'britches', 'buffalo', 'cantus',
207 'carp', 'chassis', 'clippers', 'cod', 'coitus', 'Congoese', 'contretemps', 'corps',
208 'debris', 'diabetes', 'djinn', 'eland', 'elk', 'equipment', 'Faroese', 'flounder',
209 'Foochowese', 'gallows', 'Genevese', 'Genoese', 'Gilbertese', 'graffiti',
210 'headquarters', 'herpes', 'hijinks', 'Hottentotese', 'information', 'innings',
211 'jackanapes', 'Kiplingese', 'Kongoese', 'Lucchese', 'mackerel', 'Maltese', '.*?media',
212 'mews', 'moose', 'mumps', 'Nankingese', 'news', 'nexus', 'Niasese',
213 'Pekingese', 'Piedmontese', 'pincers', 'Pistoiese', 'pliers', 'Portuguese',
214 'proceedings', 'rabies', 'rice', 'rhinoceros', 'salmon', 'Sarawakese', 'scissors',
215 'sea[- ]bass', 'series', 'Shavese', 'shears', 'siemens', 'species', 'staff', 'swine',
216 'testes', 'trousers', 'trout', 'tuna', 'Vermontese', 'Wenchowese', 'whiting',
217 'wildebeest', 'Yengeese'
221 * Method cache array.
225 private static $cache = array();
228 * The initial state of Inflector so reset() works.
232 private static $initialState = array();
235 * Converts a word into the format for a Doctrine table name. Converts 'ModelName' to 'model_name'.
237 * @param string $word The word to tableize.
239 * @return string The tableized word.
241 public static function tableize($word)
243 return strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $word));
247 * Converts a word into the format for a Doctrine class name. Converts 'table_name' to 'TableName'.
249 * @param string $word The word to classify.
251 * @return string The classified word.
253 public static function classify($word)
255 return str_replace(" ", "", ucwords(strtr($word, "_-", " ")));
259 * Camelizes a word. This uses the classify() method and turns the first character to lowercase.
261 * @param string $word The word to camelize.
263 * @return string The camelized word.
265 public static function camelize($word)
267 return lcfirst(self::classify($word));
271 * Uppercases words with configurable delimeters between words.
273 * Takes a string and capitalizes all of the words, like PHP's built-in
274 * ucwords function. This extends that behavior, however, by allowing the
275 * word delimeters to be configured, rather than only separating on
278 * Here is an example:
281 * $string = 'top-o-the-morning to all_of_you!';
282 * echo \Doctrine\Common\Inflector\Inflector::ucwords($string);
283 * // Top-O-The-Morning To All_of_you!
285 * echo \Doctrine\Common\Inflector\Inflector::ucwords($string, '-_ ');
286 * // Top-O-The-Morning To All_Of_You!
290 * @param string $string The string to operate on.
291 * @param string $delimiters A list of word separators.
293 * @return string The string with all delimeter-separated words capitalized.
295 public static function ucwords($string, $delimiters = " \n\t\r\0\x0B-")
297 return preg_replace_callback(
298 '/[^' . preg_quote($delimiters, '/') . ']+/',
300 return ucfirst($matches[0]);
307 * Clears Inflectors inflected value caches, and resets the inflection
308 * rules to the initial values.
312 public static function reset()
314 if (empty(self::$initialState)) {
315 self::$initialState = get_class_vars('Inflector');
320 foreach (self::$initialState as $key => $val) {
321 if ($key != 'initialState') {
322 self::${$key} = $val;
328 * Adds custom inflection $rules, of either 'plural' or 'singular' $type.
333 * Inflector::rules('plural', array('/^(inflect)or$/i' => '\1ables'));
334 * Inflector::rules('plural', array(
335 * 'rules' => array('/^(inflect)ors$/i' => '\1ables'),
336 * 'uninflected' => array('dontinflectme'),
337 * 'irregular' => array('red' => 'redlings')
341 * @param string $type The type of inflection, either 'plural' or 'singular'
342 * @param array $rules An array of rules to be added.
343 * @param boolean $reset If true, will unset default inflections for all
344 * new rules that are being defined in $rules.
348 public static function rules($type, $rules, $reset = false)
350 foreach ($rules as $rule => $pattern) {
351 if ( ! is_array($pattern)) {
356 self::${$type}[$rule] = $pattern;
358 self::${$type}[$rule] = ($rule === 'uninflected')
359 ? array_merge($pattern, self::${$type}[$rule])
360 : $pattern + self::${$type}[$rule];
363 unset($rules[$rule], self::${$type}['cache' . ucfirst($rule)]);
365 if (isset(self::${$type}['merged'][$rule])) {
366 unset(self::${$type}['merged'][$rule]);
369 if ($type === 'plural') {
370 self::$cache['pluralize'] = self::$cache['tableize'] = array();
371 } elseif ($type === 'singular') {
372 self::$cache['singularize'] = array();
376 self::${$type}['rules'] = $rules + self::${$type}['rules'];
380 * Returns a word in plural form.
382 * @param string $word The word in singular form.
384 * @return string The word in plural form.
386 public static function pluralize($word)
388 if (isset(self::$cache['pluralize'][$word])) {
389 return self::$cache['pluralize'][$word];
392 if (!isset(self::$plural['merged']['irregular'])) {
393 self::$plural['merged']['irregular'] = self::$plural['irregular'];
396 if (!isset(self::$plural['merged']['uninflected'])) {
397 self::$plural['merged']['uninflected'] = array_merge(self::$plural['uninflected'], self::$uninflected);
400 if (!isset(self::$plural['cacheUninflected']) || !isset(self::$plural['cacheIrregular'])) {
401 self::$plural['cacheUninflected'] = '(?:' . implode('|', self::$plural['merged']['uninflected']) . ')';
402 self::$plural['cacheIrregular'] = '(?:' . implode('|', array_keys(self::$plural['merged']['irregular'])) . ')';
405 if (preg_match('/(.*)\\b(' . self::$plural['cacheIrregular'] . ')$/i', $word, $regs)) {
406 self::$cache['pluralize'][$word] = $regs[1] . substr($word, 0, 1) . substr(self::$plural['merged']['irregular'][strtolower($regs[2])], 1);
408 return self::$cache['pluralize'][$word];
411 if (preg_match('/^(' . self::$plural['cacheUninflected'] . ')$/i', $word, $regs)) {
412 self::$cache['pluralize'][$word] = $word;
417 foreach (self::$plural['rules'] as $rule => $replacement) {
418 if (preg_match($rule, $word)) {
419 self::$cache['pluralize'][$word] = preg_replace($rule, $replacement, $word);
421 return self::$cache['pluralize'][$word];
427 * Returns a word in singular form.
429 * @param string $word The word in plural form.
431 * @return string The word in singular form.
433 public static function singularize($word)
435 if (isset(self::$cache['singularize'][$word])) {
436 return self::$cache['singularize'][$word];
439 if (!isset(self::$singular['merged']['uninflected'])) {
440 self::$singular['merged']['uninflected'] = array_merge(
441 self::$singular['uninflected'],
446 if (!isset(self::$singular['merged']['irregular'])) {
447 self::$singular['merged']['irregular'] = array_merge(
448 self::$singular['irregular'],
449 array_flip(self::$plural['irregular'])
453 if (!isset(self::$singular['cacheUninflected']) || !isset(self::$singular['cacheIrregular'])) {
454 self::$singular['cacheUninflected'] = '(?:' . join('|', self::$singular['merged']['uninflected']) . ')';
455 self::$singular['cacheIrregular'] = '(?:' . join('|', array_keys(self::$singular['merged']['irregular'])) . ')';
458 if (preg_match('/(.*)\\b(' . self::$singular['cacheIrregular'] . ')$/i', $word, $regs)) {
459 self::$cache['singularize'][$word] = $regs[1] . substr($word, 0, 1) . substr(self::$singular['merged']['irregular'][strtolower($regs[2])], 1);
461 return self::$cache['singularize'][$word];
464 if (preg_match('/^(' . self::$singular['cacheUninflected'] . ')$/i', $word, $regs)) {
465 self::$cache['singularize'][$word] = $word;
470 foreach (self::$singular['rules'] as $rule => $replacement) {
471 if (preg_match($rule, $word)) {
472 self::$cache['singularize'][$word] = preg_replace($rule, $replacement, $word);
474 return self::$cache['singularize'][$word];
478 self::$cache['singularize'][$word] = $word;