Skip to content

Commit 71e5e5c

Browse files
zds-slimingxinleo
andauthored
Optimized the implementation of Pluralizer. (#6700)
Co-authored-by: 李铭昕 <715557344@qq.com>
1 parent 4206bfa commit 71e5e5c

File tree

3 files changed

+175
-71
lines changed

3 files changed

+175
-71
lines changed

src/Pluralizer.php

Lines changed: 28 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212

1313
namespace Hyperf\Stringable;
1414

15-
use Doctrine\Inflector\CachedWordInflector;
15+
use Countable;
1616
use Doctrine\Inflector\Inflector;
17-
use Doctrine\Inflector\Rules\English;
18-
use Doctrine\Inflector\RulesetInflector;
17+
use Doctrine\Inflector\InflectorFactory;
18+
use Doctrine\Inflector\Language;
1919

2020
class Pluralizer
2121
{
@@ -24,61 +24,27 @@ class Pluralizer
2424
*/
2525
public static array $uncountable
2626
= [
27-
'audio',
28-
'bison',
29-
'cattle',
30-
'chassis',
31-
'compensation',
32-
'coreopsis',
33-
'data',
34-
'deer',
35-
'education',
36-
'emoji',
37-
'equipment',
38-
'evidence',
39-
'feedback',
40-
'firmware',
41-
'fish',
42-
'furniture',
43-
'gold',
44-
'hardware',
45-
'information',
46-
'jedi',
47-
'kin',
48-
'knowledge',
49-
'love',
50-
'metadata',
51-
'money',
52-
'moose',
53-
'news',
54-
'nutrition',
55-
'offspring',
56-
'plankton',
57-
'pokemon',
58-
'police',
59-
'rain',
60-
'rice',
61-
'series',
62-
'sheep',
63-
'software',
64-
'species',
65-
'swine',
66-
'traffic',
67-
'wheat',
27+
'recommended',
28+
'related',
6829
];
6930

31+
/**
32+
* The language that should be used by the inflector.
33+
*/
34+
protected static string $language = Language::ENGLISH;
35+
7036
protected static ?Inflector $inflector = null;
7137

7238
/**
7339
* Get the plural form of an English word.
74-
*
75-
* @param string $value
76-
* @param int $count
77-
* @return string
7840
*/
79-
public static function plural($value, $count = 2)
41+
public static function plural(string $value, array|Countable|int $count = 2): string
8042
{
81-
if ((int) abs($count) === 1 || static::uncountable($value)) {
43+
if (is_countable($count)) {
44+
$count = count($count);
45+
}
46+
47+
if ((int) abs($count) === 1 || static::uncountable($value) || preg_match('/^(.*)[A-Za-z0-9\x{0080}-\x{FFFF}]$/u', $value) == 0) {
8248
return $value;
8349
}
8450

@@ -89,11 +55,8 @@ public static function plural($value, $count = 2)
8955

9056
/**
9157
* Get the singular form of an English word.
92-
*
93-
* @param string $value
94-
* @return string
9558
*/
96-
public static function singular($value)
59+
public static function singular(string $value): string
9760
{
9861
$singular = static::getInflector()->singularize($value);
9962

@@ -111,19 +74,19 @@ public static function setInflector(?Inflector $inflector): void
11174
public static function getInflector(): Inflector
11275
{
11376
if (is_null(static::$inflector)) {
114-
static::$inflector = new Inflector(
115-
new CachedWordInflector(new RulesetInflector(
116-
English\Rules::getSingularRuleset()
117-
)),
118-
new CachedWordInflector(new RulesetInflector(
119-
English\Rules::getPluralRuleset()
120-
))
121-
);
77+
static::$inflector = InflectorFactory::createForLanguage(static::$language)->build();
12278
}
12379

12480
return static::$inflector;
12581
}
12682

83+
public static function useLanguage(string $language): void
84+
{
85+
static::$language = $language;
86+
87+
static::$inflector = null;
88+
}
89+
12790
/**
12891
* Determine if the given value is uncountable.
12992
*
@@ -137,18 +100,14 @@ protected static function uncountable($value)
137100

138101
/**
139102
* Attempt to match the case on two strings.
140-
*
141-
* @param string $value
142-
* @param string $comparison
143-
* @return string
144103
*/
145-
protected static function matchCase($value, $comparison)
104+
protected static function matchCase(string $value, string $comparison): string
146105
{
147106
$functions = ['mb_strtolower', 'mb_strtoupper', 'ucfirst', 'ucwords'];
148107

149108
foreach ($functions as $function) {
150-
if (call_user_func($function, $comparison) === $comparison) {
151-
return call_user_func($function, $value);
109+
if ($function($comparison) === $comparison) {
110+
return $function($value);
152111
}
153112
}
154113

src/Str.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
namespace Hyperf\Stringable;
1414

1515
use Closure;
16+
use Countable;
1617
use DateTimeInterface;
1718
use Hyperf\Collection\Arr;
1819
use Hyperf\Collection\Collection;
@@ -546,8 +547,9 @@ public static function parseCallback(string $callback, $default = null): array
546547

547548
/**
548549
* Pluralize the last word of an English, studly caps case string.
550+
* @param mixed $count
549551
*/
550-
public static function pluralStudly(string $value, int $count = 2): string
552+
public static function pluralStudly(string $value, $count = 2): string
551553
{
552554
$parts = preg_split('/(.)(?=[A-Z])/u', $value, -1, PREG_SPLIT_DELIM_CAPTURE);
553555

@@ -559,7 +561,7 @@ public static function pluralStudly(string $value, int $count = 2): string
559561
/**
560562
* Get the plural form of an English word.
561563
*/
562-
public static function plural(string $value, int $count = 2): string
564+
public static function plural(string $value, array|Countable|int $count = 2): string
563565
{
564566
return Pluralizer::plural($value, $count);
565567
}

tests/PluralizerTest.php

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* This file is part of Hyperf.
6+
*
7+
* @link https://www.hyperf.io
8+
* @document https://hyperf.wiki
9+
* @contact group@hyperf.io
10+
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
11+
*/
12+
13+
namespace HyperfTest\Stringable;
14+
15+
use Hyperf\Stringable\Str;
16+
use PHPUnit\Framework\TestCase;
17+
18+
use function Hyperf\Collection\collect;
19+
20+
/**
21+
* @internal
22+
* @coversNothing
23+
*/
24+
class PluralizerTest extends TestCase
25+
{
26+
public function testBasicSingular(): void
27+
{
28+
$this->assertSame('child', Str::singular('children'));
29+
}
30+
31+
public function testBasicPlural(): void
32+
{
33+
$this->assertSame('children', Str::plural('child'));
34+
$this->assertSame('cod', Str::plural('cod'));
35+
$this->assertSame('The words', Str::plural('The word'));
36+
$this->assertSame('Bouquetés', Str::plural('Bouqueté'));
37+
}
38+
39+
public function testCaseSensitiveSingularUsage(): void
40+
{
41+
$this->assertSame('Child', Str::singular('Children'));
42+
$this->assertSame('CHILD', Str::singular('CHILDREN'));
43+
$this->assertSame('Test', Str::singular('Tests'));
44+
}
45+
46+
public function testCaseSensitiveSingularPlural(): void
47+
{
48+
$this->assertSame('Children', Str::plural('Child'));
49+
$this->assertSame('CHILDREN', Str::plural('CHILD'));
50+
$this->assertSame('Tests', Str::plural('Test'));
51+
$this->assertSame('children', Str::plural('cHiLd'));
52+
}
53+
54+
public function testIfEndOfWordPlural(): void
55+
{
56+
$this->assertSame('VortexFields', Str::plural('VortexField'));
57+
$this->assertSame('MatrixFields', Str::plural('MatrixField'));
58+
$this->assertSame('IndexFields', Str::plural('IndexField'));
59+
$this->assertSame('VertexFields', Str::plural('VertexField'));
60+
61+
// This is expected behavior, use "Str::pluralStudly" instead.
62+
$this->assertSame('RealHumen', Str::plural('RealHuman'));
63+
}
64+
65+
public function testPluralWithNegativeCount(): void
66+
{
67+
$this->assertSame('test', Str::plural('test', 1));
68+
$this->assertSame('tests', Str::plural('test', 2));
69+
$this->assertSame('test', Str::plural('test', -1));
70+
$this->assertSame('tests', Str::plural('test', -2));
71+
}
72+
73+
public function testPluralStudly(): void
74+
{
75+
$this->assertPluralStudly('RealHumans', 'RealHuman');
76+
$this->assertPluralStudly('Models', 'Model');
77+
$this->assertPluralStudly('VortexFields', 'VortexField');
78+
$this->assertPluralStudly('MultipleWordsInOneStrings', 'MultipleWordsInOneString');
79+
}
80+
81+
public function testPluralStudlyWithCount(): void
82+
{
83+
$this->assertPluralStudly('RealHuman', 'RealHuman', 1);
84+
$this->assertPluralStudly('RealHumans', 'RealHuman', 2);
85+
$this->assertPluralStudly('RealHuman', 'RealHuman', -1);
86+
$this->assertPluralStudly('RealHumans', 'RealHuman', -2);
87+
}
88+
89+
public function testPluralNotAppliedForStringEndingWithNonAlphanumericCharacter(): void
90+
{
91+
$this->assertSame('Alien.', Str::plural('Alien.'));
92+
$this->assertSame('Alien!', Str::plural('Alien!'));
93+
$this->assertSame('Alien ', Str::plural('Alien '));
94+
$this->assertSame('50%', Str::plural('50%'));
95+
}
96+
97+
public function testPluralAppliedForStringEndingWithNumericCharacter(): void
98+
{
99+
$this->assertSame('User1s', Str::plural('User1'));
100+
$this->assertSame('User2s', Str::plural('User2'));
101+
$this->assertSame('User3s', Str::plural('User3'));
102+
}
103+
104+
public function testPluralSupportsArrays(): void
105+
{
106+
$this->assertSame('users', Str::plural('user', []));
107+
$this->assertSame('user', Str::plural('user', ['one']));
108+
$this->assertSame('users', Str::plural('user', ['one', 'two']));
109+
}
110+
111+
public function testPluralSupportsCollections()
112+
{
113+
$this->assertSame('users', Str::plural('user', collect()));
114+
$this->assertSame('user', Str::plural('user', collect(['one'])));
115+
$this->assertSame('users', Str::plural('user', collect(['one', 'two'])));
116+
}
117+
118+
public function testPluralStudlySupportsArrays()
119+
{
120+
$this->assertPluralStudly('SomeUsers', 'SomeUser', []);
121+
$this->assertPluralStudly('SomeUser', 'SomeUser', ['one']);
122+
$this->assertPluralStudly('SomeUsers', 'SomeUser', ['one', 'two']);
123+
}
124+
125+
public function testPluralStudlySupportsCollections(): void
126+
{
127+
$this->assertPluralStudly('SomeUsers', 'SomeUser', collect());
128+
$this->assertPluralStudly('SomeUser', 'SomeUser', collect(['one']));
129+
$this->assertPluralStudly('SomeUsers', 'SomeUser', collect(['one', 'two']));
130+
}
131+
132+
public function testPluralizerAboutData()
133+
{
134+
$this->assertSame('xxx_datum', Str::singular('xxx_data'));
135+
$this->assertSame('data', Str::plural('datum'));
136+
$this->assertSame('data', Str::singular('data'));
137+
}
138+
139+
private function assertPluralStudly($expected, $value, $count = 2): void
140+
{
141+
$this->assertSame($expected, Str::pluralStudly($value, $count));
142+
}
143+
}

0 commit comments

Comments
 (0)