Skip to content

Commit 0156b53

Browse files
committed
[Intl] Add PHP 8.5 IntlListFormatter to ICU polyfill
Adds a new polyfill to `symfony/polyfill-intl-icu` that provides the functionality of the new `IntlListFormatter` to PHP 7.2 and later. - [ICU listPatterns](https://github.com/unicode-org/cldr-json/blob/main/cldr-json/cldr-misc-full/main/en/listPatterns.json) - [php-src commit](php/php-src@3f7545245) - [PHP.Watch: IntlListFormatter](https://php.watch/versions/8.5/IntlListFormatter) Closes GH-530.
1 parent 41ace00 commit 0156b53

File tree

3 files changed

+372
-0
lines changed

3 files changed

+372
-0
lines changed

src/Intl/Icu/IntlListFormatter.php

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Polyfill\Intl\Icu;
13+
14+
/**
15+
* A polyfill implementation of the IntlListFormatter class provided by the intl extension.
16+
*
17+
* @author Ayesh Karunaratne <[email protected]>
18+
*
19+
* @internal
20+
*/
21+
class IntlListFormatter
22+
{
23+
public const TYPE_AND = 0;
24+
public const TYPE_OR = 1;
25+
public const TYPE_UNITS = 2;
26+
27+
public const WIDTH_WIDE = 0;
28+
public const WIDTH_SHORT = 1;
29+
public const WIDTH_NARROW = 2;
30+
31+
/**
32+
* @var int
33+
*/
34+
private $type;
35+
/**
36+
* @var int
37+
*/
38+
private $width;
39+
40+
private const EN_LIST_PATTERNS = [
41+
'listPattern-type-standard' => [
42+
'start' => '{0}, {1}',
43+
'middle' => '{0}, {1}',
44+
'end' => '{0}, and {1}',
45+
2 => '{0} and {1}',
46+
],
47+
'listPattern-type-or' => [
48+
'start' => '{0}, {1}',
49+
'middle' => '{0}, {1}',
50+
'end' => '{0}, or {1}',
51+
2 => '{0} or {1}',
52+
],
53+
'listPattern-type-or-narrow' => [
54+
'start' => '{0}, {1}',
55+
'middle' => '{0}, {1}',
56+
'end' => '{0}, or {1}',
57+
2 => '{0} or {1}',
58+
],
59+
'listPattern-type-or-short' => [
60+
'start' => '{0}, {1}',
61+
'middle' => '{0}, {1}',
62+
'end' => '{0}, or {1}',
63+
2 => '{0} or {1}',
64+
],
65+
'listPattern-type-standard-narrow' => [
66+
'start' => '{0}, {1}',
67+
'middle' => '{0}, {1}',
68+
'end' => '{0}, {1}',
69+
2 => '{0}, {1}',
70+
],
71+
'listPattern-type-standard-short' => [
72+
'start' => '{0}, {1}',
73+
'middle' => '{0}, {1}',
74+
'end' => '{0}, & {1}',
75+
2 => '{0} & {1}',
76+
],
77+
'listPattern-type-unit' => [
78+
'start' => '{0}, {1}',
79+
'middle' => '{0}, {1}',
80+
'end' => '{0}, {1}',
81+
2 => '{0}, {1}',
82+
],
83+
'listPattern-type-unit-narrow' => [
84+
'start' => '{0} {1}',
85+
'middle' => '{0} {1}',
86+
'end' => '{0} {1}',
87+
2 => '{0} {1}',
88+
],
89+
'listPattern-type-unit-short' => [
90+
'start' => '{0}, {1}',
91+
'middle' => '{0}, {1}',
92+
'end' => '{0}, {1}',
93+
2 => '{0}, {1}',
94+
],
95+
];
96+
97+
public function __construct(
98+
string $locale,
99+
int $type = self::TYPE_AND,
100+
int $width = self::WIDTH_WIDE
101+
) {
102+
$exceptionClass = PHP_VERSION_ID >= 80000 ? \ValueError::class : \InvalidArgumentException::class;
103+
if ($locale !== 'en' && strpos($locale, 'en') !== 0) {
104+
throw new $exceptionClass('Invalid locale, only "en" and "en-*" locales are supported');
105+
}
106+
107+
if ($type !== self::TYPE_AND && $type !== self::TYPE_OR && $type !== self::TYPE_UNITS) {
108+
throw new $exceptionClass('Argument #2 ($type) must be one of IntlListFormatter::TYPE_AND, IntlListFormatter::TYPE_OR, or IntlListFormatter::TYPE_UNITS.');
109+
}
110+
111+
if ($width !== self::WIDTH_WIDE && $width !== self::WIDTH_SHORT && $width !== self::WIDTH_NARROW) {
112+
throw new $exceptionClass('Argument #3 ($width) must be one of IntlListFormatter::WIDTH_WIDE, IntlListFormatter::WIDTH_SHORT, or IntlListFormatter::WIDTH_NARROW.');
113+
}
114+
115+
$this->type = $type;
116+
$this->width = $width;
117+
}
118+
119+
public function format(array $strings): string
120+
{
121+
$itemCount = count($strings);
122+
123+
if ($itemCount === 0) {
124+
return '';
125+
}
126+
127+
$strings = array_values($strings);
128+
129+
if ($itemCount === 1) {
130+
return (string) $strings[0];
131+
}
132+
133+
$pattern = $this->getListPattern();
134+
135+
switch ($this->type) {
136+
case self::TYPE_AND:
137+
$lookupKeyType = 'standard';
138+
break;
139+
case self::TYPE_OR:
140+
$lookupKeyType = 'or';
141+
break;
142+
case self::TYPE_UNITS:
143+
$lookupKeyType = 'unit';
144+
break;
145+
}
146+
147+
switch ($this->width) {
148+
case self::WIDTH_WIDE:
149+
$lookupKeyWidth = '';
150+
break;
151+
case self::WIDTH_SHORT:
152+
$lookupKeyWidth = '-short';
153+
break;
154+
case self::WIDTH_NARROW:
155+
$lookupKeyWidth = '-narrow';
156+
break;
157+
}
158+
159+
$pattern = $pattern['listPattern-type-' . $lookupKeyType . $lookupKeyWidth];
160+
161+
if ($itemCount === 2) {
162+
return strtr($pattern[2], ['{0}' => (string) $strings[0], '{1}' => (string) $strings[1]]);
163+
}
164+
165+
if ($itemCount === 3) {
166+
$start = strtr($pattern['start'], ['{0}' => (string) $strings[0], '{1}' => (string) $strings[1]]);
167+
return strtr($pattern['end'], ['{0}' => $start, '{1}' => (string) $strings[2]]);
168+
}
169+
170+
$result = strtr($pattern['start'], ['{0}' => (string) $strings[0], '{1}' => (string) $strings[1]]);
171+
172+
for ($i = 2; $i < $itemCount - 1; $i++) {
173+
$result = strtr($pattern["middle"], [
174+
"{0}" => $result,
175+
"{1}" => $strings[$i],
176+
]);
177+
}
178+
179+
return strtr($pattern["end"], [
180+
"{0}" => $result,
181+
"{1}" => $strings[$itemCount - 1],
182+
]);
183+
}
184+
185+
private function getListPattern(): array {
186+
return self::EN_LIST_PATTERNS;
187+
}
188+
189+
public function getErrorCode(): int
190+
{
191+
return 0;
192+
}
193+
194+
public function getErrorMessage(): string
195+
{
196+
return '';
197+
}
198+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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+
final class IntlListFormatter extends Symfony\Polyfill\Intl\Icu\IntlListFormatter
13+
{
14+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Polyfill\Tests\Intl\ListFormatter;
13+
14+
use IntlListFormatter;
15+
use PHPUnit\Framework\TestCase;
16+
17+
/**
18+
* @author Ayesh Karunaratne <[email protected]>
19+
*
20+
* @group class-polyfill
21+
*/
22+
class IntlListFormatterTest extends TestCase
23+
{
24+
public function testSupportedLocales()
25+
{
26+
$this->expectNotToPerformAssertions();
27+
new IntlListFormatter('en');
28+
new IntlListFormatter('en-US');
29+
new IntlListFormatter('en_US');
30+
new IntlListFormatter('en-LK');
31+
}
32+
33+
public function testUnsupportedLocales()
34+
{
35+
if (PHP_VERSION_ID >= 80000) {
36+
$this->expectException(\ValueError::class);
37+
}
38+
else {
39+
$this->expectException(\InvalidArgumentException::class);
40+
}
41+
42+
new IntlListFormatter('ja');
43+
}
44+
45+
public function testUnsupportedType()
46+
{
47+
if (PHP_VERSION_ID >= 80000) {
48+
$this->expectException(\ValueError::class);
49+
}
50+
else {
51+
$this->expectException(\InvalidArgumentException::class);
52+
}
53+
$this->expectExceptionMessage('must be one of IntlListFormatter::TYPE_AND, IntlListFormatter::TYPE_OR, or IntlListFormatter::TYPE_UNITS');
54+
new IntlListFormatter('en', 42);
55+
}
56+
57+
public function testUnsupportedWidth()
58+
{
59+
if (PHP_VERSION_ID >= 80000) {
60+
$this->expectException(\ValueError::class);
61+
}
62+
else {
63+
$this->expectException(\InvalidArgumentException::class);
64+
}
65+
$this->expectExceptionMessage('must be one of IntlListFormatter::WIDTH_WIDE, IntlListFormatter::WIDTH_SHORT, or IntlListFormatter::WIDTH_NARROW');
66+
new IntlListFormatter('en', IntlListFormatter::TYPE_AND, 42);
67+
}
68+
69+
/**
70+
* @dataProvider formattingLists
71+
*/
72+
public function testFormatting(int $type, int $wide, array $strings, string $expected)
73+
{
74+
$formatter = new IntlListFormatter('en', $type, $wide);
75+
self::assertSame($expected, $formatter->format($strings));
76+
}
77+
78+
public function formattingLists() {
79+
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_WIDE, [], ''];
80+
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_WIDE, [1], '1'];
81+
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_WIDE, ['1'], '1'];
82+
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_WIDE, ['apple'], 'apple'];
83+
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_WIDE, ['apple'], 'apple'];
84+
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_WIDE, ['apple', 'banana', 'strawberry'], 'apple, banana, and strawberry'];
85+
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_WIDE, ['apple', 'banana', 'strawberry', 'orange'], 'apple, banana, strawberry, and orange'];
86+
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_WIDE, ['apple', 'banana', 'strawberry', 'orange', 16], 'apple, banana, strawberry, orange, and 16'];
87+
88+
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_SHORT, [], ''];
89+
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_SHORT, [1], '1'];
90+
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_SHORT, ['1'], '1'];
91+
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_SHORT, ['apple'], 'apple'];
92+
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_SHORT, ['apple'], 'apple'];
93+
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_SHORT, ['apple', 'banana', 'strawberry'], 'apple, banana, & strawberry'];
94+
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_SHORT, ['apple', 'banana', 'strawberry', 'orange'], 'apple, banana, strawberry, & orange'];
95+
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_SHORT, ['apple', 'banana', 'strawberry', 'orange', 16], 'apple, banana, strawberry, orange, & 16'];
96+
97+
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_NARROW, [], ''];
98+
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_NARROW, [1], '1'];
99+
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_NARROW, ['1'], '1'];
100+
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_NARROW, ['apple'], 'apple'];
101+
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_NARROW, ['apple'], 'apple'];
102+
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_NARROW, ['apple', 'banana', 'strawberry'], 'apple, banana, strawberry'];
103+
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_NARROW, ['apple', 'banana', 'strawberry', 'orange'], 'apple, banana, strawberry, orange'];
104+
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_NARROW, ['apple', 'banana', 'strawberry', 'orange', 16], 'apple, banana, strawberry, orange, 16'];
105+
106+
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_WIDE, [], ''];
107+
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_WIDE, [1], '1'];
108+
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_WIDE, ['1'], '1'];
109+
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_WIDE, ['apple'], 'apple'];
110+
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_WIDE, ['apple'], 'apple'];
111+
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_WIDE, ['apple', 'banana', 'strawberry'], 'apple, banana, or strawberry'];
112+
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_WIDE, ['apple', 'banana', 'strawberry', 'orange'], 'apple, banana, strawberry, or orange'];
113+
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_WIDE, ['apple', 'banana', 'strawberry', 'orange', 16], 'apple, banana, strawberry, orange, or 16'];
114+
115+
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_SHORT, [], ''];
116+
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_SHORT, [1], '1'];
117+
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_SHORT, ['1'], '1'];
118+
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_SHORT, ['apple'], 'apple'];
119+
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_SHORT, ['apple'], 'apple'];
120+
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_SHORT, ['apple', 'banana', 'strawberry'], 'apple, banana, or strawberry'];
121+
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_SHORT, ['apple', 'banana', 'strawberry', 'orange'], 'apple, banana, strawberry, or orange'];
122+
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_SHORT, ['apple', 'banana', 'strawberry', 'orange', 16], 'apple, banana, strawberry, orange, or 16'];
123+
124+
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_NARROW, [], ''];
125+
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_NARROW, [1], '1'];
126+
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_NARROW, ['1'], '1'];
127+
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_NARROW, ['apple'], 'apple'];
128+
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_NARROW, ['apple'], 'apple'];
129+
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_NARROW, ['apple', 'banana', 'strawberry', 'orange'], 'apple, banana, strawberry, or orange'];
130+
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_NARROW, ['apple', 'banana', 'strawberry', 'orange', 16], 'apple, banana, strawberry, orange, or 16'];
131+
132+
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_WIDE, [], ''];
133+
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_WIDE, [1], '1'];
134+
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_WIDE, ['1'], '1'];
135+
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_WIDE, ['apple'], 'apple'];
136+
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_WIDE, ['apple'], 'apple'];
137+
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_WIDE, ['apple', 'banana', 'strawberry'], 'apple, banana, strawberry'];
138+
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_WIDE, ['apple', 'banana', 'strawberry', 'orange'], 'apple, banana, strawberry, orange'];
139+
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_WIDE, ['apple', 'banana', 'strawberry', 'orange', 16], 'apple, banana, strawberry, orange, 16'];
140+
141+
142+
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_SHORT, [], ''];
143+
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_SHORT, [1], '1'];
144+
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_SHORT, ['1'], '1'];
145+
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_SHORT, ['apple'], 'apple'];
146+
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_SHORT, ['apple'], 'apple'];
147+
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_SHORT, ['apple', 'banana', 'strawberry'], 'apple, banana, strawberry'];
148+
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_SHORT, ['apple', 'banana', 'strawberry', 'orange'], 'apple, banana, strawberry, orange'];
149+
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_SHORT, ['apple', 'banana', 'strawberry', 'orange', 16], 'apple, banana, strawberry, orange, 16'];
150+
151+
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_NARROW, [], ''];
152+
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_NARROW, [1], '1'];
153+
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_NARROW, ['1'], '1'];
154+
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_NARROW, ['apple'], 'apple'];
155+
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_NARROW, ['apple'], 'apple'];
156+
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_NARROW, ['apple', 'banana', 'strawberry'], 'apple banana strawberry'];
157+
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_NARROW, ['apple', 'banana', 'strawberry', 'orange'], 'apple banana strawberry orange'];
158+
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_NARROW, ['apple', 'banana', 'strawberry', 'orange', 16], 'apple banana strawberry orange 16'];
159+
}
160+
}

0 commit comments

Comments
 (0)