8000 Add Inflector component (from StringUtil of PropertyAccess) · symfony/symfony@9695e78 · GitHub
[go: up one dir, main page]

Skip to content

Commit 9695e78

Browse files
committed
Add Inflector component (from StringUtil of PropertyAccess)
1 parent 003507d commit 9695e78

File tree

9 files changed

+342
-189
lines changed
  • 9 files changed

    +342
    -189
    lines changed
    Lines changed: 222 additions & 0 deletions
    < 6D40 tr class="diff-line-row">
    Original file line numberDiff line numberDiff line change
    @@ -0,0 +1,222 @@
    1+
    <?php
    2+
    3+
    /*
    4+
    * This file is part of the Symfony package.
    5+
    *
    6+
    * (c) Fabien Potencier <fabien@symfony.com>
    7+
    *
    8+
    * For the full copyright and license information, please view the LICENSE
    9+
    * file that was distributed with this source code.
    10+
    */
    11+
    12+
    namespace Symfony\Component\Inflector;
    13+
    14+
    /**
    15+
    * Converts words between singular and plural forms.
    16+
    *
    17+
    * @author Bernhard Schussek <bschussek@gmail.com>
    18+
    */
    19+
    final class Inflector
    20+
    {
    21+
    /**
    22+
    * Map English plural to singular suffixes.
    23+
    *
    24+
    * @var array
    25+
    *
    26+
    * @see http://english-zone.com/spelling/plurals.html
    27+
    * @see http://www.scribd.com/doc/3271143/List-of-100-Irregular-Plural-Nouns-in-English
    28+
    */
    29+
    private static $pluralMap = array(
    30+
    // First entry: plural suffix, reversed
    31+
    // Second entry: length of plural suffix
    32+
    // Third entry: Whether the suffix may succeed a vocal
    33+
    // Fourth entry: Whether the suffix may succeed a consonant
    34+
    // Fifth entry: singular suffix, normal
    35+
    36+
    // bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
    37+
    array('a', 1, true, true, array('on', 'um')),
    38+
    39+
    // nebulae (nebula)
    40+
    array('ea', 2, true, true, 'a'),
    41+
    42+
    // services (service)
    43+
    array('secivres', 8, true, true, 'service'),
    44+
    45+
    // mice (mouse), lice (louse)
    46+
    array('eci', 3, false, true, 'ouse'),
    47+
    48+
    // geese (goose)
    49+
    array('esee', 4, false, true, 'oose'),
    50+
    51+
    // fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius)
    52+
    array('i', 1, true, true, 'us'),
    53+
    54+
    // men (man), women (woman)
    55+
    array('nem', 3, true, true, 'man'),
    56+
    57+
    // children (child)
    58+
    array('nerdlihc', 8, true, true, 'child'),
    59+
    60+
    // oxen (ox)
    61+
    array('nexo', 4, false, false, 'ox'),
    62+
    63+
    // indices (index), appendices (appendix), prices (price)
    64+
    array('seci', 4, false, true, array('ex', 'ix', 'ice')),
    65+
    66+
    // selfies (selfie)
    67+
    array('seifles', 7, true, true, 'selfie'),
    68+
    69+
    // movies (movie)
    70+
    array('seivom', 6, true, true, 'movie'),
    71+
    72+
    // news (news)
    73+
    array('swen', 4, true, true, 'news'),
    74+
    75+
    // series (series)
    76+
    array('seires', 6, true, true, 'series'),
    77+
    78+
    // babies (baby)
    79+
    array('sei', 3, false, true, 'y'),
    80+
    81+
    // accesses (access), addresses (address), kisses (kiss)
    82+
    array('sess', 4, true, false, 'ss'),
    83+
    84+
    // analyses (analysis), ellipses (ellipsis), funguses (fungus),
    85+
    // neuroses (neurosis), theses (thesis), emphases (emphasis),
    86+
    // oases (oasis), crises (crisis), houses (house), bases (base),
    87+
    // atlases (atlas)
    88+
    array('ses', 3, true, true, array('s', 'se', 'sis')),
    89+
    90+
    // objectives (objective), alternative (alternatives)
    91+
    array('sevit', 5, true, true, 'tive'),
    92+
    93+
    // drives (drive)
    94+
    array('sevird', 6, false, true, 'drive'),
    95+
    96+
    // lives (life), wives (wife)
    97+
    array('sevi', 4, false, true, 'ife'),
    98+
    99+
    // moves (move)
    100+
    array('sevom', 5, true, true, 'move'),
    101+
    102+
    // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf), caves (cave), staves (staff)
    103+
    array('sev', 3, true, true, array('f', 've', 'ff')),
    104+
    105+
    // axes (axis), axes (ax), axes (axe)
    106+
    array('sexa', 4, false, false, array('ax', 'axe', 'axis')),
    107+
    108+
    // indexes (index), matrixes (matrix)
    109+
    array('sex', 3, true, false, 'x'),
    110+
    111+
    // quizzes (quiz)
    112+
    array('sezz', 4, true, false, 'z'),
    113+
    114+
    // bureaus (bureau)
    115+
    array('suae', 4, false, true, 'eau'),
    116+
    117+
    // roses (rose), garages (garage), cassettes (cassette),
    118+
    // waltzes (waltz), heroes (hero), bushes (bush), arches (arch),
    119+
    // shoes (shoe)
    120+
    array('se', 2, true, true, array('', 'e')),
    121+
    122+
    // tags (tag)
    123+
    array('s', 1, true, true, ''),
    124+
    125+
    // chateaux (chateau)
    126+
    array('xuae', 4, false, true, 'eau'),
    127+
    );
    128+
    129+
    /**
    130+
    * This class should not be instantiated.
    131+
    */
    132+
    private function __construct()
    133+
    {
    134+
    }
    135+
    136+
    /**
    137+
    * Returns the singular form of a word.
    138+
    *
    139+
    * If the method can't determine the form with certainty, an array of the
    140+
    * possible singulars is returned.
    141+
    *
    142+
    * @param string $plural A word in plural form
    143+
    *
    144+
    * @return string|array The singular form or an array of possible singular
    145+
    * forms
    146+
    */
    147+
    public static function singularize($plural)
    148+
    {
    149+
    $pluralRev = strrev($plural);
    150+
    $lowerPluralRev = strtolower($pluralRev);
    151+
    $pluralLength = strlen($lowerPluralRev);
    152+
    153+
    // The outer loop iterates over the entries of the plural table
    154+
    // The inner loop $j iterates over the characters of the plural suffix
    155+
    // in the plural table to compare them with the characters of the actual
    156+
    // given plural suffix
    157+
    foreach (self::$pluralMap as $map) {
    158+
    $suffix = $map[0];
    159+
    $suffixLength = $map[1];
    160+
    $j = 0;
    161+
    162+
    // Compare characters in the plural table and of the suffix of the
    163+
    // given plural one by one
    164+
    while ($suffix[$j] === $lowerPluralRev[$j]) {
    165+
    // Let $j point to the next character
    166+
    ++$j;
    167+
    168+
    // Successfully compared the last character
    169+
    // Add an entry with the singular suffix to the singular array
    170+
    if ($j === $suffixLength) {
    171+
    // Is there any character preceding the suffix in the plural string?
    172+
    if ($j < $pluralLength) {
    173+
    $nextIsVocal = false !== strpos('aeiou', $lowerPluralRev[$j]);
    174+
    175+
    if (!$map[2] && $nextIsVocal) {
    176+
    // suffix may not succeed a vocal but next char is one
    177+
    break;
    178+
    }
    179+
    180+
    if (!$map[3] && !$nextIsVocal) {
    181+
    // suffix may not succeed a consonant but next char is one
    182+
    break;
    183+
    }
    184+
    }
    185+
    186+
    $newBase = substr($plural, 0, $pluralLength - $suffixLength);
    187+
    $newSuffix = $map[4];
    188+
    189+
    // Check whether the first character in the plural suffix
    190+
    // is uppercased. If yes, uppercase the first character in
    191+
    // the singular suffix too
    192+
    $firstUpper = ctype_upper($pluralRev[$j - 1]);
    193+
    194+
    if (is_array($newSuffix)) {
    195+
    $singulars = array();
    196+
    197+
    foreach ($newSuffix as $newSuffixEntry) {
    198+
    $singulars[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry);
    199+
    }
    200+
    201+
    return $singulars;
    202+
    }
    203+
    204+
    return $newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix);
    205+
    }
    206+
    207+
    // Suffix is longer than word
    208+
    if ($j === $pluralLength) {
    209+
    break;
    210+
    }
    211+
    }
    212+
    }
    213+
    214+
    // Convert teeth to tooth, feet to foot
    215+
    if (false !== ($pos = strpos($plural, 'ee')) && strlen($plural) > 3 && 'feedback' !== $plural) {
    216+
    return substr_replace($plural, 'oo', $pos, 2);
    217+
    }
    218+
    219+
    // Assume that plural and singular is identical
    220+
    return $plural;
    221+
    }
    222+
    }
    Lines changed: 19 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -0,0 +1,19 @@
    1+
    Copyright (c) 2012-2016 Fabien Potencier
    2+
    3+
    Permission is hereby granted, free of charge, to any person obtaining a copy
    4+
    of this software and associated documentation files (the "Software"), to deal
    5+
    in the Software without restriction, including without limitation the rights
    6+
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    7+
    copies of the Software, and to permit persons to whom the Software is furnished
    8+
    to do so, subject to the following conditions:
    9+
    10+
    The above copyright notice and this permission notice shall be included in all
    11+
    copies or substantial portions of the Software.
    12+
    13+
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14+
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15+
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16+
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17+
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18+
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19+
    THE SOFTWARE.
    Lines changed: 12 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -0,0 +1,12 @@
    1+
    Inflector Component
    2+
    ======================
    3+
    4+
    Inflector converts words between their singular and plural forms (English only).
    5+
    6+
    Resources
    7+
    ---------
    8+
    9+
    * [Contributing](https://symfony.com/doc/current/contributing/index.html)
    10+
    * [Report issues](https://github.com/symfony/symfony/issues) and
    11+
    [send Pull Requests](https://github.com/symfony/symfony/pulls)
    12+
    in the [main Symfony repository](https://github.com/symfony/symfony)

    src/Symfony/Component/PropertyAccess/Tests/StringUtilTest.php renamed to src/Symfony/Component/Inflector/Tests/InflectorTest.php

    Lines changed: 7 additions & 7 deletions
    Original file line numberDiff line numberDiff line change
    @@ -9,13 +9,13 @@
    99
    * file that was distributed with this source code.
    1010
    */
    1111

    12-
    namespace Symfony\Component\PropertyAccess\Tests;
    12+
    namespace Symfony\Component\Inflector\Tests;
    1313

    14-
    use Symfony\Component\PropertyAccess\StringUtil;
    14+
    use Symfony\Component\Inflector\Inflector;
    1515

    16-
    class StringUtilTest extends \PHPUnit_Framework_TestCase
    16+
    class InflectorTest extends \PHPUnit_Framework_TestCase
    1717
    {
    18-
    public function singularifyProvider()
    18+
    public function singularizeProvider()
    1919
    {
    2020
    // see http://english-zone.com/spelling/plurals.html
    2121
    // see http://www.scribd.com/doc/3271143/List-of-100-Irregular-Plural-Nouns-in-English
    @@ -152,11 +152,11 @@ public function singularifyProvider()
    152152
    }
    153153

    154154
    /**
    155-
    * @dataProvider singularifyProvider
    155+
    * @dataProvider singularizeProvider
    156156
    */
    157-
    public function testSingularify($plural, $singular)
    157+
    public function testSingularize($plural, $singular)
    158158
    {
    159-
    $single = StringUtil::singularify($plural);
    159+
    $single = Inflector::singularize($plural);
    160160
    if (is_string($singular) && is_array($single)) {
    161161
    $this->fail("--- Expected\n`string`: ".$singular."\n+++ Actual\n`array`: ".implode(', ', $single));
    162162
    } elseif (is_array($singular) && is_string($single)) {
    Lines changed: 40 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -0,0 +1,40 @@
    1+
    {
    2+
    "name": "symfony/inflector",
    3+
    "type": "library",
    4+
    "description": "Symfony Inflector Component",
    5+
    "keywords": [
    6+
    "string",
    7+
    "inflection",
    8+
    "singularize",
    9+
    "pluralize",
    10+
    "words",
    11+
    "symfony"
    12+
    ],
    13+
    "homepage": "https://symfony.com",
    14+
    "license": "MIT",
    15+
    "authors": [
    16+
    {
    17+
    "name": "Bernhard Schussek",
    18+
    "email": "bschussek@gmail.com"
    19+
    },
    20+
    {
    21+
    "name": "Symfony Community",
    22+
    "homepage": "https://symfony.com/contributors"
    23+
    }
    24+
    ],
    25+
    "require": {
    26+
    "php": ">=5.5.9"
    27+
    },
    28+
    "autoload": {
    29+
    "psr-4": { "Symfony\\Component\\Inflector\\": "" },
    30+
    "exclude-from-classmap": [
    31+
    "/Tests/"
    32+
    ]
    33+
    },
    34+
    "minimum-stability": "dev",
    35+
    "extra": {
    36+
    "branch-alias": {
    37+
    "dev-master": "3.1-dev"
    38+
    }
    39+
    }
    40+
    }
    Lines changed: 29 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -0,0 +1,29 @@
    1+
    <?xml version="1.0" encoding="UTF-8"?>
    2+
    3+
    <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4+
    xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
    5+
    backupGlobals="false"
    6+
    colors="true"
    7+
    bootstrap="vendor/autoload.php"
    8+
    >
    9+
    <php>
    10+
    <ini name="error_reporting" value="-1" />
    11+
    </php>
    12+
    13+
    <testsuites>
    14+
    <testsuite name="Symfony Inflector Component Test Suite">
    15+
    <directory>./Tests/</directory>
    16+
    </testsuite>
    17+
    </testsuites>
    18+
    19+
    <filter>
    20+
    <whitelist>
    21+
    <directory>./</directory>
    22+
    <exclude>
    23+
    <directory>./Resources</directory>
    24+
    <directory>./Tests</directory>
    25+
    <directory>./vendor</directory>
    26+
    </exclude>
    27+
    </whitelist>
    28+
    </filter>
    29+
    </phpunit>

    src/Symfony/Component/PropertyAccess/PropertyAccessor.php

    Lines changed: 3 additions & 2 deletions
    Original file line numberDiff line numberDiff line change
    @@ -11,6 +11,7 @@
    1111

    1212
    namespace Symfony\Component\PropertyAccess;
    1313

    14+
    use Symfony\Component\Inflector\Inflector;
    1415
    use Symfony\Component\PropertyAccess\Exception\AccessException;
    1516
    use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
    1617
    use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
    @@ -681,7 +682,7 @@ private function getWriteAccessInfo($object, $property, $value)
    681682
    $reflClass = new \ReflectionClass($object);
    682683
    $access[self::ACCESS_HAS_PROPERTY] = $reflClass->hasProperty($property);
    683684
    $camelized = $this->camelize($property);
    684-
    $singulars = (array) StringUtil::singularify($camelized);
    685+
    $singulars = (array) Inflector::singularize($camelized);
    685686

    686687
    if (is_array($value) || $value instanceof \Traversable) {
    687688
    $methods = $this->findAdderAndRemover($reflClass, $singulars);
    @@ -765,7 +766,7 @@ private function isPropertyWritable($object, $property)
    765766
    return true;
    766767
    }
    767768

    768-
    $singulars = (array) StringUtil::singularify($camelized);
    769+
    $singulars = (array) Inflector::singularize($camelized);
    769770

    770771
    // Any of the two methods is required, but not yet known
    771772
    if (null !== $this->findAdderAndRemover($reflClass, $singulars)) {

    0 commit comments

    Comments
     (0)
    0