Skip to content
This repository
Browse code

Merge pull request #227 from jbergler/master

[DDC-2160] Smart Pluralize/Singularize support for Doctrine/Common/Util/Inflector
  • Loading branch information...
commit 28b0e7fde8007d5eba23c8ca3fe741d2757cf70d 2 parents bc6bc47 + 4de779a
Benjamin Eberlei authored January 10, 2013
321  lib/Doctrine/Common/Util/Inflector.php
@@ -26,7 +26,9 @@
26 26
  *
27 27
  * The methods in these classes are from several different sources collected
28 28
  * across several different php projects and several different authors. The
29  
- * original author names and emails are not known
  29
+ * original author names and emails are not known.
  30
+ *
  31
+ * Plurialize & Singularize implementation are borrowed from CakePHP with some modifications.
30 32
  *
31 33
  * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
32 34
  * @link        www.doctrine-project.org
@@ -38,6 +40,161 @@
38 40
 class Inflector
39 41
 {
40 42
     /**
  43
+     * Plural inflector rules
  44
+     *
  45
+     * @var array
  46
+     */
  47
+    private static $plural = array(
  48
+        'rules' => array(
  49
+            '/(s)tatus$/i' => '\1\2tatuses',
  50
+            '/(quiz)$/i' => '\1zes',
  51
+            '/^(ox)$/i' => '\1\2en',
  52
+            '/([m|l])ouse$/i' => '\1ice',
  53
+            '/(matr|vert|ind)(ix|ex)$/i' => '\1ices',
  54
+            '/(x|ch|ss|sh)$/i' => '\1es',
  55
+            '/([^aeiouy]|qu)y$/i' => '\1ies',
  56
+            '/(hive)$/i' => '\1s',
  57
+            '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves',
  58
+            '/sis$/i' => 'ses',
  59
+            '/([ti])um$/i' => '\1a',
  60
+            '/(p)erson$/i' => '\1eople',
  61
+            '/(m)an$/i' => '\1en',
  62
+            '/(c)hild$/i' => '\1hildren',
  63
+            '/(buffal|tomat)o$/i' => '\1\2oes',
  64
+            '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i',
  65
+            '/us$/i' => 'uses',
  66
+            '/(alias)$/i' => '\1es',
  67
+            '/(ax|cris|test)is$/i' => '\1es',
  68
+            '/s$/' => 's',
  69
+            '/^$/' => '',
  70
+            '/$/' => 's',
  71
+        ),
  72
+        'uninflected' => array(
  73
+            '.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox', '.*sheep', 'people', 'cookie'
  74
+        ),
  75
+        'irregular' => array(
  76
+            'atlas' => 'atlases',
  77
+            'beef' => 'beefs',
  78
+            'brother' => 'brothers',
  79
+            'cafe' => 'cafes',
  80
+            'child' => 'children',
  81
+            'cookie' => 'cookies',
  82
+            'corpus' => 'corpuses',
  83
+            'cow' => 'cows',
  84
+            'ganglion' => 'ganglions',
  85
+            'genie' => 'genies',
  86
+            'genus' => 'genera',
  87
+            'graffito' => 'graffiti',
  88
+            'hoof' => 'hoofs',
  89
+            'loaf' => 'loaves',
  90
+            'man' => 'men',
  91
+            'money' => 'monies',
  92
+            'mongoose' => 'mongooses',
  93
+            'move' => 'moves',
  94
+            'mythos' => 'mythoi',
  95
+            'niche' => 'niches',
  96
+            'numen' => 'numina',
  97
+            'occiput' => 'occiputs',
  98
+            'octopus' => 'octopuses',
  99
+            'opus' => 'opuses',
  100
+            'ox' => 'oxen',
  101
+            'penis' => 'penises',
  102
+            'person' => 'people',
  103
+            'sex' => 'sexes',
  104
+            'soliloquy' => 'soliloquies',
  105
+            'testis' => 'testes',
  106
+            'trilby' => 'trilbys',
  107
+            'turf' => 'turfs'
  108
+        )
  109
+    );
  110
+
  111
+    /**
  112
+     * Singular inflector rules
  113
+     *
  114
+     * @var array
  115
+     */
  116
+    private static $singular = array(
  117
+        'rules' => array(
  118
+            '/(s)tatuses$/i' => '\1\2tatus',
  119
+            '/^(.*)(menu)s$/i' => '\1\2',
  120
+            '/(quiz)zes$/i' => '\\1',
  121
+            '/(matr)ices$/i' => '\1ix',
  122
+            '/(vert|ind)ices$/i' => '\1ex',
  123
+            '/^(ox)en/i' => '\1',
  124
+            '/(alias)(es)*$/i' => '\1',
  125
+            '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us',
  126
+            '/([ftw]ax)es/i' => '\1',
  127
+            '/(cris|ax|test)es$/i' => '\1is',
  128
+            '/(shoe|slave)s$/i' => '\1',
  129
+            '/(o)es$/i' => '\1',
  130
+            '/ouses$/' => 'ouse',
  131
+            '/([^a])uses$/' => '\1us',
  132
+            '/([m|l])ice$/i' => '\1ouse',
  133
+            '/(x|ch|ss|sh)es$/i' => '\1',
  134
+            '/(m)ovies$/i' => '\1\2ovie',
  135
+            '/(s)eries$/i' => '\1\2eries',
  136
+            '/([^aeiouy]|qu)ies$/i' => '\1y',
  137
+            '/([lr])ves$/i' => '\1f',
  138
+            '/(tive)s$/i' => '\1',
  139
+            '/(hive)s$/i' => '\1',
  140
+            '/(drive)s$/i' => '\1',
  141
+            '/([^fo])ves$/i' => '\1fe',
  142
+            '/(^analy)ses$/i' => '\1sis',
  143
+            '/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
  144
+            '/([ti])a$/i' => '\1um',
  145
+            '/(p)eople$/i' => '\1\2erson',
  146
+            '/(m)en$/i' => '\1an',
  147
+            '/(c)hildren$/i' => '\1\2hild',
  148
+            '/(n)ews$/i' => '\1\2ews',
  149
+            '/eaus$/' => 'eau',
  150
+            '/^(.*us)$/' => '\\1',
  151
+            '/s$/i' => ''
  152
+        ),
  153
+        'uninflected' => array(
  154
+            '.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox', '.*sheep', '.*ss'
  155
+        ),
  156
+        'irregular' => array(
  157
+            'foes' => 'foe',
  158
+            'waves' => 'wave',
  159
+            'curves' => 'curve'
  160
+        )
  161
+    );
  162
+
  163
+    /**
  164
+     * Words that should not be inflected
  165
+     *
  166
+     * @var array
  167
+     */
  168
+    private static $uninflected = array(
  169
+        'Amoyese', 'bison', 'Borghese', 'bream', 'breeches', 'britches', 'buffalo', 'cantus',
  170
+        'carp', 'chassis', 'clippers', 'cod', 'coitus', 'Congoese', 'contretemps', 'corps',
  171
+        'debris', 'diabetes', 'djinn', 'eland', 'elk', 'equipment', 'Faroese', 'flounder',
  172
+        'Foochowese', 'gallows', 'Genevese', 'Genoese', 'Gilbertese', 'graffiti',
  173
+        'headquarters', 'herpes', 'hijinks', 'Hottentotese', 'information', 'innings',
  174
+        'jackanapes', 'Kiplingese', 'Kongoese', 'Lucchese', 'mackerel', 'Maltese', '.*?media',
  175
+        'mews', 'moose', 'mumps', 'Nankingese', 'news', 'nexus', 'Niasese',
  176
+        'Pekingese', 'Piedmontese', 'pincers', 'Pistoiese', 'pliers', 'Portuguese',
  177
+        'proceedings', 'rabies', 'rice', 'rhinoceros', 'salmon', 'Sarawakese', 'scissors',
  178
+        'sea[- ]bass', 'series', 'Shavese', 'shears', 'siemens', 'species', 'swine', 'testes',
  179
+        'trousers', 'trout', 'tuna', 'Vermontese', 'Wenchowese', 'whiting', 'wildebeest',
  180
+        'Yengeese'
  181
+    );
  182
+
  183
+    /**
  184
+     * Method cache array.
  185
+     *
  186
+     * @var array
  187
+     */
  188
+    private static $cache = array();
  189
+
  190
+    /**
  191
+     * The initial state of Inflector so reset() works.
  192
+     *
  193
+     * @var array
  194
+     */
  195
+    private static $initialState = array();
  196
+
  197
+    /**
41 198
      * Convert word in to the format for a Doctrine table name. Converts 'ModelName' to 'model_name'
42 199
      *
43 200
      * @param  string $word  Word to tableize
@@ -69,4 +226,164 @@ public static function camelize($word)
69 226
     {
70 227
         return lcfirst(self::classify($word));
71 228
     }
72  
-}
  229
+
  230
+    /**
  231
+     * Clears Inflectors inflected value caches. And resets the inflection
  232
+     * rules to the initial values.
  233
+     *
  234
+     * @return void
  235
+     */
  236
+    public static function reset()
  237
+    {
  238
+        if (empty(self::$initialState)) {
  239
+            self::$initialState = get_class_vars('Inflector');
  240
+            return;
  241
+        }
  242
+        foreach (self::$initialState as $key => $val) {
  243
+            if ($key != 'initialState') {
  244
+                self::${$key} = $val;
  245
+            }
  246
+        }
  247
+    }
  248
+
  249
+    /**
  250
+     * Adds custom inflection $rules, of either 'plural' or 'singular' $type.
  251
+     *
  252
+     * ### Usage:
  253
+     *
  254
+     * {{{
  255
+     * Inflector::rules('plural', array('/^(inflect)or$/i' => '\1ables'));
  256
+     * Inflector::rules('plural', array(
  257
+     *     'rules' => array('/^(inflect)ors$/i' => '\1ables'),
  258
+     *     'uninflected' => array('dontinflectme'),
  259
+     *     'irregular' => array('red' => 'redlings')
  260
+     * ));
  261
+     * }}}
  262
+     *
  263
+     * @param string $type The type of inflection, either 'plural' or 'singular'
  264
+     * @param array $rules Array of rules to be added.
  265
+     * @param boolean $reset If true, will unset default inflections for all
  266
+     *        new rules that are being defined in $rules.
  267
+     * @return void
  268
+     */
  269
+    public static function rules($type, $rules, $reset = false)
  270
+    {
  271
+        foreach ($rules as $rule => $pattern) {
  272
+            if (is_array($pattern)) {
  273
+                if ($reset) {
  274
+                    self::${$type}[$rule] = $pattern;
  275
+                } else {
  276
+                    if ($rule === 'uninflected') {
  277
+                        self::${$type}[$rule] = array_merge($pattern, self::${$type}[$rule]);
  278
+                    } else {
  279
+                        self::${$type}[$rule] = $pattern + self::${$type}[$rule];
  280
+                    }
  281
+                }
  282
+                unset($rules[$rule], self::${$type}['cache' . ucfirst($rule)]);
  283
+                if (isset(self::${$type}['merged'][$rule])) {
  284
+                    unset(self::${$type}['merged'][$rule]);
  285
+                }
  286
+                if ($type === 'plural') {
  287
+                    self::$cache['pluralize'] = self::$cache['tableize'] = array();
  288
+                } elseif ($type === 'singular') {
  289
+                    self::$cache['singularize'] = array();
  290
+                }
  291
+            }
  292
+        }
  293
+        self::${$type}['rules'] = $rules + self::${$type}['rules'];
  294
+    }
  295
+
  296
+    /**
  297
+     * Return $word in plural form.
  298
+     *
  299
+     * @param string $word Word in singular
  300
+     * @return string Word in plural
  301
+     */
  302
+    public static function pluralize($word)
  303
+    {
  304
+        if (isset(self::$cache['pluralize'][$word])) {
  305
+            return self::$cache['pluralize'][$word];
  306
+        }
  307
+
  308
+        if (!isset(self::$plural['merged']['irregular'])) {
  309
+            self::$plural['merged']['irregular'] = self::$plural['irregular'];
  310
+        }
  311
+
  312
+        if (!isset(self::$plural['merged']['uninflected'])) {
  313
+            self::$plural['merged']['uninflected'] = array_merge(self::$plural['uninflected'], self::$uninflected);
  314
+        }
  315
+
  316
+        if (!isset(self::$plural['cacheUninflected']) || !isset(self::$plural['cacheIrregular'])) {
  317
+            self::$plural['cacheUninflected'] = '(?:' . implode('|', self::$plural['merged']['uninflected']) . ')';
  318
+            self::$plural['cacheIrregular'] = '(?:' . implode('|', array_keys(self::$plural['merged']['irregular'])) . ')';
  319
+        }
  320
+
  321
+        if (preg_match('/(.*)\\b(' . self::$plural['cacheIrregular'] . ')$/i', $word, $regs)) {
  322
+            self::$cache['pluralize'][$word] = $regs[1] . substr($word, 0, 1) . substr(self::$plural['merged']['irregular'][strtolower($regs[2])], 1);
  323
+            return self::$cache['pluralize'][$word];
  324
+        }
  325
+
  326
+        if (preg_match('/^(' . self::$plural['cacheUninflected'] . ')$/i', $word, $regs)) {
  327
+            self::$cache['pluralize'][$word] = $word;
  328
+            return $word;
  329
+        }
  330
+
  331
+        foreach (self::$plural['rules'] as $rule => $replacement) {
  332
+            if (preg_match($rule, $word)) {
  333
+                self::$cache['pluralize'][$word] = preg_replace($rule, $replacement, $word);
  334
+                return self::$cache['pluralize'][$word];
  335
+            }
  336
+        }
  337
+    }
  338
+
  339
+    /**
  340
+     * Return $word in singular form.
  341
+     *
  342
+     * @param string $word Word in plural
  343
+     * @return string Word in singular
  344
+     */
  345
+    public static function singularize($word)
  346
+    {
  347
+        if (isset(self::$cache['singularize'][$word])) {
  348
+            return self::$cache['singularize'][$word];
  349
+        }
  350
+
  351
+        if (!isset(self::$singular['merged']['uninflected'])) {
  352
+            self::$singular['merged']['uninflected'] = array_merge(
  353
+                self::$singular['uninflected'],
  354
+                self::$uninflected
  355
+            );
  356
+        }
  357
+
  358
+        if (!isset(self::$singular['merged']['irregular'])) {
  359
+            self::$singular['merged']['irregular'] = array_merge(
  360
+                self::$singular['irregular'],
  361
+                array_flip(self::$plural['irregular'])
  362
+            );
  363
+        }
  364
+
  365
+        if (!isset(self::$singular['cacheUninflected']) || !isset(self::$singular['cacheIrregular'])) {
  366
+            self::$singular['cacheUninflected'] = '(?:' . join('|', self::$singular['merged']['uninflected']) . ')';
  367
+            self::$singular['cacheIrregular'] = '(?:' . join('|', array_keys(self::$singular['merged']['irregular'])) . ')';
  368
+        }
  369
+
  370
+        if (preg_match('/(.*)\\b(' . self::$singular['cacheIrregular'] . ')$/i', $word, $regs)) {
  371
+            self::$cache['singularize'][$word] = $regs[1] . substr($word, 0, 1) . substr(self::$singular['merged']['irregular'][strtolower($regs[2])], 1);
  372
+            return self::$cache['singularize'][$word];
  373
+        }
  374
+
  375
+        if (preg_match('/^(' . self::$singular['cacheUninflected'] . ')$/i', $word, $regs)) {
  376
+            self::$cache['singularize'][$word] = $word;
  377
+            return $word;
  378
+        }
  379
+
  380
+        foreach (self::$singular['rules'] as $rule => $replacement) {
  381
+            if (preg_match($rule, $word)) {
  382
+                self::$cache['singularize'][$word] = preg_replace($rule, $replacement, $word);
  383
+                return self::$cache['singularize'][$word];
  384
+            }
  385
+        }
  386
+        self::$cache['singularize'][$word] = $word;
  387
+        return $word;
  388
+    }
  389
+}
186  tests/Doctrine/Tests/Common/Util/InflectorTest.php
... ...
@@ -0,0 +1,186 @@
  1
+<?php
  2
+
  3
+namespace Doctrine\Tests\Common\Util
  4
+{
  5
+    use Doctrine\Tests\DoctrineTestCase;
  6
+    use Doctrine\Common\Util\Inflector;
  7
+
  8
+    class InflectorTest extends DoctrineTestCase
  9
+    {
  10
+
  11
+        /**
  12
+         * Singular & Plural test data. Returns an array of sample words.
  13
+         *
  14
+         * @return array
  15
+         */ 
  16
+        public function dataSampleWords() {
  17
+            Inflector::reset();
  18
+            // in the format array('singular', 'plural') 
  19
+            return array(
  20
+                array('categoria', 'categorias'),
  21
+                array('house', 'houses'),
  22
+                array('powerhouse', 'powerhouses'),
  23
+                array('Bus', 'Buses'),
  24
+                array('bus', 'buses'),
  25
+                array('menu', 'menus'),
  26
+                array('news', 'news'),
  27
+                array('food_menu', 'food_menus'),
  28
+                array('Menu', 'Menus'),
  29
+                array('FoodMenu', 'FoodMenus'),
  30
+                array('quiz', 'quizzes'),
  31
+                array('matrix_row', 'matrix_rows'),
  32
+                array('matrix', 'matrices'),
  33
+                array('vertex', 'vertices'),
  34
+                array('index', 'indices'),
  35
+                array('Alias', 'Aliases'),
  36
+                array('Media', 'Media'),
  37
+                array('NodeMedia', 'NodeMedia'),
  38
+                array('alumnus', 'alumni'),
  39
+                array('bacillus', 'bacilli'),
  40
+                array('cactus', 'cacti'),
  41
+                array('focus', 'foci'),
  42
+                array('fungus', 'fungi'),
  43
+                array('nucleus', 'nuclei'),
  44
+                array('octopus', 'octopuses'),
  45
+                array('radius', 'radii'),
  46
+                array('stimulus', 'stimuli'),
  47
+                array('syllabus', 'syllabi'),
  48
+                array('terminus', 'termini'),
  49
+                array('virus', 'viri'),
  50
+                array('person', 'people'),
  51
+                array('glove', 'gloves'),
  52
+                array('crisis', 'crises'),
  53
+                array('tax', 'taxes'),
  54
+                array('wave', 'waves'),
  55
+                array('bureau', 'bureaus'),
  56
+                array('cafe', 'cafes'),
  57
+                array('roof', 'roofs'),
  58
+                array('foe', 'foes'),
  59
+                array('cookie', 'cookies'),
  60
+                array('', ''),
  61
+            );
  62
+        }
  63
+
  64
+        /**
  65
+         * testInflectingSingulars method
  66
+         *
  67
+         * @dataProvider dataSampleWords
  68
+         * @return void
  69
+         */
  70
+        public function testInflectingSingulars($singular, $plural) {
  71
+            $this->assertEquals($singular, Inflector::singularize($plural), "'$plural' should be singularized to '$singular'");
  72
+        }
  73
+
  74
+        /**
  75
+         * testInflectingPlurals method
  76
+         *
  77
+         * @dataProvider dataSampleWords
  78
+         * @return void
  79
+         */
  80
+        public function testInflectingPlurals($singular, $plural) {
  81
+            $this->assertEquals($plural, Inflector::pluralize($singular), "'$singular' should be pluralized to '$plural'");
  82
+        }
  83
+
  84
+        /**
  85
+         * testCustomPluralRule method
  86
+         *
  87
+         * @return void
  88
+         */
  89
+        public function testCustomPluralRule() {
  90
+            Inflector::reset();
  91
+            Inflector::rules('plural', array('/^(custom)$/i' => '\1izables'));
  92
+            $this->assertEquals(Inflector::pluralize('custom'), 'customizables');
  93
+
  94
+            Inflector::rules('plural', array('uninflected' => array('uninflectable')));
  95
+            $this->assertEquals(Inflector::pluralize('uninflectable'), 'uninflectable');
  96
+
  97
+            Inflector::rules('plural', array(
  98
+                'rules' => array('/^(alert)$/i' => '\1ables'),
  99
+                'uninflected' => array('noflect', 'abtuse'),
  100
+                'irregular' => array('amaze' => 'amazable', 'phone' => 'phonezes')
  101
+            ));
  102
+            $this->assertEquals(Inflector::pluralize('noflect'), 'noflect');
  103
+            $this->assertEquals(Inflector::pluralize('abtuse'), 'abtuse');
  104
+            $this->assertEquals(Inflector::pluralize('alert'), 'alertables');
  105
+            $this->assertEquals(Inflector::pluralize('amaze'), 'amazable');
  106
+            $this->assertEquals(Inflector::pluralize('phone'), 'phonezes');
  107
+        }
  108
+
  109
+        /**
  110
+         * testCustomSingularRule method
  111
+         *
  112
+         * @return void
  113
+         */
  114
+        public function testCustomSingularRule() {
  115
+            Inflector::reset();
  116
+            Inflector::rules('singular', array('/(eple)r$/i' => '\1', '/(jente)r$/i' => '\1'));
  117
+
  118
+            $this->assertEquals(Inflector::singularize('epler'), 'eple');
  119
+            $this->assertEquals(Inflector::singularize('jenter'), 'jente');
  120
+
  121
+            Inflector::rules('singular', array(
  122
+                'rules' => array('/^(bil)er$/i' => '\1', '/^(inflec|contribu)tors$/i' => '\1ta'),
  123
+                'uninflected' => array('singulars'),
  124
+                'irregular' => array('spins' => 'spinor')
  125
+            ));
  126
+
  127
+            $this->assertEquals(Inflector::singularize('inflectors'), 'inflecta');
  128
+            $this->assertEquals(Inflector::singularize('contributors'), 'contributa');
  129
+            $this->assertEquals(Inflector::singularize('spins'), 'spinor');
  130
+            $this->assertEquals(Inflector::singularize('singulars'), 'singulars');
  131
+        }
  132
+
  133
+        /**
  134
+         * test that setting new rules clears the inflector caches.
  135
+         *
  136
+         * @return void
  137
+         */
  138
+        public function testRulesClearsCaches() {
  139
+            Inflector::reset();
  140
+            $this->assertEquals(Inflector::singularize('Bananas'), 'Banana');
  141
+            $this->assertEquals(Inflector::pluralize('Banana'), 'Bananas');
  142
+
  143
+            Inflector::rules('singular', array(
  144
+                'rules' => array('/(.*)nas$/i' => '\1zzz')
  145
+            ));
  146
+            $this->assertEquals('Banazzz', Inflector::singularize('Bananas'), 'Was inflected with old rules.');
  147
+
  148
+            Inflector::rules('plural', array(
  149
+                'rules' => array('/(.*)na$/i' => '\1zzz'),
  150
+                'irregular' => array('corpus' => 'corpora')
  151
+            ));
  152
+            $this->assertEquals(Inflector::pluralize('Banana'), 'Banazzz', 'Was inflected with old rules.');
  153
+            $this->assertEquals(Inflector::pluralize('corpus'), 'corpora', 'Was inflected with old irregular form.');
  154
+        }
  155
+
  156
+        /**
  157
+         * Test resetting inflection rules.
  158
+         *
  159
+         * @return void
  160
+         */
  161
+        public function testCustomRuleWithReset() {
  162
+            Inflector::reset();
  163
+            $uninflected = array('atlas', 'lapis', 'onibus', 'pires', 'virus', '.*x');
  164
+            $pluralIrregular = array('as' => 'ases');
  165
+
  166
+            Inflector::rules('singular', array(
  167
+                'rules' => array('/^(.*)(a|e|o|u)is$/i' => '\1\2l'),
  168
+                'uninflected' => $uninflected,
  169
+            ), true);
  170
+
  171
+            Inflector::rules('plural', array(
  172
+                'rules' => array(
  173
+                    '/^(.*)(a|e|o|u)l$/i' => '\1\2is',
  174
+                ),
  175
+                'uninflected' => $uninflected,
  176
+                'irregular' => $pluralIrregular
  177
+            ), true);
  178
+
  179
+            $this->assertEquals(Inflector::pluralize('Alcool'), 'Alcoois');
  180
+            $this->assertEquals(Inflector::pluralize('Atlas'), 'Atlas');
  181
+            $this->assertEquals(Inflector::singularize('Alcoois'), 'Alcool');
  182
+            $this->assertEquals(Inflector::singularize('Atlas'), 'Atlas');
  183
+        }
  184
+    }
  185
+}
  186
+

0 notes on commit 28b0e7f

Please sign in to comment.
Something went wrong with that request. Please try again.