Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 198 additions & 0 deletions src/Intl/Icu/IntlListFormatter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Polyfill\Intl\Icu;

/**
* A polyfill implementation of the IntlListFormatter class provided by the intl extension.
*
* @author Ayesh Karunaratne <[email protected]>
*
* @internal
*/
class IntlListFormatter
{
public const TYPE_AND = 0;
public const TYPE_OR = 1;
public const TYPE_UNITS = 2;

public const WIDTH_WIDE = 0;
public const WIDTH_SHORT = 1;
public const WIDTH_NARROW = 2;

/**
* @var int
*/
private $type;
/**
* @var int
*/
private $width;

private const EN_LIST_PATTERNS = [
'listPattern-type-standard' => [
'start' => '{0}, {1}',
'middle' => '{0}, {1}',
'end' => '{0}, and {1}',
2 => '{0} and {1}',
],
'listPattern-type-or' => [
'start' => '{0}, {1}',
'middle' => '{0}, {1}',
'end' => '{0}, or {1}',
2 => '{0} or {1}',
],
'listPattern-type-or-narrow' => [
'start' => '{0}, {1}',
'middle' => '{0}, {1}',
'end' => '{0}, or {1}',
2 => '{0} or {1}',
],
'listPattern-type-or-short' => [
'start' => '{0}, {1}',
'middle' => '{0}, {1}',
'end' => '{0}, or {1}',
2 => '{0} or {1}',
],
'listPattern-type-standard-narrow' => [
'start' => '{0}, {1}',
'middle' => '{0}, {1}',
'end' => '{0}, {1}',
2 => '{0}, {1}',
],
'listPattern-type-standard-short' => [
'start' => '{0}, {1}',
'middle' => '{0}, {1}',
'end' => '{0}, & {1}',
2 => '{0} & {1}',
],
'listPattern-type-unit' => [
'start' => '{0}, {1}',
'middle' => '{0}, {1}',
'end' => '{0}, {1}',
2 => '{0}, {1}',
],
'listPattern-type-unit-narrow' => [
'start' => '{0} {1}',
'middle' => '{0} {1}',
'end' => '{0} {1}',
2 => '{0} {1}',
],
'listPattern-type-unit-short' => [
'start' => '{0}, {1}',
'middle' => '{0}, {1}',
'end' => '{0}, {1}',
2 => '{0}, {1}',
],
];

public function __construct(
string $locale,
int $type = self::TYPE_AND,
int $width = self::WIDTH_WIDE
) {
$exceptionClass = PHP_VERSION_ID >= 80000 ? \ValueError::class : \InvalidArgumentException::class;
if ($locale !== 'en' && strpos($locale, 'en') !== 0) {
throw new $exceptionClass('Invalid locale, only "en" and "en-*" locales are supported');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some mixed feelings about this. Since it's using the en-US set, for en-gb it would return a different result than the real class.

}

if ($type !== self::TYPE_AND && $type !== self::TYPE_OR && $type !== self::TYPE_UNITS) {
throw new $exceptionClass('Argument #2 ($type) must be one of IntlListFormatter::TYPE_AND, IntlListFormatter::TYPE_OR, or IntlListFormatter::TYPE_UNITS.');
}

if ($width !== self::WIDTH_WIDE && $width !== self::WIDTH_SHORT && $width !== self::WIDTH_NARROW) {
throw new $exceptionClass('Argument #3 ($width) must be one of IntlListFormatter::WIDTH_WIDE, IntlListFormatter::WIDTH_SHORT, or IntlListFormatter::WIDTH_NARROW.');
}

$this->type = $type;
$this->width = $width;
}

public function format(array $strings): string
{
$itemCount = count($strings);

if ($itemCount === 0) {
return '';
}

$strings = array_values($strings);

if ($itemCount === 1) {
return (string) $strings[0];
}

$pattern = $this->getListPattern();

switch ($this->type) {
case self::TYPE_AND:
$lookupKeyType = 'standard';
break;
case self::TYPE_OR:
$lookupKeyType = 'or';
break;
case self::TYPE_UNITS:
$lookupKeyType = 'unit';
break;
}

switch ($this->width) {
case self::WIDTH_WIDE:
$lookupKeyWidth = '';
break;
case self::WIDTH_SHORT:
$lookupKeyWidth = '-short';
break;
case self::WIDTH_NARROW:
$lookupKeyWidth = '-narrow';
break;
}

$pattern = $pattern['listPattern-type-' . $lookupKeyType . $lookupKeyWidth];

if ($itemCount === 2) {
return strtr($pattern[2], ['{0}' => (string) $strings[0], '{1}' => (string) $strings[1]]);
}

if ($itemCount === 3) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could remove this case. It can be covered by the same code that for 4 items or more (the for loop will simply do no iterations, which is not an error)

$start = strtr($pattern['start'], ['{0}' => (string) $strings[0], '{1}' => (string) $strings[1]]);
return strtr($pattern['end'], ['{0}' => $start, '{1}' => (string) $strings[2]]);
}

$result = strtr($pattern['start'], ['{0}' => (string) $strings[0], '{1}' => (string) $strings[1]]);

for ($i = 2; $i < $itemCount - 1; $i++) {
$result = strtr($pattern["middle"], [
"{0}" => $result,
"{1}" => $strings[$i],
]);
}

return strtr($pattern["end"], [
"{0}" => $result,
"{1}" => $strings[$itemCount - 1],
]);
}

private function getListPattern(): array {
return self::EN_LIST_PATTERNS;
}

public function getErrorCode(): int
{
return 0;
}

public function getErrorMessage(): string
{
return '';
}
}
14 changes: 14 additions & 0 deletions src/Intl/Icu/Resources/stubs/IntlListFormatter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

final class IntlListFormatter extends Symfony\Polyfill\Intl\Icu\IntlListFormatter
{
}
160 changes: 160 additions & 0 deletions tests/Intl/Icu/IntlListFormatterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Polyfill\Tests\Intl\ListFormatter;

use IntlListFormatter;
use PHPUnit\Framework\TestCase;

/**
* @author Ayesh Karunaratne <[email protected]>
*
* @group class-polyfill
*/
class IntlListFormatterTest extends TestCase
{
public function testSupportedLocales()
{
$this->expectNotToPerformAssertions();
new IntlListFormatter('en');
new IntlListFormatter('en-US');
new IntlListFormatter('en_US');
new IntlListFormatter('en-LK');
}

public function testUnsupportedLocales()
{
if (PHP_VERSION_ID >= 80000) {
$this->expectException(\ValueError::class);
}
else {
$this->expectException(\InvalidArgumentException::class);
}

new IntlListFormatter('ja');
}

public function testUnsupportedType()
{
if (PHP_VERSION_ID >= 80000) {
$this->expectException(\ValueError::class);
}
else {
$this->expectException(\InvalidArgumentException::class);
}
$this->expectExceptionMessage('must be one of IntlListFormatter::TYPE_AND, IntlListFormatter::TYPE_OR, or IntlListFormatter::TYPE_UNITS');
new IntlListFormatter('en', 42);
}

public function testUnsupportedWidth()
{
if (PHP_VERSION_ID >= 80000) {
$this->expectException(\ValueError::class);
}
else {
$this->expectException(\InvalidArgumentException::class);
}
$this->expectExceptionMessage('must be one of IntlListFormatter::WIDTH_WIDE, IntlListFormatter::WIDTH_SHORT, or IntlListFormatter::WIDTH_NARROW');
new IntlListFormatter('en', IntlListFormatter::TYPE_AND, 42);
}

/**
* @dataProvider formattingLists
*/
public function testFormatting(int $type, int $wide, array $strings, string $expected)
{
$formatter = new IntlListFormatter('en', $type, $wide);
self::assertSame($expected, $formatter->format($strings));
}

public function formattingLists() {
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_WIDE, [], ''];
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_WIDE, [1], '1'];
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_WIDE, ['1'], '1'];
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_WIDE, ['apple'], 'apple'];
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_WIDE, ['apple'], 'apple'];
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_WIDE, ['apple', 'banana', 'strawberry'], 'apple, banana, and strawberry'];
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_WIDE, ['apple', 'banana', 'strawberry', 'orange'], 'apple, banana, strawberry, and orange'];
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_WIDE, ['apple', 'banana', 'strawberry', 'orange', 16], 'apple, banana, strawberry, orange, and 16'];

yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_SHORT, [], ''];
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_SHORT, [1], '1'];
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_SHORT, ['1'], '1'];
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_SHORT, ['apple'], 'apple'];
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_SHORT, ['apple'], 'apple'];
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_SHORT, ['apple', 'banana', 'strawberry'], 'apple, banana, & strawberry'];
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_SHORT, ['apple', 'banana', 'strawberry', 'orange'], 'apple, banana, strawberry, & orange'];
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_SHORT, ['apple', 'banana', 'strawberry', 'orange', 16], 'apple, banana, strawberry, orange, & 16'];

yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_NARROW, [], ''];
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_NARROW, [1], '1'];
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_NARROW, ['1'], '1'];
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_NARROW, ['apple'], 'apple'];
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_NARROW, ['apple'], 'apple'];
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_NARROW, ['apple', 'banana', 'strawberry'], 'apple, banana, strawberry'];
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_NARROW, ['apple', 'banana', 'strawberry', 'orange'], 'apple, banana, strawberry, orange'];
yield [IntlListFormatter::TYPE_AND, IntlListFormatter::WIDTH_NARROW, ['apple', 'banana', 'strawberry', 'orange', 16], 'apple, banana, strawberry, orange, 16'];

yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_WIDE, [], ''];
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_WIDE, [1], '1'];
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_WIDE, ['1'], '1'];
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_WIDE, ['apple'], 'apple'];
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_WIDE, ['apple'], 'apple'];
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_WIDE, ['apple', 'banana', 'strawberry'], 'apple, banana, or strawberry'];
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_WIDE, ['apple', 'banana', 'strawberry', 'orange'], 'apple, banana, strawberry, or orange'];
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_WIDE, ['apple', 'banana', 'strawberry', 'orange', 16], 'apple, banana, strawberry, orange, or 16'];

yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_SHORT, [], ''];
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_SHORT, [1], '1'];
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_SHORT, ['1'], '1'];
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_SHORT, ['apple'], 'apple'];
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_SHORT, ['apple'], 'apple'];
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_SHORT, ['apple', 'banana', 'strawberry'], 'apple, banana, or strawberry'];
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_SHORT, ['apple', 'banana', 'strawberry', 'orange'], 'apple, banana, strawberry, or orange'];
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_SHORT, ['apple', 'banana', 'strawberry', 'orange', 16], 'apple, banana, strawberry, orange, or 16'];

yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_NARROW, [], ''];
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_NARROW, [1], '1'];
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_NARROW, ['1'], '1'];
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_NARROW, ['apple'], 'apple'];
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_NARROW, ['apple'], 'apple'];
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_NARROW, ['apple', 'banana', 'strawberry', 'orange'], 'apple, banana, strawberry, or orange'];
yield [IntlListFormatter::TYPE_OR, IntlListFormatter::WIDTH_NARROW, ['apple', 'banana', 'strawberry', 'orange', 16], 'apple, banana, strawberry, orange, or 16'];

yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_WIDE, [], ''];
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_WIDE, [1], '1'];
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_WIDE, ['1'], '1'];
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_WIDE, ['apple'], 'apple'];
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_WIDE, ['apple'], 'apple'];
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_WIDE, ['apple', 'banana', 'strawberry'], 'apple, banana, strawberry'];
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_WIDE, ['apple', 'banana', 'strawberry', 'orange'], 'apple, banana, strawberry, orange'];
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_WIDE, ['apple', 'banana', 'strawberry', 'orange', 16], 'apple, banana, strawberry, orange, 16'];


yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_SHORT, [], ''];
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_SHORT, [1], '1'];
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_SHORT, ['1'], '1'];
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_SHORT, ['apple'], 'apple'];
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_SHORT, ['apple'], 'apple'];
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_SHORT, ['apple', 'banana', 'strawberry'], 'apple, banana, strawberry'];
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_SHORT, ['apple', 'banana', 'strawberry', 'orange'], 'apple, banana, strawberry, orange'];
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_SHORT, ['apple', 'banana', 'strawberry', 'orange', 16], 'apple, banana, strawberry, orange, 16'];

yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_NARROW, [], ''];
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_NARROW, [1], '1'];
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_NARROW, ['1'], '1'];
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_NARROW, ['apple'], 'apple'];
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_NARROW, ['apple'], 'apple'];
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_NARROW, ['apple', 'banana', 'strawberry'], 'apple banana strawberry'];
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_NARROW, ['apple', 'banana', 'strawberry', 'orange'], 'apple banana strawberry orange'];
yield [IntlListFormatter::TYPE_UNITS, IntlListFormatter::WIDTH_NARROW, ['apple', 'banana', 'strawberry', 'orange', 16], 'apple banana strawberry orange 16'];
}
}
Loading