feature #35092 [Inflector][String] Move Inflector in String (fancyweb)
This PR was merged into the 5.1-dev branch.
Discussion
----------
[Inflector][String] Move Inflector in String
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | no
| New feature? | yes
| Deprecations? | yes
| Tickets | https://github.com/orgs/symfony/projects/1#card-30499514
| License | MIT
| Doc PR | -
Needs https://github.com/symfony/symfony/pull/35091.
Should we have a standalone inflector (like the Slugger) or 2 new methods (pluralize and singularize) on the AbstractString class? I implemented both but since we only handle English I finally preferred the first one.
TODO (after the "move" is OK):
- [x] Deprecate the Inflector component
- [x] Use the String inflector in Symfony's code
Commits
-------
9c6a5c0093
[String] Move Inflector in String
This commit is contained in:
commit
3e737ec28f
@ -71,6 +71,11 @@ HttpKernel
|
|||||||
not returning an array is deprecated
|
not returning an array is deprecated
|
||||||
* Deprecated support for `service:action` syntax to reference controllers. Use `serviceOrFqcn::method` instead.
|
* Deprecated support for `service:action` syntax to reference controllers. Use `serviceOrFqcn::method` instead.
|
||||||
|
|
||||||
|
Inflector
|
||||||
|
---------
|
||||||
|
|
||||||
|
* The component has been deprecated, use `EnglishInflector` from the String component instead.
|
||||||
|
|
||||||
Mailer
|
Mailer
|
||||||
------
|
------
|
||||||
|
|
||||||
|
@ -66,6 +66,10 @@ HttpKernel
|
|||||||
* Made `WarmableInterface::warmUp()` return a list of classes or files to preload on PHP 7.4+
|
* Made `WarmableInterface::warmUp()` return a list of classes or files to preload on PHP 7.4+
|
||||||
* Removed support for `service:action` syntax to reference controllers. Use `serviceOrFqcn::method` instead.
|
* Removed support for `service:action` syntax to reference controllers. Use `serviceOrFqcn::method` instead.
|
||||||
|
|
||||||
|
Inflector
|
||||||
|
---------
|
||||||
|
|
||||||
|
* The component has been removed, use `EnglishInflector` from the String component instead.
|
||||||
|
|
||||||
Mailer
|
Mailer
|
||||||
------
|
------
|
||||||
|
7
src/Symfony/Component/Inflector/CHANGELOG.md
Normal file
7
src/Symfony/Component/Inflector/CHANGELOG.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
CHANGELOG
|
||||||
|
=========
|
||||||
|
|
||||||
|
5.1.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* The component has been deprecated, use `EnglishInflector` from the String component instead.
|
@ -11,315 +11,20 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Inflector;
|
namespace Symfony\Component\Inflector;
|
||||||
|
|
||||||
|
use Symfony\Component\String\Inflector\EnglishInflector;
|
||||||
|
|
||||||
|
trigger_deprecation('symfony/inflector', '5.1', sprintf('The "%s" class is deprecated, use "%s" instead.', Inflector::class, EnglishInflector::class));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts words between singular and plural forms.
|
* Converts words between singular and plural forms.
|
||||||
*
|
*
|
||||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
|
*
|
||||||
|
* @deprecated since Symfony 5.1, use Symfony\Component\String\Inflector\EnglishInflector instead.
|
||||||
*/
|
*/
|
||||||
final class Inflector
|
final class Inflector
|
||||||
{
|
{
|
||||||
/**
|
private static $englishInflector;
|
||||||
* Map English plural to singular suffixes.
|
|
||||||
*
|
|
||||||
* @see http://english-zone.com/spelling/plurals.html
|
|
||||||
*/
|
|
||||||
private static $pluralMap = [
|
|
||||||
// First entry: plural suffix, reversed
|
|
||||||
// Second entry: length of plural suffix
|
|
||||||
// Third entry: Whether the suffix may succeed a vocal
|
|
||||||
// Fourth entry: Whether the suffix may succeed a consonant
|
|
||||||
// Fifth entry: singular suffix, normal
|
|
||||||
|
|
||||||
// bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
|
|
||||||
['a', 1, true, true, ['on', 'um']],
|
|
||||||
|
|
||||||
// nebulae (nebula)
|
|
||||||
['ea', 2, true, true, 'a'],
|
|
||||||
|
|
||||||
// services (service)
|
|
||||||
['secivres', 8, true, true, 'service'],
|
|
||||||
|
|
||||||
// mice (mouse), lice (louse)
|
|
||||||
['eci', 3, false, true, 'ouse'],
|
|
||||||
|
|
||||||
// geese (goose)
|
|
||||||
['esee', 4, false, true, 'oose'],
|
|
||||||
|
|
||||||
// fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius)
|
|
||||||
['i', 1, true, true, 'us'],
|
|
||||||
|
|
||||||
// men (man), women (woman)
|
|
||||||
['nem', 3, true, true, 'man'],
|
|
||||||
|
|
||||||
// children (child)
|
|
||||||
['nerdlihc', 8, true, true, 'child'],
|
|
||||||
|
|
||||||
// oxen (ox)
|
|
||||||
['nexo', 4, false, false, 'ox'],
|
|
||||||
|
|
||||||
// indices (index), appendices (appendix), prices (price)
|
|
||||||
['seci', 4, false, true, ['ex', 'ix', 'ice']],
|
|
||||||
|
|
||||||
// selfies (selfie)
|
|
||||||
['seifles', 7, true, true, 'selfie'],
|
|
||||||
|
|
||||||
// movies (movie)
|
|
||||||
['seivom', 6, true, true, 'movie'],
|
|
||||||
|
|
||||||
// feet (foot)
|
|
||||||
['teef', 4, true, true, 'foot'],
|
|
||||||
|
|
||||||
// geese (goose)
|
|
||||||
['eseeg', 5, true, true, 'goose'],
|
|
||||||
|
|
||||||
// teeth (tooth)
|
|
||||||
['hteet', 5, true, true, 'tooth'],
|
|
||||||
|
|
||||||
// news (news)
|
|
||||||
['swen', 4, true, true, 'news'],
|
|
||||||
|
|
||||||
// series (series)
|
|
||||||
['seires', 6, true, true, 'series'],
|
|
||||||
|
|
||||||
// babies (baby)
|
|
||||||
['sei', 3, false, true, 'y'],
|
|
||||||
|
|
||||||
// accesses (access), addresses (address), kisses (kiss)
|
|
||||||
['sess', 4, true, false, 'ss'],
|
|
||||||
|
|
||||||
// analyses (analysis), ellipses (ellipsis), fungi (fungus),
|
|
||||||
// neuroses (neurosis), theses (thesis), emphases (emphasis),
|
|
||||||
// oases (oasis), crises (crisis), houses (house), bases (base),
|
|
||||||
// atlases (atlas)
|
|
||||||
['ses', 3, true, true, ['s', 'se', 'sis']],
|
|
||||||
|
|
||||||
// objectives (objective), alternative (alternatives)
|
|
||||||
['sevit', 5, true, true, 'tive'],
|
|
||||||
|
|
||||||
// drives (drive)
|
|
||||||
['sevird', 6, false, true, 'drive'],
|
|
||||||
|
|
||||||
// lives (life), wives (wife)
|
|
||||||
['sevi', 4, false, true, 'ife'],
|
|
||||||
|
|
||||||
// moves (move)
|
|
||||||
['sevom', 5, true, true, 'move'],
|
|
||||||
|
|
||||||
// hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf), caves (cave), staves (staff)
|
|
||||||
['sev', 3, true, true, ['f', 've', 'ff']],
|
|
||||||
|
|
||||||
// axes (axis), axes (ax), axes (axe)
|
|
||||||
['sexa', 4, false, false, ['ax', 'axe', 'axis']],
|
|
||||||
|
|
||||||
// indexes (index), matrixes (matrix)
|
|
||||||
['sex', 3, true, false, 'x'],
|
|
||||||
|
|
||||||
// quizzes (quiz)
|
|
||||||
['sezz', 4, true, false, 'z'],
|
|
||||||
|
|
||||||
// bureaus (bureau)
|
|
||||||
['suae', 4, false, true, 'eau'],
|
|
||||||
|
|
||||||
// fees (fee), trees (tree), employees (employee)
|
|
||||||
['see', 3, true, true, 'ee'],
|
|
||||||
|
|
||||||
// roses (rose), garages (garage), cassettes (cassette),
|
|
||||||
// waltzes (waltz), heroes (hero), bushes (bush), arches (arch),
|
|
||||||
// shoes (shoe)
|
|
||||||
['se', 2, true, true, ['', 'e']],
|
|
||||||
|
|
||||||
// tags (tag)
|
|
||||||
['s', 1, true, true, ''],
|
|
||||||
|
|
||||||
// chateaux (chateau)
|
|
||||||
['xuae', 4, false, true, 'eau'],
|
|
||||||
|
|
||||||
// people (person)
|
|
||||||
['elpoep', 6, true, true, 'person'],
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map English singular to plural suffixes.
|
|
||||||
*
|
|
||||||
* @see http://english-zone.com/spelling/plurals.html
|
|
||||||
*/
|
|
||||||
private static $singularMap = [
|
|
||||||
// First entry: singular suffix, reversed
|
|
||||||
// Second entry: length of singular suffix
|
|
||||||
// Third entry: Whether the suffix may succeed a vocal
|
|
||||||
// Fourth entry: Whether the suffix may succeed a consonant
|
|
||||||
// Fifth entry: plural suffix, normal
|
|
||||||
|
|
||||||
// criterion (criteria)
|
|
||||||
['airetirc', 8, false, false, 'criterion'],
|
|
||||||
|
|
||||||
// nebulae (nebula)
|
|
||||||
['aluben', 6, false, false, 'nebulae'],
|
|
||||||
|
|
||||||
// children (child)
|
|
||||||
['dlihc', 5, true, true, 'children'],
|
|
||||||
|
|
||||||
// prices (price)
|
|
||||||
['eci', 3, false, true, 'ices'],
|
|
||||||
|
|
||||||
// services (service)
|
|
||||||
['ecivres', 7, true, true, 'services'],
|
|
||||||
|
|
||||||
// lives (life), wives (wife)
|
|
||||||
['efi', 3, false, true, 'ives'],
|
|
||||||
|
|
||||||
// selfies (selfie)
|
|
||||||
['eifles', 6, true, true, 'selfies'],
|
|
||||||
|
|
||||||
// movies (movie)
|
|
||||||
['eivom', 5, true, true, 'movies'],
|
|
||||||
|
|
||||||
// lice (louse)
|
|
||||||
['esuol', 5, false, true, 'lice'],
|
|
||||||
|
|
||||||
// mice (mouse)
|
|
||||||
['esuom', 5, false, true, 'mice'],
|
|
||||||
|
|
||||||
// geese (goose)
|
|
||||||
['esoo', 4, false, true, 'eese'],
|
|
||||||
|
|
||||||
// houses (house), bases (base)
|
|
||||||
['es', 2, true, true, 'ses'],
|
|
||||||
|
|
||||||
// geese (goose)
|
|
||||||
['esoog', 5, true, true, 'geese'],
|
|
||||||
|
|
||||||
// caves (cave)
|
|
||||||
['ev', 2, true, true, 'ves'],
|
|
||||||
|
|
||||||
// drives (drive)
|
|
||||||
['evird', 5, false, true, 'drives'],
|
|
||||||
|
|
||||||
// objectives (objective), alternative (alternatives)
|
|
||||||
['evit', 4, true, true, 'tives'],
|
|
||||||
|
|
||||||
// moves (move)
|
|
||||||
['evom', 4, true, true, 'moves'],
|
|
||||||
|
|
||||||
// staves (staff)
|
|
||||||
['ffats', 5, true, true, 'staves'],
|
|
||||||
|
|
||||||
// hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf)
|
|
||||||
['ff', 2, true, true, 'ffs'],
|
|
||||||
|
|
||||||
// hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf)
|
|
||||||
['f', 1, true, true, ['fs', 'ves']],
|
|
||||||
|
|
||||||
// arches (arch)
|
|
||||||
['hc', 2, true, true, 'ches'],
|
|
||||||
|
|
||||||
// bushes (bush)
|
|
||||||
['hs', 2, true, true, 'shes'],
|
|
||||||
|
|
||||||
// teeth (tooth)
|
|
||||||
['htoot', 5, true, true, 'teeth'],
|
|
||||||
|
|
||||||
// bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
|
|
||||||
['mu', 2, true, true, 'a'],
|
|
||||||
|
|
||||||
// men (man), women (woman)
|
|
||||||
['nam', 3, true, true, 'men'],
|
|
||||||
|
|
||||||
// people (person)
|
|
||||||
['nosrep', 6, true, true, ['persons', 'people']],
|
|
||||||
|
|
||||||
// bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
|
|
||||||
['noi', 3, true, true, 'ions'],
|
|
||||||
|
|
||||||
// seasons (season), treasons (treason), poisons (poison), lessons (lesson)
|
|
||||||
['nos', 3, true, true, 'sons'],
|
|
||||||
|
|
||||||
// bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
|
|
||||||
['no', 2, true, true, 'a'],
|
|
||||||
|
|
||||||
// echoes (echo)
|
|
||||||
['ohce', 4, true, true, 'echoes'],
|
|
||||||
|
|
||||||
// heroes (hero)
|
|
||||||
['oreh', 4, true, true, 'heroes'],
|
|
||||||
|
|
||||||
// atlases (atlas)
|
|
||||||
['salta', 5, true, true, 'atlases'],
|
|
||||||
|
|
||||||
// irises (iris)
|
|
||||||
['siri', 4, true, true, 'irises'],
|
|
||||||
|
|
||||||
// analyses (analysis), ellipses (ellipsis), neuroses (neurosis)
|
|
||||||
// theses (thesis), emphases (emphasis), oases (oasis),
|
|
||||||
// crises (crisis)
|
|
||||||
['sis', 3, true, true, 'ses'],
|
|
||||||
|
|
||||||
// accesses (access), addresses (address), kisses (kiss)
|
|
||||||
['ss', 2, true, false, 'sses'],
|
|
||||||
|
|
||||||
// syllabi (syllabus)
|
|
||||||
['suballys', 8, true, true, 'syllabi'],
|
|
||||||
|
|
||||||
// buses (bus)
|
|
||||||
['sub', 3, true, true, 'buses'],
|
|
||||||
|
|
||||||
// circuses (circus)
|
|
||||||
['suc', 3, true, true, 'cuses'],
|
|
||||||
|
|
||||||
// fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius)
|
|
||||||
['su', 2, true, true, 'i'],
|
|
||||||
|
|
||||||
// news (news)
|
|
||||||
['swen', 4, true, true, 'news'],
|
|
||||||
|
|
||||||
// feet (foot)
|
|
||||||
['toof', 4, true, true, 'feet'],
|
|
||||||
|
|
||||||
// chateaux (chateau), bureaus (bureau)
|
|
||||||
['uae', 3, false, true, ['eaus', 'eaux']],
|
|
||||||
|
|
||||||
// oxen (ox)
|
|
||||||
['xo', 2, false, false, 'oxen'],
|
|
||||||
|
|
||||||
// hoaxes (hoax)
|
|
||||||
['xaoh', 4, true, false, 'hoaxes'],
|
|
||||||
|
|
||||||
// indices (index)
|
|
||||||
['xedni', 5, false, true, ['indicies', 'indexes']],
|
|
||||||
|
|
||||||
// boxes (box)
|
|
||||||
['xo', 2, false, true, 'oxes'],
|
|
||||||
|
|
||||||
// indexes (index), matrixes (matrix)
|
|
||||||
['x', 1, true, false, ['cies', 'xes']],
|
|
||||||
|
|
||||||
// appendices (appendix)
|
|
||||||
['xi', 2, false, true, 'ices'],
|
|
||||||
|
|
||||||
// babies (baby)
|
|
||||||
['y', 1, false, true, 'ies'],
|
|
||||||
|
|
||||||
// quizzes (quiz)
|
|
||||||
['ziuq', 4, true, false, 'quizzes'],
|
|
||||||
|
|
||||||
// waltzes (waltz)
|
|
||||||
['z', 1, true, true, 'zes'],
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of words which should not be inflected, reversed.
|
|
||||||
*/
|
|
||||||
private static $uninflected = [
|
|
||||||
'atad',
|
|
||||||
'reed',
|
|
||||||
'kcabdeef',
|
|
||||||
'hsif',
|
|
||||||
'ofni',
|
|
||||||
'esoom',
|
|
||||||
'seires',
|
|
||||||
'peehs',
|
|
||||||
'seiceps',
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class should not be instantiated.
|
* This class should not be instantiated.
|
||||||
@ -340,78 +45,11 @@ final class Inflector
|
|||||||
*/
|
*/
|
||||||
public static function singularize(string $plural)
|
public static function singularize(string $plural)
|
||||||
{
|
{
|
||||||
$pluralRev = strrev($plural);
|
if (1 === \count($singulars = self::getEnglishInflector()->singularize($plural))) {
|
||||||
$lowerPluralRev = strtolower($pluralRev);
|
return $singulars[0];
|
||||||
$pluralLength = \strlen($lowerPluralRev);
|
|
||||||
|
|
||||||
// Check if the word is one which is not inflected, return early if so
|
|
||||||
if (\in_array($lowerPluralRev, self::$uninflected, true)) {
|
|
||||||
return $plural;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The outer loop iterates over the entries of the plural table
|
return $singulars;
|
||||||
// The inner loop $j iterates over the characters of the plural suffix
|
|
||||||
// in the plural table to compare them with the characters of the actual
|
|
||||||
// given plural suffix
|
|
||||||
foreach (self::$pluralMap as $map) {
|
|
||||||
$suffix = $map[0];
|
|
||||||
$suffixLength = $map[1];
|
|
||||||
$j = 0;
|
|
||||||
|
|
||||||
// Compare characters in the plural table and of the suffix of the
|
|
||||||
// given plural one by one
|
|
||||||
while ($suffix[$j] === $lowerPluralRev[$j]) {
|
|
||||||
// Let $j point to the next character
|
|
||||||
++$j;
|
|
||||||
|
|
||||||
// Successfully compared the last character
|
|
||||||
// Add an entry with the singular suffix to the singular array
|
|
||||||
if ($j === $suffixLength) {
|
|
||||||
// Is there any character preceding the suffix in the plural string?
|
|
||||||
if ($j < $pluralLength) {
|
|
||||||
$nextIsVocal = false !== strpos('aeiou', $lowerPluralRev[$j]);
|
|
||||||
|
|
||||||
if (!$map[2] && $nextIsVocal) {
|
|
||||||
// suffix may not succeed a vocal but next char is one
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$map[3] && !$nextIsVocal) {
|
|
||||||
// suffix may not succeed a consonant but next char is one
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$newBase = substr($plural, 0, $pluralLength - $suffixLength);
|
|
||||||
$newSuffix = $map[4];
|
|
||||||
|
|
||||||
// Check whether the first character in the plural suffix
|
|
||||||
// is uppercased. If yes, uppercase the first character in
|
|
||||||
// the singular suffix too
|
|
||||||
$firstUpper = ctype_upper($pluralRev[$j - 1]);
|
|
||||||
|
|
||||||
if (\is_array($newSuffix)) {
|
|
||||||
$singulars = [];
|
|
||||||
|
|
||||||
foreach ($newSuffix as $newSuffixEntry) {
|
|
||||||
$singulars[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $singulars;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Suffix is longer than word
|
|
||||||
if ($j === $pluralLength) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assume that plural and singular is identical
|
|
||||||
return $plural;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -426,78 +64,19 @@ final class Inflector
|
|||||||
*/
|
*/
|
||||||
public static function pluralize(string $singular)
|
public static function pluralize(string $singular)
|
||||||
{
|
{
|
||||||
$singularRev = strrev($singular);
|
if (1 === \count($plurals = self::getEnglishInflector()->pluralize($singular))) {
|
||||||
$lowerSingularRev = strtolower($singularRev);
|
return $plurals[0];
|
||||||
$singularLength = \strlen($lowerSingularRev);
|
|
||||||
|
|
||||||
// Check if the word is one which is not inflected, return early if so
|
|
||||||
if (\in_array($lowerSingularRev, self::$uninflected, true)) {
|
|
||||||
return $singular;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The outer loop iterates over the entries of the singular table
|
return $plurals;
|
||||||
// The inner loop $j iterates over the characters of the singular suffix
|
}
|
||||||
// in the singular table to compare them with the characters of the actual
|
|
||||||
// given singular suffix
|
|
||||||
foreach (self::$singularMap as $map) {
|
|
||||||
$suffix = $map[0];
|
|
||||||
$suffixLength = $map[1];
|
|
||||||
$j = 0;
|
|
||||||
|
|
||||||
// Compare characters in the singular table and of the suffix of the
|
private static function getEnglishInflector(): EnglishInflector
|
||||||
// given plural one by one
|
{
|
||||||
|
if (!self::$englishInflector) {
|
||||||
while ($suffix[$j] === $lowerSingularRev[$j]) {
|
self::$englishInflector = new EnglishInflector();
|
||||||
// Let $j point to the next character
|
|
||||||
++$j;
|
|
||||||
|
|
||||||
// Successfully compared the last character
|
|
||||||
// Add an entry with the plural suffix to the plural array
|
|
||||||
if ($j === $suffixLength) {
|
|
||||||
// Is there any character preceding the suffix in the plural string?
|
|
||||||
if ($j < $singularLength) {
|
|
||||||
$nextIsVocal = false !== strpos('aeiou', $lowerSingularRev[$j]);
|
|
||||||
|
|
||||||
if (!$map[2] && $nextIsVocal) {
|
|
||||||
// suffix may not succeed a vocal but next char is one
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$map[3] && !$nextIsVocal) {
|
|
||||||
// suffix may not succeed a consonant but next char is one
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$newBase = substr($singular, 0, $singularLength - $suffixLength);
|
|
||||||
$newSuffix = $map[4];
|
|
||||||
|
|
||||||
// Check whether the first character in the singular suffix
|
|
||||||
// is uppercased. If yes, uppercase the first character in
|
|
||||||
// the singular suffix too
|
|
||||||
$firstUpper = ctype_upper($singularRev[$j - 1]);
|
|
||||||
|
|
||||||
if (\is_array($newSuffix)) {
|
|
||||||
$plurals = [];
|
|
||||||
|
|
||||||
foreach ($newSuffix as $newSuffixEntry) {
|
|
||||||
$plurals[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $plurals;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Suffix is longer than word
|
|
||||||
if ($j === $singularLength) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assume that plural is singular with a trailing `s`
|
return self::$englishInflector;
|
||||||
return $singular.'s';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
Inflector Component
|
Inflector Component
|
||||||
===================
|
===================
|
||||||
|
|
||||||
|
**CAUTION**: this component is deprecated since Symfony 5.1. Instead, use the
|
||||||
|
[String component EnglishInflector](https://github.com/symfony/symfony/tree/master/src/Symfony/Component/String/Inflector/EnglishInflector.php).
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
Inflector converts words between their singular and plural forms (English only).
|
Inflector converts words between their singular and plural forms (English only).
|
||||||
|
|
||||||
Resources
|
Resources
|
||||||
|
@ -14,6 +14,9 @@ namespace Symfony\Component\Inflector\Tests;
|
|||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\Inflector\Inflector;
|
use Symfony\Component\Inflector\Inflector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group legacy
|
||||||
|
*/
|
||||||
class InflectorTest extends TestCase
|
class InflectorTest extends TestCase
|
||||||
{
|
{
|
||||||
public function singularizeProvider()
|
public function singularizeProvider()
|
||||||
|
@ -24,7 +24,9 @@
|
|||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.2.5",
|
"php": "^7.2.5",
|
||||||
"symfony/polyfill-ctype": "~1.8"
|
"symfony/deprecation-contracts": "^2.1",
|
||||||
|
"symfony/polyfill-ctype": "~1.8",
|
||||||
|
"symfony/string": "^5.1"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": { "Symfony\\Component\\Inflector\\": "" },
|
"psr-4": { "Symfony\\Component\\Inflector\\": "" },
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\PropertyInfo\Extractor;
|
namespace Symfony\Component\PropertyInfo\Extractor;
|
||||||
|
|
||||||
use Symfony\Component\Inflector\Inflector;
|
|
||||||
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
|
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
|
||||||
use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface;
|
use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface;
|
||||||
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
|
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
|
||||||
@ -21,6 +20,8 @@ use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
|
|||||||
use Symfony\Component\PropertyInfo\PropertyWriteInfo;
|
use Symfony\Component\PropertyInfo\PropertyWriteInfo;
|
||||||
use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface;
|
use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface;
|
||||||
use Symfony\Component\PropertyInfo\Type;
|
use Symfony\Component\PropertyInfo\Type;
|
||||||
|
use Symfony\Component\String\Inflector\EnglishInflector;
|
||||||
|
use Symfony\Component\String\Inflector\InflectorInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts data using the reflection API.
|
* Extracts data using the reflection API.
|
||||||
@ -62,6 +63,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
|
|||||||
private $enableConstructorExtraction;
|
private $enableConstructorExtraction;
|
||||||
private $methodReflectionFlags;
|
private $methodReflectionFlags;
|
||||||
private $propertyReflectionFlags;
|
private $propertyReflectionFlags;
|
||||||
|
private $inflector;
|
||||||
|
|
||||||
private $arrayMutatorPrefixesFirst;
|
private $arrayMutatorPrefixesFirst;
|
||||||
private $arrayMutatorPrefixesLast;
|
private $arrayMutatorPrefixesLast;
|
||||||
@ -71,7 +73,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
|
|||||||
* @param string[]|null $accessorPrefixes
|
* @param string[]|null $accessorPrefixes
|
||||||
* @param string[]|null $arrayMutatorPrefixes
|
* @param string[]|null $arrayMutatorPrefixes
|
||||||
*/
|
*/
|
||||||
public function __construct(array $mutatorPrefixes = null, array $accessorPrefixes = null, array $arrayMutatorPrefixes = null, bool $enableConstructorExtraction = true, int $accessFlags = self::ALLOW_PUBLIC)
|
public function __construct(array $mutatorPrefixes = null, array $accessorPrefixes = null, array $arrayMutatorPrefixes = null, bool $enableConstructorExtraction = true, int $accessFlags = self::ALLOW_PUBLIC, InflectorInterface $inflector = null)
|
||||||
{
|
{
|
||||||
$this->mutatorPrefixes = null !== $mutatorPrefixes ? $mutatorPrefixes : self::$defaultMutatorPrefixes;
|
$this->mutatorPrefixes = null !== $mutatorPrefixes ? $mutatorPrefixes : self::$defaultMutatorPrefixes;
|
||||||
$this->accessorPrefixes = null !== $accessorPrefixes ? $accessorPrefixes : self::$defaultAccessorPrefixes;
|
$this->accessorPrefixes = null !== $accessorPrefixes ? $accessorPrefixes : self::$defaultAccessorPrefixes;
|
||||||
@ -79,6 +81,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
|
|||||||
$this->enableConstructorExtraction = $enableConstructorExtraction;
|
$this->enableConstructorExtraction = $enableConstructorExtraction;
|
||||||
$this->methodReflectionFlags = $this->getMethodsFlags($accessFlags);
|
$this->methodReflectionFlags = $this->getMethodsFlags($accessFlags);
|
||||||
$this->propertyReflectionFlags = $this->getPropertyFlags($accessFlags);
|
$this->propertyReflectionFlags = $this->getPropertyFlags($accessFlags);
|
||||||
|
$this->inflector = $inflector ?? new EnglishInflector();
|
||||||
|
|
||||||
$this->arrayMutatorPrefixesFirst = array_merge($this->arrayMutatorPrefixes, array_diff($this->mutatorPrefixes, $this->arrayMutatorPrefixes));
|
$this->arrayMutatorPrefixesFirst = array_merge($this->arrayMutatorPrefixes, array_diff($this->mutatorPrefixes, $this->arrayMutatorPrefixes));
|
||||||
$this->arrayMutatorPrefixesLast = array_reverse($this->arrayMutatorPrefixesFirst);
|
$this->arrayMutatorPrefixesLast = array_reverse($this->arrayMutatorPrefixesFirst);
|
||||||
@ -284,7 +287,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
|
|||||||
|
|
||||||
$camelized = $this->camelize($property);
|
$camelized = $this->camelize($property);
|
||||||
$constructor = $reflClass->getConstructor();
|
$constructor = $reflClass->getConstructor();
|
||||||
$singulars = (array) Inflector::singularize($camelized);
|
$singulars = $this->inflector->singularize($camelized);
|
||||||
$errors = [];
|
$errors = [];
|
||||||
|
|
||||||
if (null !== $constructor && $allowConstruct) {
|
if (null !== $constructor && $allowConstruct) {
|
||||||
@ -552,7 +555,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
|
|||||||
private function getMutatorMethod(string $class, string $property): ?array
|
private function getMutatorMethod(string $class, string $property): ?array
|
||||||
{
|
{
|
||||||
$ucProperty = ucfirst($property);
|
$ucProperty = ucfirst($property);
|
||||||
$ucSingulars = (array) Inflector::singularize($ucProperty);
|
$ucSingulars = $this->inflector->singularize($ucProperty);
|
||||||
|
|
||||||
$mutatorPrefixes = \in_array($ucProperty, $ucSingulars, true) ? $this->arrayMutatorPrefixesLast : $this->arrayMutatorPrefixesFirst;
|
$mutatorPrefixes = \in_array($ucProperty, $ucSingulars, true) ? $this->arrayMutatorPrefixesLast : $this->arrayMutatorPrefixesFirst;
|
||||||
|
|
||||||
@ -592,7 +595,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach ($reflectionProperties as $reflectionProperty) {
|
foreach ($reflectionProperties as $reflectionProperty) {
|
||||||
foreach ((array) Inflector::singularize($reflectionProperty->name) as $name) {
|
foreach ($this->inflector->singularize($reflectionProperty->name) as $name) {
|
||||||
if (strtolower($name) === strtolower($matches[2])) {
|
if (strtolower($name) === strtolower($matches[2])) {
|
||||||
return $reflectionProperty->name;
|
return $reflectionProperty->name;
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,8 @@
|
|||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.2.5",
|
"php": "^7.2.5",
|
||||||
"symfony/inflector": "^4.4|^5.0",
|
"symfony/polyfill-php80": "^1.15",
|
||||||
"symfony/polyfill-php80": "^1.15"
|
"symfony/string": "^5.1"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"symfony/serializer": "^4.4|^5.0",
|
"symfony/serializer": "^4.4|^5.0",
|
||||||
|
477
src/Symfony/Component/String/Inflector/EnglishInflector.php
Normal file
477
src/Symfony/Component/String/Inflector/EnglishInflector.php
Normal file
@ -0,0 +1,477 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\String\Inflector;
|
||||||
|
|
||||||
|
final class EnglishInflector implements InflectorInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Map English plural to singular suffixes.
|
||||||
|
*
|
||||||
|
* @see http://english-zone.com/spelling/plurals.html
|
||||||
|
*/
|
||||||
|
private static $pluralMap = [
|
||||||
|
// First entry: plural suffix, reversed
|
||||||
|
// Second entry: length of plural suffix
|
||||||
|
// Third entry: Whether the suffix may succeed a vocal
|
||||||
|
// Fourth entry: Whether the suffix may succeed a consonant
|
||||||
|
// Fifth entry: singular suffix, normal
|
||||||
|
|
||||||
|
// bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
|
||||||
|
['a', 1, true, true, ['on', 'um']],
|
||||||
|
|
||||||
|
// nebulae (nebula)
|
||||||
|
['ea', 2, true, true, 'a'],
|
||||||
|
|
||||||
|
// services (service)
|
||||||
|
['secivres', 8, true, true, 'service'],
|
||||||
|
|
||||||
|
// mice (mouse), lice (louse)
|
||||||
|
['eci', 3, false, true, 'ouse'],
|
||||||
|
|
||||||
|
// geese (goose)
|
||||||
|
['esee', 4, false, true, 'oose'],
|
||||||
|
|
||||||
|
// fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius)
|
||||||
|
['i', 1, true, true, 'us'],
|
||||||
|
|
||||||
|
// men (man), women (woman)
|
||||||
|
['nem', 3, true, true, 'man'],
|
||||||
|
|
||||||
|
// children (child)
|
||||||
|
['nerdlihc', 8, true, true, 'child'],
|
||||||
|
|
||||||
|
// oxen (ox)
|
||||||
|
['nexo', 4, false, false, 'ox'],
|
||||||
|
|
||||||
|
// indices (index), appendices (appendix), prices (price)
|
||||||
|
['seci', 4, false, true, ['ex', 'ix', 'ice']],
|
||||||
|
|
||||||
|
// selfies (selfie)
|
||||||
|
['seifles', 7, true, true, 'selfie'],
|
||||||
|
|
||||||
|
// movies (movie)
|
||||||
|
['seivom', 6, true, true, 'movie'],
|
||||||
|
|
||||||
|
// feet (foot)
|
||||||
|
['teef', 4, true, true, 'foot'],
|
||||||
|
|
||||||
|
// geese (goose)
|
||||||
|
['eseeg', 5, true, true, 'goose'],
|
||||||
|
|
||||||
|
// teeth (tooth)
|
||||||
|
['hteet', 5, true, true, 'tooth'],
|
||||||
|
|
||||||
|
// news (news)
|
||||||
|
['swen', 4, true, true, 'news'],
|
||||||
|
|
||||||
|
// series (series)
|
||||||
|
['seires', 6, true, true, 'series'],
|
||||||
|
|
||||||
|
// babies (baby)
|
||||||
|
['sei', 3, false, true, 'y'],
|
||||||
|
|
||||||
|
// accesses (access), addresses (address), kisses (kiss)
|
||||||
|
['sess', 4, true, false, 'ss'],
|
||||||
|
|
||||||
|
// analyses (analysis), ellipses (ellipsis), fungi (fungus),
|
||||||
|
// neuroses (neurosis), theses (thesis), emphases (emphasis),
|
||||||
|
// oases (oasis), crises (crisis), houses (house), bases (base),
|
||||||
|
// atlases (atlas)
|
||||||
|
['ses', 3, true, true, ['s', 'se', 'sis']],
|
||||||
|
|
||||||
|
// objectives (objective), alternative (alternatives)
|
||||||
|
['sevit', 5, true, true, 'tive'],
|
||||||
|
|
||||||
|
// drives (drive)
|
||||||
|
['sevird', 6, false, true, 'drive'],
|
||||||
|
|
||||||
|
// lives (life), wives (wife)
|
||||||
|
['sevi', 4, false, true, 'ife'],
|
||||||
|
|
||||||
|
// moves (move)
|
||||||
|
['sevom', 5, true, true, 'move'],
|
||||||
|
|
||||||
|
// hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf), caves (cave), staves (staff)
|
||||||
|
['sev', 3, true, true, ['f', 've', 'ff']],
|
||||||
|
|
||||||
|
// axes (axis), axes (ax), axes (axe)
|
||||||
|
['sexa', 4, false, false, ['ax', 'axe', 'axis']],
|
||||||
|
|
||||||
|
// indexes (index), matrixes (matrix)
|
||||||
|
['sex', 3, true, false, 'x'],
|
||||||
|
|
||||||
|
// quizzes (quiz)
|
||||||
|
['sezz', 4, true, false, 'z'],
|
||||||
|
|
||||||
|
// bureaus (bureau)
|
||||||
|
['suae', 4, false, true, 'eau'],
|
||||||
|
|
||||||
|
// fees (fee), trees (tree), employees (employee)
|
||||||
|
['see', 3, true, true, 'ee'],
|
||||||
|
|
||||||
|
// roses (rose), garages (garage), cassettes (cassette),
|
||||||
|
// waltzes (waltz), heroes (hero), bushes (bush), arches (arch),
|
||||||
|
// shoes (shoe)
|
||||||
|
['se', 2, true, true, ['', 'e']],
|
||||||
|
|
||||||
|
// tags (tag)
|
||||||
|
['s', 1, true, true, ''],
|
||||||
|
|
||||||
|
// chateaux (chateau)
|
||||||
|
['xuae', 4, false, true, 'eau'],
|
||||||
|
|
||||||
|
// people (person)
|
||||||
|
['elpoep', 6, true, true, 'person'],
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map English singular to plural suffixes.
|
||||||
|
*
|
||||||
|
* @see http://english-zone.com/spelling/plurals.html
|
||||||
|
*/
|
||||||
|
private static $singularMap = [
|
||||||
|
// First entry: singular suffix, reversed
|
||||||
|
// Second entry: length of singular suffix
|
||||||
|
// Third entry: Whether the suffix may succeed a vocal
|
||||||
|
// Fourth entry: Whether the suffix may succeed a consonant
|
||||||
|
// Fifth entry: plural suffix, normal
|
||||||
|
|
||||||
|
// criterion (criteria)
|
||||||
|
['airetirc', 8, false, false, 'criterion'],
|
||||||
|
|
||||||
|
// nebulae (nebula)
|
||||||
|
['aluben', 6, false, false, 'nebulae'],
|
||||||
|
|
||||||
|
// children (child)
|
||||||
|
['dlihc', 5, true, true, 'children'],
|
||||||
|
|
||||||
|
// prices (price)
|
||||||
|
['eci', 3, false, true, 'ices'],
|
||||||
|
|
||||||
|
// services (service)
|
||||||
|
['ecivres', 7, true, true, 'services'],
|
||||||
|
|
||||||
|
// lives (life), wives (wife)
|
||||||
|
['efi', 3, false, true, 'ives'],
|
||||||
|
|
||||||
|
// selfies (selfie)
|
||||||
|
['eifles', 6, true, true, 'selfies'],
|
||||||
|
|
||||||
|
// movies (movie)
|
||||||
|
['eivom', 5, true, true, 'movies'],
|
||||||
|
|
||||||
|
// lice (louse)
|
||||||
|
['esuol', 5, false, true, 'lice'],
|
||||||
|
|
||||||
|
// mice (mouse)
|
||||||
|
['esuom', 5, false, true, 'mice'],
|
||||||
|
|
||||||
|
// geese (goose)
|
||||||
|
['esoo', 4, false, true, 'eese'],
|
||||||
|
|
||||||
|
// houses (house), bases (base)
|
||||||
|
['es', 2, true, true, 'ses'],
|
||||||
|
|
||||||
|
// geese (goose)
|
||||||
|
['esoog', 5, true, true, 'geese'],
|
||||||
|
|
||||||
|
// caves (cave)
|
||||||
|
['ev', 2, true, true, 'ves'],
|
||||||
|
|
||||||
|
// drives (drive)
|
||||||
|
['evird', 5, false, true, 'drives'],
|
||||||
|
|
||||||
|
// objectives (objective), alternative (alternatives)
|
||||||
|
['evit', 4, true, true, 'tives'],
|
||||||
|
|
||||||
|
// moves (move)
|
||||||
|
['evom', 4, true, true, 'moves'],
|
||||||
|
|
||||||
|
// staves (staff)
|
||||||
|
['ffats', 5, true, true, 'staves'],
|
||||||
|
|
||||||
|
// hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf)
|
||||||
|
['ff', 2, true, true, 'ffs'],
|
||||||
|
|
||||||
|
// hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf)
|
||||||
|
['f', 1, true, true, ['fs', 'ves']],
|
||||||
|
|
||||||
|
// arches (arch)
|
||||||
|
['hc', 2, true, true, 'ches'],
|
||||||
|
|
||||||
|
// bushes (bush)
|
||||||
|
['hs', 2, true, true, 'shes'],
|
||||||
|
|
||||||
|
// teeth (tooth)
|
||||||
|
['htoot', 5, true, true, 'teeth'],
|
||||||
|
|
||||||
|
// bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
|
||||||
|
['mu', 2, true, true, 'a'],
|
||||||
|
|
||||||
|
// men (man), women (woman)
|
||||||
|
['nam', 3, true, true, 'men'],
|
||||||
|
|
||||||
|
// people (person)
|
||||||
|
['nosrep', 6, true, true, ['persons', 'people']],
|
||||||
|
|
||||||
|
// bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
|
||||||
|
['noi', 3, true, true, 'ions'],
|
||||||
|
|
||||||
|
// seasons (season), treasons (treason), poisons (poison), lessons (lesson)
|
||||||
|
['nos', 3, true, true, 'sons'],
|
||||||
|
|
||||||
|
// bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
|
||||||
|
['no', 2, true, true, 'a'],
|
||||||
|
|
||||||
|
// echoes (echo)
|
||||||
|
['ohce', 4, true, true, 'echoes'],
|
||||||
|
|
||||||
|
// heroes (hero)
|
||||||
|
['oreh', 4, true, true, 'heroes'],
|
||||||
|
|
||||||
|
// atlases (atlas)
|
||||||
|
['salta', 5, true, true, 'atlases'],
|
||||||
|
|
||||||
|
// irises (iris)
|
||||||
|
['siri', 4, true, true, 'irises'],
|
||||||
|
|
||||||
|
// analyses (analysis), ellipses (ellipsis), neuroses (neurosis)
|
||||||
|
// theses (thesis), emphases (emphasis), oases (oasis),
|
||||||
|
// crises (crisis)
|
||||||
|
['sis', 3, true, true, 'ses'],
|
||||||
|
|
||||||
|
// accesses (access), addresses (address), kisses (kiss)
|
||||||
|
['ss', 2, true, false, 'sses'],
|
||||||
|
|
||||||
|
// syllabi (syllabus)
|
||||||
|
['suballys', 8, true, true, 'syllabi'],
|
||||||
|
|
||||||
|
// buses (bus)
|
||||||
|
['sub', 3, true, true, 'buses'],
|
||||||
|
|
||||||
|
// circuses (circus)
|
||||||
|
['suc', 3, true, true, 'cuses'],
|
||||||
|
|
||||||
|
// fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius)
|
||||||
|
['su', 2, true, true, 'i'],
|
||||||
|
|
||||||
|
// news (news)
|
||||||
|
['swen', 4, true, true, 'news'],
|
||||||
|
|
||||||
|
// feet (foot)
|
||||||
|
['toof', 4, true, true, 'feet'],
|
||||||
|
|
||||||
|
// chateaux (chateau), bureaus (bureau)
|
||||||
|
['uae', 3, false, true, ['eaus', 'eaux']],
|
||||||
|
|
||||||
|
// oxen (ox)
|
||||||
|
['xo', 2, false, false, 'oxen'],
|
||||||
|
|
||||||
|
// hoaxes (hoax)
|
||||||
|
['xaoh', 4, true, false, 'hoaxes'],
|
||||||
|
|
||||||
|
// indices (index)
|
||||||
|
['xedni', 5, false, true, ['indicies', 'indexes']],
|
||||||
|
|
||||||
|
// boxes (box)
|
||||||
|
['xo', 2, false, true, 'oxes'],
|
||||||
|
|
||||||
|
// indexes (index), matrixes (matrix)
|
||||||
|
['x', 1, true, false, ['cies', 'xes']],
|
||||||
|
|
||||||
|
// appendices (appendix)
|
||||||
|
['xi', 2, false, true, 'ices'],
|
||||||
|
|
||||||
|
// babies (baby)
|
||||||
|
['y', 1, false, true, 'ies'],
|
||||||
|
|
||||||
|
// quizzes (quiz)
|
||||||
|
['ziuq', 4, true, false, 'quizzes'],
|
||||||
|
|
||||||
|
// waltzes (waltz)
|
||||||
|
['z', 1, true, true, 'zes'],
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of words which should not be inflected, reversed.
|
||||||
|
*/
|
||||||
|
private static $uninflected = [
|
||||||
|
'atad',
|
||||||
|
'reed',
|
||||||
|
'kcabdeef',
|
||||||
|
'hsif',
|
||||||
|
'ofni',
|
||||||
|
'esoom',
|
||||||
|
'seires',
|
||||||
|
'peehs',
|
||||||
|
'seiceps',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function singularize(string $plural): array
|
||||||
|
{
|
||||||
|
$pluralRev = strrev($plural);
|
||||||
|
$lowerPluralRev = strtolower($pluralRev);
|
||||||
|
$pluralLength = \strlen($lowerPluralRev);
|
||||||
|
|
||||||
|
// Check if the word is one which is not inflected, return early if so
|
||||||
|
if (\in_array($lowerPluralRev, self::$uninflected, true)) {
|
||||||
|
return [$plural];
|
||||||
|
}
|
||||||
|
|
||||||
|
// The outer loop iterates over the entries of the plural table
|
||||||
|
// The inner loop $j iterates over the characters of the plural suffix
|
||||||
|
// in the plural table to compare them with the characters of the actual
|
||||||
|
// given plural suffix
|
||||||
|
foreach (self::$pluralMap as $map) {
|
||||||
|
$suffix = $map[0];
|
||||||
|
$suffixLength = $map[1];
|
||||||
|
$j = 0;
|
||||||
|
|
||||||
|
// Compare characters in the plural table and of the suffix of the
|
||||||
|
// given plural one by one
|
||||||
|
while ($suffix[$j] === $lowerPluralRev[$j]) {
|
||||||
|
// Let $j point to the next character
|
||||||
|
++$j;
|
||||||
|
|
||||||
|
// Successfully compared the last character
|
||||||
|
// Add an entry with the singular suffix to the singular array
|
||||||
|
if ($j === $suffixLength) {
|
||||||
|
// Is there any character preceding the suffix in the plural string?
|
||||||
|
if ($j < $pluralLength) {
|
||||||
|
$nextIsVocal = false !== strpos('aeiou', $lowerPluralRev[$j]);
|
||||||
|
|
||||||
|
if (!$map[2] && $nextIsVocal) {
|
||||||
|
// suffix may not succeed a vocal but next char is one
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$map[3] && !$nextIsVocal) {
|
||||||
|
// suffix may not succeed a consonant but next char is one
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$newBase = substr($plural, 0, $pluralLength - $suffixLength);
|
||||||
|
$newSuffix = $map[4];
|
||||||
|
|
||||||
|
// Check whether the first character in the plural suffix
|
||||||
|
// is uppercased. If yes, uppercase the first character in
|
||||||
|
// the singular suffix too
|
||||||
|
$firstUpper = ctype_upper($pluralRev[$j - 1]);
|
||||||
|
|
||||||
|
if (\is_array($newSuffix)) {
|
||||||
|
$singulars = [];
|
||||||
|
|
||||||
|
foreach ($newSuffix as $newSuffixEntry) {
|
||||||
|
$singulars[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $singulars;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suffix is longer than word
|
||||||
|
if ($j === $pluralLength) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume that plural and singular is identical
|
||||||
|
return [$plural];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function pluralize(string $singular): array
|
||||||
|
{
|
||||||
|
$singularRev = strrev($singular);
|
||||||
|
$lowerSingularRev = strtolower($singularRev);
|
||||||
|
$singularLength = \strlen($lowerSingularRev);
|
||||||
|
|
||||||
|
// Check if the word is one which is not inflected, return early if so
|
||||||
|
if (\in_array($lowerSingularRev, self::$uninflected, true)) {
|
||||||
|
return [$singular];
|
||||||
|
}
|
||||||
|
|
||||||
|
// The outer loop iterates over the entries of the singular table
|
||||||
|
// The inner loop $j iterates over the characters of the singular suffix
|
||||||
|
// in the singular table to compare them with the characters of the actual
|
||||||
|
// given singular suffix
|
||||||
|
foreach (self::$singularMap as $map) {
|
||||||
|
$suffix = $map[0];
|
||||||
|
$suffixLength = $map[1];
|
||||||
|
$j = 0;
|
||||||
|
|
||||||
|
// Compare characters in the singular table and of the suffix of the
|
||||||
|
// given plural one by one
|
||||||
|
|
||||||
|
while ($suffix[$j] === $lowerSingularRev[$j]) {
|
||||||
|
// Let $j point to the next character
|
||||||
|
++$j;
|
||||||
|
|
||||||
|
// Successfully compared the last character
|
||||||
|
// Add an entry with the plural suffix to the plural array
|
||||||
|
if ($j === $suffixLength) {
|
||||||
|
// Is there any character preceding the suffix in the plural string?
|
||||||
|
if ($j < $singularLength) {
|
||||||
|
$nextIsVocal = false !== strpos('aeiou', $lowerSingularRev[$j]);
|
||||||
|
|
||||||
|
if (!$map[2] && $nextIsVocal) {
|
||||||
|
// suffix may not succeed a vocal but next char is one
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$map[3] && !$nextIsVocal) {
|
||||||
|
// suffix may not succeed a consonant but next char is one
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$newBase = substr($singular, 0, $singularLength - $suffixLength);
|
||||||
|
$newSuffix = $map[4];
|
||||||
|
|
||||||
|
// Check whether the first character in the singular suffix
|
||||||
|
// is uppercased. If yes, uppercase the first character in
|
||||||
|
// the singular suffix too
|
||||||
|
$firstUpper = ctype_upper($singularRev[$j - 1]);
|
||||||
|
|
||||||
|
if (\is_array($newSuffix)) {
|
||||||
|
$plurals = [];
|
||||||
|
|
||||||
|
foreach ($newSuffix as $newSuffixEntry) {
|
||||||
|
$plurals[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $plurals;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suffix is longer than word
|
||||||
|
if ($j === $singularLength) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume that plural is singular with a trailing `s`
|
||||||
|
return [$singular.'s'];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\String\Inflector;
|
||||||
|
|
||||||
|
interface InflectorInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns the singular forms of a string.
|
||||||
|
*
|
||||||
|
* If the method can't determine the form with certainty, several possible singulars are returned.
|
||||||
|
*
|
||||||
|
* @return string[] An array of possible singular forms
|
||||||
|
*/
|
||||||
|
public function singularize(string $plural): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the plural forms of a string.
|
||||||
|
*
|
||||||
|
* If the method can't determine the form with certainty, several possible plurals are returned.
|
||||||
|
*
|
||||||
|
* @return string[] An array of possible plural forms
|
||||||
|
*/
|
||||||
|
public function pluralize(string $singular): array;
|
||||||
|
}
|
309
src/Symfony/Component/String/Tests/EnglishInflectorTest.php
Normal file
309
src/Symfony/Component/String/Tests/EnglishInflectorTest.php
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\String\Tests;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\String\Inflector\EnglishInflector;
|
||||||
|
|
||||||
|
class EnglishInflectorTest extends TestCase
|
||||||
|
{
|
||||||
|
public function singularizeProvider()
|
||||||
|
{
|
||||||
|
// see http://english-zone.com/spelling/plurals.html
|
||||||
|
// see http://www.scribd.com/doc/3271143/List-of-100-Irregular-Plural-Nouns-in-English
|
||||||
|
return [
|
||||||
|
['accesses', 'access'],
|
||||||
|
['addresses', 'address'],
|
||||||
|
['agendas', 'agenda'],
|
||||||
|
['alumnae', 'alumna'],
|
||||||
|
['alumni', 'alumnus'],
|
||||||
|
['analyses', ['analys', 'analyse', 'analysis']],
|
||||||
|
['antennae', 'antenna'],
|
||||||
|
['antennas', 'antenna'],
|
||||||
|
['appendices', ['appendex', 'appendix', 'appendice']],
|
||||||
|
['arches', ['arch', 'arche']],
|
||||||
|
['atlases', ['atlas', 'atlase', 'atlasis']],
|
||||||
|
['axes', ['ax', 'axe', 'axis']],
|
||||||
|
['babies', 'baby'],
|
||||||
|
['bacteria', ['bacterion', 'bacterium']],
|
||||||
|
['bases', ['bas', 'base', 'basis']],
|
||||||
|
['batches', ['batch', 'batche']],
|
||||||
|
['beaux', 'beau'],
|
||||||
|
['bees', 'bee'],
|
||||||
|
['boxes', 'box'],
|
||||||
|
['boys', 'boy'],
|
||||||
|
['bureaus', 'bureau'],
|
||||||
|
['bureaux', 'bureau'],
|
||||||
|
['buses', ['bus', 'buse', 'busis']],
|
||||||
|
['bushes', ['bush', 'bushe']],
|
||||||
|
['calves', ['calf', 'calve', 'calff']],
|
||||||
|
['cars', 'car'],
|
||||||
|
['cassettes', ['cassett', 'cassette']],
|
||||||
|
['caves', ['caf', 'cave', 'caff']],
|
||||||
|
['chateaux', 'chateau'],
|
||||||
|
['cheeses', ['chees', 'cheese', 'cheesis']],
|
||||||
|
['children', 'child'],
|
||||||
|
['circuses', ['circus', 'circuse', 'circusis']],
|
||||||
|
['cliffs', 'cliff'],
|
||||||
|
['committee', 'committee'],
|
||||||
|
['crises', ['cris', 'crise', 'crisis']],
|
||||||
|
['criteria', ['criterion', 'criterium']],
|
||||||
|
['cups', 'cup'],
|
||||||
|
['data', 'data'],
|
||||||
|
['days', 'day'],
|
||||||
|
['discos', 'disco'],
|
||||||
|
['devices', ['devex', 'devix', 'device']],
|
||||||
|
['drives', 'drive'],
|
||||||
|
['drivers', 'driver'],
|
||||||
|
['dwarves', ['dwarf', 'dwarve', 'dwarff']],
|
||||||
|
['echoes', ['echo', 'echoe']],
|
||||||
|
['elves', ['elf', 'elve', 'elff']],
|
||||||
|
['emphases', ['emphas', 'emphase', 'emphasis']],
|
||||||
|
['employees', 'employee'],
|
||||||
|
['faxes', 'fax'],
|
||||||
|
['fees', 'fee'],
|
||||||
|
['feet', 'foot'],
|
||||||
|
['feedback', 'feedback'],
|
||||||
|
['foci', 'focus'],
|
||||||
|
['focuses', ['focus', 'focuse', 'focusis']],
|
||||||
|
['formulae', 'formula'],
|
||||||
|
['formulas', 'formula'],
|
||||||
|
['fungi', 'fungus'],
|
||||||
|
['funguses', ['fungus', 'funguse', 'fungusis']],
|
||||||
|
['garages', ['garag', 'garage']],
|
||||||
|
['geese', 'goose'],
|
||||||
|
['halves', ['half', 'halve', 'halff']],
|
||||||
|
['hats', 'hat'],
|
||||||
|
['heroes', ['hero', 'heroe']],
|
||||||
|
['hippopotamuses', ['hippopotamus', 'hippopotamuse', 'hippopotamusis']], //hippopotami
|
||||||
|
['hoaxes', 'hoax'],
|
||||||
|
['hooves', ['hoof', 'hoove', 'hooff']],
|
||||||
|
['houses', ['hous', 'house', 'housis']],
|
||||||
|
['indexes', 'index'],
|
||||||
|
['indices', ['index', 'indix', 'indice']],
|
||||||
|
['ions', 'ion'],
|
||||||
|
['irises', ['iris', 'irise', 'irisis']],
|
||||||
|
['kisses', 'kiss'],
|
||||||
|
['knives', 'knife'],
|
||||||
|
['lamps', 'lamp'],
|
||||||
|
['lessons', 'lesson'],
|
||||||
|
['leaves', ['leaf', 'leave', 'leaff']],
|
||||||
|
['lice', 'louse'],
|
||||||
|
['lives', 'life'],
|
||||||
|
['matrices', ['matrex', 'matrix', 'matrice']],
|
||||||
|
['matrixes', 'matrix'],
|
||||||
|
['men', 'man'],
|
||||||
|
['mice', 'mouse'],
|
||||||
|
['moves', 'move'],
|
||||||
|
['movies', 'movie'],
|
||||||
|
['nebulae', 'nebula'],
|
||||||
|
['neuroses', ['neuros', 'neurose', 'neurosis']],
|
||||||
|
['news', 'news'],
|
||||||
|
['oases', ['oas', 'oase', 'oasis']],
|
||||||
|
['objectives', 'objective'],
|
||||||
|
['oxen', 'ox'],
|
||||||
|
['parties', 'party'],
|
||||||
|
['people', 'person'],
|
||||||
|
['persons', 'person'],
|
||||||
|
['phenomena', ['phenomenon', 'phenomenum']],
|
||||||
|
['photos', 'photo'],
|
||||||
|
['pianos', 'piano'],
|
||||||
|
['plateaux', 'plateau'],
|
||||||
|
['poisons', 'poison'],
|
||||||
|
['poppies', 'poppy'],
|
||||||
|
['prices', ['prex', 'prix', 'price']],
|
||||||
|
['quizzes', 'quiz'],
|
||||||
|
['radii', 'radius'],
|
||||||
|
['roofs', 'roof'],
|
||||||
|
['roses', ['ros', 'rose', 'rosis']],
|
||||||
|
['sandwiches', ['sandwich', 'sandwiche']],
|
||||||
|
['scarves', ['scarf', 'scarve', 'scarff']],
|
||||||
|
['schemas', 'schema'], //schemata
|
||||||
|
['seasons', 'season'],
|
||||||
|
['selfies', 'selfie'],
|
||||||
|
['series', 'series'],
|
||||||
|
['services', 'service'],
|
||||||
|
['sheriffs', 'sheriff'],
|
||||||
|
['shoes', ['sho', 'shoe']],
|
||||||
|
['species', 'species'],
|
||||||
|
['spies', 'spy'],
|
||||||
|
['staves', ['staf', 'stave', 'staff']],
|
||||||
|
['stories', 'story'],
|
||||||
|
['strata', ['straton', 'stratum']],
|
||||||
|
['suitcases', ['suitcas', 'suitcase', 'suitcasis']],
|
||||||
|
['syllabi', 'syllabus'],
|
||||||
|
['tags', 'tag'],
|
||||||
|
['teeth', 'tooth'],
|
||||||
|
['theses', ['thes', 'these', 'thesis']],
|
||||||
|
['thieves', ['thief', 'thieve', 'thieff']],
|
||||||
|
['treasons', 'treason'],
|
||||||
|
['trees', 'tree'],
|
||||||
|
['waltzes', ['waltz', 'waltze']],
|
||||||
|
['wives', 'wife'],
|
||||||
|
|
||||||
|
// test casing: if the first letter was uppercase, it should remain so
|
||||||
|
['Men', 'Man'],
|
||||||
|
['GrandChildren', 'GrandChild'],
|
||||||
|
['SubTrees', 'SubTree'],
|
||||||
|
|
||||||
|
// Known issues
|
||||||
|
//['insignia', 'insigne'],
|
||||||
|
//['insignias', 'insigne'],
|
||||||
|
//['rattles', 'rattle'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function pluralizeProvider()
|
||||||
|
{
|
||||||
|
// see http://english-zone.com/spelling/plurals.html
|
||||||
|
// see http://www.scribd.com/doc/3271143/List-of-100-Irregular-Plural-Nouns-in-English
|
||||||
|
return [
|
||||||
|
['access', 'accesses'],
|
||||||
|
['address', 'addresses'],
|
||||||
|
['agenda', 'agendas'],
|
||||||
|
['alumnus', 'alumni'],
|
||||||
|
['analysis', 'analyses'],
|
||||||
|
['antenna', 'antennas'], //antennae
|
||||||
|
['appendix', ['appendicies', 'appendixes']],
|
||||||
|
['arch', 'arches'],
|
||||||
|
['atlas', 'atlases'],
|
||||||
|
['axe', 'axes'],
|
||||||
|
['baby', 'babies'],
|
||||||
|
['bacterium', 'bacteria'],
|
||||||
|
['base', 'bases'],
|
||||||
|
['batch', 'batches'],
|
||||||
|
['beau', ['beaus', 'beaux']],
|
||||||
|
['bee', 'bees'],
|
||||||
|
['box', 'boxes'],
|
||||||
|
['boy', 'boys'],
|
||||||
|
['bureau', ['bureaus', 'bureaux']],
|
||||||
|
['bus', 'buses'],
|
||||||
|
['bush', 'bushes'],
|
||||||
|
['calf', ['calfs', 'calves']],
|
||||||
|
['car', 'cars'],
|
||||||
|
['cassette', 'cassettes'],
|
||||||
|
['cave', 'caves'],
|
||||||
|
['chateau', ['chateaus', 'chateaux']],
|
||||||
|
['cheese', 'cheeses'],
|
||||||
|
['child', 'children'],
|
||||||
|
['circus', 'circuses'],
|
||||||
|
['cliff', 'cliffs'],
|
||||||
|
['committee', 'committees'],
|
||||||
|
['crisis', 'crises'],
|
||||||
|
['criteria', 'criterion'],
|
||||||
|
['cup', 'cups'],
|
||||||
|
['data', 'data'],
|
||||||
|
['day', 'days'],
|
||||||
|
['disco', 'discos'],
|
||||||
|
['device', 'devices'],
|
||||||
|
['drive', 'drives'],
|
||||||
|
['driver', 'drivers'],
|
||||||
|
['dwarf', ['dwarfs', 'dwarves']],
|
||||||
|
['echo', 'echoes'],
|
||||||
|
['elf', ['elfs', 'elves']],
|
||||||
|
['emphasis', 'emphases'],
|
||||||
|
['fax', ['facies', 'faxes']],
|
||||||
|
['feedback', 'feedback'],
|
||||||
|
['focus', 'focuses'],
|
||||||
|
['foot', 'feet'],
|
||||||
|
['formula', 'formulas'], //formulae
|
||||||
|
['fungus', 'fungi'],
|
||||||
|
['garage', 'garages'],
|
||||||
|
['goose', 'geese'],
|
||||||
|
['half', ['halfs', 'halves']],
|
||||||
|
['hat', 'hats'],
|
||||||
|
['hero', 'heroes'],
|
||||||
|
['hippopotamus', 'hippopotami'], //hippopotamuses
|
||||||
|
['hoax', 'hoaxes'],
|
||||||
|
['hoof', ['hoofs', 'hooves']],
|
||||||
|
['house', 'houses'],
|
||||||
|
['index', ['indicies', 'indexes']],
|
||||||
|
['ion', 'ions'],
|
||||||
|
['iris', 'irises'],
|
||||||
|
['kiss', 'kisses'],
|
||||||
|
['knife', 'knives'],
|
||||||
|
['lamp', 'lamps'],
|
||||||
|
['leaf', ['leafs', 'leaves']],
|
||||||
|
['lesson', 'lessons'],
|
||||||
|
['life', 'lives'],
|
||||||
|
['louse', 'lice'],
|
||||||
|
['man', 'men'],
|
||||||
|
['matrix', ['matricies', 'matrixes']],
|
||||||
|
['mouse', 'mice'],
|
||||||
|
['move', 'moves'],
|
||||||
|
['movie', 'movies'],
|
||||||
|
['nebula', 'nebulae'],
|
||||||
|
['neurosis', 'neuroses'],
|
||||||
|
['news', 'news'],
|
||||||
|
['oasis', 'oases'],
|
||||||
|
['objective', 'objectives'],
|
||||||
|
['ox', 'oxen'],
|
||||||
|
['party', 'parties'],
|
||||||
|
['person', ['persons', 'people']],
|
||||||
|
['phenomenon', 'phenomena'],
|
||||||
|
['photo', 'photos'],
|
||||||
|
['piano', 'pianos'],
|
||||||
|
['plateau', ['plateaus', 'plateaux']],
|
||||||
|
['poison', 'poisons'],
|
||||||
|
['poppy', 'poppies'],
|
||||||
|
['price', 'prices'],
|
||||||
|
['quiz', 'quizzes'],
|
||||||
|
['radius', 'radii'],
|
||||||
|
['roof', ['roofs', 'rooves']],
|
||||||
|
['rose', 'roses'],
|
||||||
|
['sandwich', 'sandwiches'],
|
||||||
|
['scarf', ['scarfs', 'scarves']],
|
||||||
|
['schema', 'schemas'], //schemata
|
||||||
|
['season', 'seasons'],
|
||||||
|
['selfie', 'selfies'],
|
||||||
|
['series', 'series'],
|
||||||
|
['service', 'services'],
|
||||||
|
['sheriff', 'sheriffs'],
|
||||||
|
['shoe', 'shoes'],
|
||||||
|
['species', 'species'],
|
||||||
|
['spy', 'spies'],
|
||||||
|
['staff', 'staves'],
|
||||||
|
['story', 'stories'],
|
||||||
|
['stratum', 'strata'],
|
||||||
|
['suitcase', 'suitcases'],
|
||||||
|
['syllabus', 'syllabi'],
|
||||||
|
['tag', 'tags'],
|
||||||
|
['thief', ['thiefs', 'thieves']],
|
||||||
|
['tooth', 'teeth'],
|
||||||
|
['treason', 'treasons'],
|
||||||
|
['tree', 'trees'],
|
||||||
|
['waltz', 'waltzes'],
|
||||||
|
['wife', 'wives'],
|
||||||
|
|
||||||
|
// test casing: if the first letter was uppercase, it should remain so
|
||||||
|
['Man', 'Men'],
|
||||||
|
['GrandChild', 'GrandChildren'],
|
||||||
|
['SubTree', 'SubTrees'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider singularizeProvider
|
||||||
|
*/
|
||||||
|
public function testSingularize(string $plural, $singular)
|
||||||
|
{
|
||||||
|
$this->assertSame(\is_array($singular) ? $singular : [$singular], (new EnglishInflector())->singularize($plural));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider pluralizeProvider
|
||||||
|
*/
|
||||||
|
public function testPluralize(string $singular, $plural)
|
||||||
|
{
|
||||||
|
$this->assertSame(\is_array($plural) ? $plural : [$plural], (new EnglishInflector())->pluralize($singular));
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user