From e2c97fe441c50fc8826bbe9428c75f4ba62ceeaf Mon Sep 17 00:00:00 2001 From: Javier Spagnoletti Date: Thu, 4 Jun 2020 00:30:59 -0300 Subject: [PATCH 1/2] Require "twig/intl-extra" --- UPGRADE-2.x.md | 16 +++ composer.json | 2 + docs/reference/number.rst | 4 +- src/Resources/config/intl.xml | 5 + src/Templating/Helper/LocaleHelper.php | 72 +++++++--- src/Templating/Helper/NumberHelper.php | 186 ++++++++++++++++++++++++- src/Twig/Extension/LocaleExtension.php | 2 + tests/Helper/LocaleHelperTest.php | 93 ++++++++++--- tests/Helper/NumberHelperTest.php | 136 +++++++++++------- 9 files changed, 421 insertions(+), 95 deletions(-) diff --git a/UPGRADE-2.x.md b/UPGRADE-2.x.md index c02b94b0..979cb367 100644 --- a/UPGRADE-2.x.md +++ b/UPGRADE-2.x.md @@ -4,6 +4,22 @@ UPGRADE 2.x UPGRADE FROM 2.7 to 2.8 ======================= +### Locale helper + +``Sonata\IntlBundle\Templating\Helper\LocaleHelper`` is marked as final, you must +not inherit this class. + +``Sonata\IntlBundle\Templating\Helper\LocaleHelper::__construct()`` expects ``Twig\Extra\Intl\IntlExtension`` +in argument 3. + +### Number helper + +``Sonata\IntlBundle\Templating\Helper\NumberHelper`` is marked as final, you must +not inherit this class. + +``Sonata\IntlBundle\Templating\Helper\LocaleHelper::__construct()`` expects ``Twig\Extra\Intl\IntlExtension`` +in argument 6. + ### Timezone detector ``Sonata\IntlBundle\Timezone\TimezoneAwareInterface`` was added in order to provide diff --git a/composer.json b/composer.json index 5981ce41..1634dacb 100644 --- a/composer.json +++ b/composer.json @@ -29,6 +29,8 @@ "symfony/http-kernel": "^4.4", "symfony/intl": "^4.4", "symfony/templating": "^4.4", + "twig/extra-bundle": "^3.0", + "twig/intl-extra": "^3.0", "twig/twig": "^2.9" }, "conflict": { diff --git a/docs/reference/number.rst b/docs/reference/number.rst index dcf51bb6..ce84d114 100644 --- a/docs/reference/number.rst +++ b/docs/reference/number.rst @@ -35,7 +35,7 @@ For a list of available values, check the PHP_ documentation. {{ 42|number_format_spellout }} {# => quarante-deux #} {{ 1.999|number_format_percent }} {# => 200 % #} {{ 1|number_format_ordinal }} {# => 1ᵉʳ #} - {{ (-1.1337)|number_format_decimal({'fraction_digits': 2}, {'negative_prefix': 'MINUS'}) }} {# => MINUS1,34 #} + {{ (-1.1337)|number_format_decimal({'fraction_digit': 2}, {'negative_prefix': 'MINUS'}) }} {# => MINUS1,34 #} PHP usage ^^^^^^^^^ @@ -56,7 +56,7 @@ When defining your Admin, you can also provide extra parameters:: { $listMapper ->add('amount', 'decimal', [ - 'attributes' => ['fraction_digits' => 2], + 'attributes' => ['fraction_digit' => 2], 'textAttributes' => ['negative_prefix' => 'MINUS'], ]) ; diff --git a/src/Resources/config/intl.xml b/src/Resources/config/intl.xml index 3c1e0441..171f3d90 100644 --- a/src/Resources/config/intl.xml +++ b/src/Resources/config/intl.xml @@ -26,11 +26,16 @@ %kernel.charset% + %kernel.charset% + + + + diff --git a/src/Templating/Helper/LocaleHelper.php b/src/Templating/Helper/LocaleHelper.php index e86d55c8..012e6607 100644 --- a/src/Templating/Helper/LocaleHelper.php +++ b/src/Templating/Helper/LocaleHelper.php @@ -13,18 +13,36 @@ namespace Sonata\IntlBundle\Templating\Helper; +use Sonata\IntlBundle\Locale\LocaleDetectorInterface; use Symfony\Component\Intl\Countries; -use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\Languages; use Symfony\Component\Intl\Locales; +use Twig\Extra\Intl\IntlExtension; /** * LocaleHelper displays culture information. * + * @final since sonata-project/intl-bundle 2.x + * * @author Thomas Rabaix */ class LocaleHelper extends BaseHelper { + /** + * @var IntlExtension|null + */ + private $intlExtension; + + /** + * @param string $charset The output charset of the helper + */ + public function __construct(string $charset, LocaleDetectorInterface $localeDetector, ?IntlExtension $intlExtension = null) + { + parent::__construct($charset, $localeDetector); + + $this->intlExtension = $intlExtension; + } + /** * @param string $code * @param string|null $locale @@ -33,13 +51,19 @@ class LocaleHelper extends BaseHelper */ public function country($code, $locale = null) { - // NEXT_MAJOR: Remove this when dropping < 4.3 Symfony support - if (!class_exists(Countries::class)) { - $name = Intl::getRegionBundle()->getCountryName($code, $locale ?: $this->localeDetector->getLocale()); - - return $name ? $this->fixCharset($name) : ''; + if ($this->intlExtension) { + return $this->fixCharset($this->intlExtension->getCountryName($code, $locale ?: $this->localeDetector->getLocale())); } + // NEXT_MAJOR: Execute the previous block unconditionally and remove following lines in this method. + + @trigger_error(sprintf( + 'Not passing an instance of "%s" as argument 3 for %s::__construct() is deprecated since sonata-project/intl-bundle 2.x.' + .' and will throw an exception since version 3.x.', + IntlExtension::class, + __CLASS__ + )); + return $this->fixCharset(Countries::getName($code, $locale ?: $this->localeDetector->getLocale())); } @@ -51,18 +75,18 @@ public function country($code, $locale = null) */ public function language($code, $locale = null) { - // NEXT_MAJOR: Remove this when dropping < 4.3 Symfony support - if (!class_exists(Languages::class)) { - $codes = explode('_', $code); + if ($this->intlExtension) { + $this->fixCharset($this->intlExtension->getLanguageName($code, $locale)); + } - $name = Intl::getLanguageBundle()->getLanguageName( - $codes[0], - $codes[1] ?? null, - $locale ?: $this->localeDetector->getLocale() - ); + // NEXT_MAJOR: Execute the previous block unconditionally and remove following lines in this method. - return $name ? $this->fixCharset($name) : ''; - } + @trigger_error(sprintf( + 'Not passing an instance of "%s" as argument 3 for %s::__construct() is deprecated since sonata-project/intl-bundle 2.x.' + .' and will throw an exception since version 3.x.', + IntlExtension::class, + __CLASS__ + )); return $this->fixCharset(Languages::getName($code, $locale ?: $this->localeDetector->getLocale())); } @@ -75,13 +99,19 @@ public function language($code, $locale = null) */ public function locale($code, $locale = null) { - // NEXT_MAJOR: Remove this when dropping < 4.3 Symfony support - if (!class_exists(Locales::class)) { - $name = Intl::getLocaleBundle()->getLocaleName($code, $locale ?: $this->localeDetector->getLocale()); - - return $name ? $this->fixCharset($name) : ''; + if ($this->intlExtension) { + $this->fixCharset($this->intlExtension->getLocaleName($code, $locale)); } + // NEXT_MAJOR: Execute the previous block unconditionally and remove following lines in this method. + + @trigger_error(sprintf( + 'Not passing an instance of "%s" as argument 3 for %s::__construct() is deprecated since sonata-project/intl-bundle 2.x.' + .' and will throw an exception since version 3.x.', + IntlExtension::class, + __CLASS__ + )); + return $this->fixCharset(Locales::getName($code, $locale ?: $this->localeDetector->getLocale())); } diff --git a/src/Templating/Helper/NumberHelper.php b/src/Templating/Helper/NumberHelper.php index de63f019..c3dc683c 100644 --- a/src/Templating/Helper/NumberHelper.php +++ b/src/Templating/Helper/NumberHelper.php @@ -14,10 +14,13 @@ namespace Sonata\IntlBundle\Templating\Helper; use Sonata\IntlBundle\Locale\LocaleDetectorInterface; +use Twig\Extra\Intl\IntlExtension; /** * NumberHelper displays culture information. * + * @final since sonata-project/intl-bundle 2.x + * * @author Thomas Rabaix * @author Stefano Arlandini */ @@ -38,6 +41,11 @@ class NumberHelper extends BaseHelper */ protected $symbols = []; + /** + * @var IntlExtension|null + */ + private $intlExtension; + /** * @param string $charset The output charset of the helper * @param LocaleDetectorInterface $localeDetector A locale detector instance @@ -45,13 +53,14 @@ class NumberHelper extends BaseHelper * @param array $textAttributes The default text attributes to apply to the \NumberFormatter instance * @param array $symbols The default symbols to apply to the \NumberFormatter instance */ - public function __construct($charset, LocaleDetectorInterface $localeDetector, array $attributes = [], array $textAttributes = [], array $symbols = []) + public function __construct($charset, LocaleDetectorInterface $localeDetector, array $attributes = [], array $textAttributes = [], array $symbols = [], ?IntlExtension $intlExtension = null) { parent::__construct($charset, $localeDetector); $this->attributes = $attributes; $this->textAttributes = $textAttributes; $this->symbols = $symbols; + $this->intlExtension = $intlExtension; } /** @@ -67,6 +76,21 @@ public function __construct($charset, LocaleDetectorInterface $localeDetector, a */ public function formatPercent($number, array $attributes = [], array $textAttributes = [], $locale = null) { + if ($this->intlExtension) { + $attributes = self::processLegacyAttributes($attributes); + + return $this->fixCharset($this->intlExtension->formatNumberStyle('percent', $number, $attributes, 'default', $locale ?: $this->localeDetector->getLocale())); + } + + // NEXT_MAJOR: Execute the previous block unconditionally and remove following lines in this method. + + @trigger_error(sprintf( + 'Not passing an instance of "%s" as argument 6 for %s::__construct() is deprecated since sonata-project/intl-bundle 2.x.' + .' and will throw an exception in version 3.x.', + IntlExtension::class, + __CLASS__ + )); + $methodArgs = array_pad(\func_get_args(), 5, null); [$locale, $symbols] = $this->normalizeMethodSignature($methodArgs[3], $methodArgs[4]); @@ -87,6 +111,21 @@ public function formatPercent($number, array $attributes = [], array $textAttrib */ public function formatDuration($number, array $attributes = [], array $textAttributes = [], $locale = null) { + if ($this->intlExtension) { + $attributes = self::processLegacyAttributes($attributes); + + return $this->fixCharset($this->intlExtension->formatNumberStyle('duration', $number, $attributes, 'default', $locale ?: $this->localeDetector->getLocale())); + } + + // NEXT_MAJOR: Execute the previous block unconditionally and remove following lines in this method. + + @trigger_error(sprintf( + 'Not passing an instance of "%s" as argument 6 for %s::__construct() is deprecated since sonata-project/intl-bundle 2.x.' + .' and will throw an exception in version 3.x.', + IntlExtension::class, + __CLASS__ + )); + $methodArgs = array_pad(\func_get_args(), 5, null); [$locale, $symbols] = $this->normalizeMethodSignature($methodArgs[3], $methodArgs[4]); @@ -107,6 +146,21 @@ public function formatDuration($number, array $attributes = [], array $textAttri */ public function formatDecimal($number, array $attributes = [], array $textAttributes = [], $locale = null) { + if ($this->intlExtension) { + $attributes = self::processLegacyAttributes($attributes); + + return $this->fixCharset($this->intlExtension->formatNumberStyle('decimal', $number, $attributes, 'default', $locale ?: $this->localeDetector->getLocale())); + } + + // NEXT_MAJOR: Execute the previous block unconditionally and remove following lines in this method. + + @trigger_error(sprintf( + 'Not passing an instance of "%s" as argument 6 for %s::__construct() is deprecated since sonata-project/intl-bundle 2.x.' + .' and will throw an exception in version 3.x.', + IntlExtension::class, + __CLASS__ + )); + $methodArgs = array_pad(\func_get_args(), 5, null); [$locale, $symbols] = $this->normalizeMethodSignature($methodArgs[3], $methodArgs[4]); @@ -127,6 +181,21 @@ public function formatDecimal($number, array $attributes = [], array $textAttrib */ public function formatSpellout($number, array $attributes = [], array $textAttributes = [], $locale = null) { + if ($this->intlExtension) { + $attributes = self::processLegacyAttributes($attributes); + + return $this->fixCharset($this->intlExtension->formatNumberStyle('spellout', $number, $attributes, 'default', $locale ?: $this->localeDetector->getLocale())); + } + + // NEXT_MAJOR: Execute the previous block unconditionally and remove following lines in this method. + + @trigger_error(sprintf( + 'Not passing an instance of "%s" as argument 6 for %s::__construct() is deprecated since sonata-project/intl-bundle 2.x.' + .' and will throw an exception in version 3.x.', + IntlExtension::class, + __CLASS__ + )); + $methodArgs = array_pad(\func_get_args(), 5, null); [$locale, $symbols] = $this->normalizeMethodSignature($methodArgs[3], $methodArgs[4]); @@ -148,6 +217,21 @@ public function formatSpellout($number, array $attributes = [], array $textAttri */ public function formatCurrency($number, $currency, array $attributes = [], array $textAttributes = [], $locale = null) { + if ($this->intlExtension) { + $attributes = self::processLegacyAttributes($attributes); + + return $this->fixCharset($this->intlExtension->formatCurrency($number, $currency, $attributes, $locale ?: $this->localeDetector->getLocale())); + } + + // NEXT_MAJOR: Execute the previous block unconditionally and remove following lines in this method. + + @trigger_error(sprintf( + 'Not passing an instance of "%s" as argument 6 for %s::__construct() is deprecated since sonata-project/intl-bundle 2.x.' + .' and will throw an exception in version 3.x.', + IntlExtension::class, + __CLASS__ + )); + // convert Doctrine's decimal type (fixed-point number represented as string) to float for backward compatibility if (\is_string($number) && is_numeric($number)) { $number = (float) $number; @@ -175,6 +259,21 @@ public function formatCurrency($number, $currency, array $attributes = [], array */ public function formatScientific($number, array $attributes = [], array $textAttributes = [], $locale = null) { + if ($this->intlExtension) { + $attributes = self::processLegacyAttributes($attributes); + + return $this->fixCharset($this->intlExtension->formatNumberStyle('scientific', $number, $attributes, 'default', $locale ?: $this->localeDetector->getLocale())); + } + + // NEXT_MAJOR: Execute the previous block unconditionally and remove following lines in this method. + + @trigger_error(sprintf( + 'Not passing an instance of "%s" as argument 6 for %s::__construct() is deprecated since sonata-project/intl-bundle 2.x.' + .' and will throw an exception in version 3.x.', + IntlExtension::class, + __CLASS__ + )); + $methodArgs = array_pad(\func_get_args(), 5, null); [$locale, $symbols] = $this->normalizeMethodSignature($methodArgs[3], $methodArgs[4]); @@ -195,6 +294,21 @@ public function formatScientific($number, array $attributes = [], array $textAtt */ public function formatOrdinal($number, array $attributes = [], array $textAttributes = [], $locale = null) { + if ($this->intlExtension) { + $attributes = self::processLegacyAttributes($attributes); + + return $this->fixCharset($this->intlExtension->formatNumberStyle('ordinal', $number, $attributes, 'default', $locale ?: $this->localeDetector->getLocale())); + } + + // NEXT_MAJOR: Execute the previous block unconditionally and remove following lines in this method. + + @trigger_error(sprintf( + 'Not passing an instance of "%s" as argument 6 for %s::__construct() is deprecated since sonata-project/intl-bundle 2.x.' + .' and will throw an exception in version 3.x.', + IntlExtension::class, + __CLASS__ + )); + $methodArgs = array_pad(\func_get_args(), 5, null); [$locale, $symbols] = $this->normalizeMethodSignature($methodArgs[3], $methodArgs[4]); @@ -216,6 +330,21 @@ public function formatOrdinal($number, array $attributes = [], array $textAttrib */ public function format($number, $style, array $attributes = [], array $textAttributes = [], $locale = null) { + if ($this->intlExtension) { + $attributes = self::processLegacyAttributes($attributes); + + return $this->fixCharset($this->intlExtension->formatNumber($number, $attributes, $style, 'default', $locale ?: $this->localeDetector->getLocale())); + } + + // NEXT_MAJOR: Execute the previous block unconditionally and remove following lines in this method. + + @trigger_error(sprintf( + 'Not passing an instance of "%s" as argument 6 for %s::__construct() is deprecated since sonata-project/intl-bundle 2.x.' + .' and will throw an exception in version 3.x.', + IntlExtension::class, + __CLASS__ + )); + $methodArgs = array_pad(\func_get_args(), 6, null); [$locale, $symbols] = $this->normalizeMethodSignature($methodArgs[4], $methodArgs[5]); @@ -226,10 +355,14 @@ public function format($number, $style, array $attributes = [], array $textAttri } /** + * NEXT_MAJOR: Remove this method. + * * Normalizes the given arguments according to the new function signature. * It asserts if neither the new nor old signature matches. This function * is public just to prevent code duplication inside the Twig Extension. * + * @deprecated since sonata-project/intl-bundle 2.x + * * @param mixed $symbols The symbols used by the formatter * @param mixed $locale The locale * @@ -241,6 +374,12 @@ public function format($number, $style, array $attributes = [], array $textAttri */ public function normalizeMethodSignature($symbols, $locale) { + @trigger_error(sprintf( + 'Method "%s()" is deprecated since sonata-project/intl-bundle 2.x.' + .' and will be removed in version 3.x.', + __METHOD__ + )); + $oldSignature = (null === $symbols || \is_string($symbols)) && null === $locale; $newSignature = \is_array($symbols) && (\is_string($locale) || null === $locale); @@ -267,9 +406,13 @@ public function getName() } /** + * NEXT_MAJOR: Remove this method. + * * Gets an instance of \NumberFormatter set with the given attributes and * style. * + * @deprecated since sonata-project/intl-bundle 2.x + * * @param string $culture The culture used by \NumberFormatter * @param string $style The style used by \NumberFormatter * @param array $attributes The attributes used by \NumberFormatter @@ -280,6 +423,12 @@ public function getName() */ protected function getFormatter($culture, $style, $attributes = [], $textAttributes = [], $symbols = []) { + @trigger_error(sprintf( + 'Method "%s()" is deprecated since sonata-project/intl-bundle 2.x.' + .' and will be removed in version 3.x.', + __METHOD__ + )); + $attributes = $this->parseAttributes(array_merge($this->attributes, $attributes)); $textAttributes = $this->parseAttributes(array_merge($this->textAttributes, $textAttributes)); $symbols = $this->parseAttributes(array_merge($this->symbols, $symbols)); @@ -306,8 +455,12 @@ protected function getFormatter($culture, $style, $attributes = [], $textAttribu } /** + * NEXT_MAJOR: Remove this method. + * * Converts keys of attributes array to values of \NumberFormatter constants. * + * @deprecated since sonata-project/intl-bundle 2.x + * * @param array $attributes The list of attributes * * @throws \InvalidArgumentException If any attribute does not match any constant @@ -316,6 +469,12 @@ protected function getFormatter($culture, $style, $attributes = [], $textAttribu */ protected function parseAttributes(array $attributes) { + @trigger_error(sprintf( + 'Method "%s()" is deprecated since sonata-project/intl-bundle 2.x.' + .' and will be removed in version 3.x.', + __METHOD__ + )); + $result = []; foreach ($attributes as $attribute => $value) { @@ -326,8 +485,12 @@ protected function parseAttributes(array $attributes) } /** + * NEXT_MAJOR: Remove this method. + * * Parse the given value trying to get a match with a \NumberFormatter constant. * + * @deprecated since sonata-project/intl-bundle 2.x + * * @param string $attribute The constant's name * * @throws \InvalidArgumentException If the value does not match any constant @@ -336,6 +499,12 @@ protected function parseAttributes(array $attributes) */ protected function parseConstantValue($attribute) { + @trigger_error(sprintf( + 'Method "%s()" is deprecated since sonata-project/intl-bundle 2.x.' + .' and will be removed in version 3.x.', + __METHOD__ + )); + $attribute = strtoupper($attribute); $constantName = 'NumberFormatter::'.$attribute; @@ -345,4 +514,19 @@ protected function parseConstantValue($attribute) return \constant($constantName); } + + /** + * NEXT_MAJOR: Remove this method. + * + * Replaces legacy attribute names with its new variants. + */ + private static function processLegacyAttributes(array $attributes): array + { + if (isset($attributes['fraction_digits'])) { + $curatedAttributes['fraction_digit'] = $attributes['fraction_digits']; + unset($attributes['fraction_digits']); + } + + return $attributes; + } } diff --git a/src/Twig/Extension/LocaleExtension.php b/src/Twig/Extension/LocaleExtension.php index 96df770a..50aa1a9d 100644 --- a/src/Twig/Extension/LocaleExtension.php +++ b/src/Twig/Extension/LocaleExtension.php @@ -20,6 +20,8 @@ /** * LocaleExtension extends Twig with local capabilities. * + * @final since sonata-project/intl-bundle 2.x + * * @author Thomas Rabaix */ class LocaleExtension extends AbstractExtension diff --git a/tests/Helper/LocaleHelperTest.php b/tests/Helper/LocaleHelperTest.php index 18c5017f..c3501c54 100644 --- a/tests/Helper/LocaleHelperTest.php +++ b/tests/Helper/LocaleHelperTest.php @@ -16,48 +16,97 @@ use PHPUnit\Framework\TestCase; use Sonata\IntlBundle\Locale\LocaleDetectorInterface; use Sonata\IntlBundle\Templating\Helper\LocaleHelper; +use Symfony\Component\Templating\Helper\HelperInterface; +use Twig\Extra\Intl\IntlExtension; -class LocaleHelperTest extends TestCase +final class LocaleHelperTest extends TestCase { - public function getHelper() + /** + * NEXT_MAJOR: Remove this property. + * + * @var HelperInterface + */ + private $legacyLocaleHelper; + + /** + * @var HelperInterface + */ + private $localeHelper; + + protected function setUp(): void { $localeDetector = $this->createMock(LocaleDetectorInterface::class); $localeDetector ->method('getLocale')->willReturn('fr'); - return new LocaleHelper('UTF-8', $localeDetector); + $this->localeHelper = new LocaleHelper('UTF-8', $localeDetector, new IntlExtension()); + $this->legacyLocaleHelper = new LocaleHelper('UTF-8', $localeDetector); } /** * @group legacy */ - public function testLanguage() + public function testLanguage(): void + { + $this->assertSame('français', $this->localeHelper->language('fr')); + $this->assertSame('français', $this->localeHelper->language('fr_FR')); + $this->assertSame('anglais américain', $this->localeHelper->language('en_US')); + $this->assertSame('French', $this->localeHelper->language('fr', 'en')); + } + + public function testCountry(): void + { + $this->assertSame('France', $this->localeHelper->country('FR')); + $this->assertSame('France', $this->localeHelper->country('FR', 'en')); + // $this->assertEquals('', $this->localeHelper->country('FR', 'fake')); + } + + public function testLocale(): void { - $helper = $this->getHelper(); - $this->assertSame('français', $helper->language('fr')); - $this->assertSame('français', $helper->language('fr_FR')); - $this->assertSame('anglais américain', $helper->language('en_US')); - $this->assertSame('French', $helper->language('fr', 'en')); + $this->assertSame('français', $this->localeHelper->locale('fr')); + $this->assertSame('français (Canada)', $this->localeHelper->locale('fr_CA')); + + $this->assertSame('French', $this->localeHelper->locale('fr', 'en')); + $this->assertSame('French (Canada)', $this->localeHelper->locale('fr_CA', 'en')); + // $this->assertEquals('', $this->localeHelper->locale('fr', 'fake')); + // $this->assertEquals('', $this->localeHelper->locale('fr_CA', 'fake')); } - public function testCountry() + /** + * NEXT_MAJOR: Remove this method. + * + * @group legacy + */ + public function testLegacyLanguage(): void { - $helper = $this->getHelper(); - $this->assertSame('France', $helper->country('FR')); - $this->assertSame('France', $helper->country('FR', 'en')); - // $this->assertEquals('', $helper->country('FR', 'fake')); + $this->assertSame('français', $this->legacyLocaleHelper->language('fr')); + $this->assertSame('français', $this->legacyLocaleHelper->language('fr_FR')); + $this->assertSame('anglais américain', $this->legacyLocaleHelper->language('en_US')); + $this->assertSame('French', $this->legacyLocaleHelper->language('fr', 'en')); } - public function testLocale() + /** + * NEXT_MAJOR: Remove this method. + * + * @group legacy + */ + public function testLegacyCountry(): void { - $helper = $this->getHelper(); + $this->assertSame('France', $this->legacyLocaleHelper->country('FR')); + $this->assertSame('France', $this->legacyLocaleHelper->country('FR', 'en')); + } - $this->assertSame('français', $helper->locale('fr')); - $this->assertSame('français (Canada)', $helper->locale('fr_CA')); + /** + * NEXT_MAJOR: Remove this method. + * + * @group legacy + */ + public function testLegacyLocale(): void + { + $this->assertSame('français', $this->legacyLocaleHelper->locale('fr')); + $this->assertSame('français (Canada)', $this->legacyLocaleHelper->locale('fr_CA')); - $this->assertSame('French', $helper->locale('fr', 'en')); - $this->assertSame('French (Canada)', $helper->locale('fr_CA', 'en')); - // $this->assertEquals('', $helper->locale('fr', 'fake')); - // $this->assertEquals('', $helper->locale('fr_CA', 'fake')); + $this->assertSame('French', $this->legacyLocaleHelper->locale('fr', 'en')); + $this->assertSame('French (Canada)', $this->legacyLocaleHelper->locale('fr_CA', 'en')); } } diff --git a/tests/Helper/NumberHelperTest.php b/tests/Helper/NumberHelperTest.php index 582c377d..3270951a 100644 --- a/tests/Helper/NumberHelperTest.php +++ b/tests/Helper/NumberHelperTest.php @@ -16,16 +16,83 @@ use PHPUnit\Framework\TestCase; use Sonata\IntlBundle\Locale\LocaleDetectorInterface; use Sonata\IntlBundle\Templating\Helper\NumberHelper; +use Twig\Extra\Intl\IntlExtension; /** * @author Stefano Arlandini */ -class NumberHelperTest extends TestCase +final class NumberHelperTest extends TestCase { - public function testLocale() + /** + * @var LocaleDetectorInterface + */ + private $localeDetector; + + protected function setUp(): void + { + $this->localeDetector = $this->createStub(LocaleDetectorInterface::class); + $this->localeDetector + ->method('getLocale') + ->willReturn('en'); + } + + public function testLocale(): void + { + $helper = new NumberHelper('UTF-8', $this->localeDetector, [], [], [], new IntlExtension()); + + // currency + $this->assertSame('€10.49', $helper->formatCurrency(10.49, 'EUR')); + $this->assertSame('€10.50', $helper->formatCurrency(10.499, 'EUR')); + $this->assertSame('€10,000.50', $helper->formatCurrency(10000.499, 'EUR')); + + // test compatibility with Doctrine's decimal type (fixed-point number represented as string) + $this->assertSame('€10.49', $helper->formatCurrency('10.49', 'EUR')); + + $this->assertSame('€10.49', $helper->formatCurrency(10.49, 'EUR', [ + // the fraction_digits is not supported by the currency lib, https://bugs.php.net/bug.php?id=63140 + 'fraction_digits' => 0, + ])); + + // decimal + $this->assertSame('10', $helper->formatDecimal(10)); + $this->assertSame('10.155', $helper->formatDecimal(10.15459)); + $this->assertSame('1,000,000.155', $helper->formatDecimal(1000000.15459)); + + // scientific + $this->assertSame('1E1', $helper->formatScientific(10)); + $this->assertSame('1E3', $helper->formatScientific(1000)); + $this->assertSame('1.0001E3', $helper->formatScientific(1000.1)); + $this->assertSame('1.00000015459E6', $helper->formatScientific(1000000.15459)); + $this->assertSame('1.00000015459E6', $helper->formatScientific(1000000.15459)); + + // duration + $this->assertSame('277:46:40', $helper->formatDuration(1000000)); + + // spell out + $this->assertSame('one', $helper->formatSpellout(1)); + $this->assertSame('forty-two', $helper->formatSpellout(42)); + + $this->assertSame('one million two hundred twenty-four thousand five hundred fifty-seven point one two five four', $helper->formatSpellout(1224557.1254)); + + // percent + $this->assertSame('10%', $helper->formatPercent(0.1)); + $this->assertSame('200%', $helper->formatPercent(1.999)); + $this->assertSame('99%', $helper->formatPercent(0.99)); + + // ordinal + $this->assertSame('1st', $helper->formatOrdinal(1), 'ICU Version: '.NumberHelper::getICUDataVersion()); + $this->assertSame('100th', $helper->formatOrdinal(100), 'ICU Version: '.NumberHelper::getICUDataVersion()); + $this->assertSame('10,000th', $helper->formatOrdinal(10000), 'ICU Version: '.NumberHelper::getICUDataVersion()); + } + + /** + * NEXT_MAJOR: Remove this method. + * + * @group legacy + */ + public function testLegacyLocale(): void { - $localeDetector = $this->createLocaleDetectorMock(); - $helper = new NumberHelper('UTF-8', $localeDetector); + $helper = new NumberHelper('UTF-8', $this->localeDetector); // currency $this->assertSame('€10.49', $helper->formatCurrency(10.49, 'EUR')); @@ -72,10 +139,9 @@ public function testLocale() $this->assertSame('10,000th', $helper->formatOrdinal(10000), 'ICU Version: '.NumberHelper::getICUDataVersion()); } - public function testArguments() + public function testArguments(): void { - $localeDetector = $this->createLocaleDetectorMock(); - $helper = new NumberHelper('UTF-8', $localeDetector, ['fraction_digits' => 2], ['negative_prefix' => 'MINUS']); + $helper = new NumberHelper('UTF-8', $this->localeDetector, ['fraction_digits' => 2], ['negative_prefix' => 'MINUS']); // Check that the 'default' options are used $this->assertSame('1.34', $helper->formatDecimal(1.337)); @@ -86,35 +152,20 @@ public function testArguments() $this->assertSame('MIN1.34', $helper->formatDecimal(-1.337, [], ['negative_prefix' => 'MIN'])); } - public function testExceptionOnInvalidParams() + public function testExceptionOnInvalidParams(): void { - $this->expectException(\RuntimeException::class); + $this->expectException(\IntlException::class); // https://wiki.php.net/rfc/internal_constructor_behaviour - try { - $formatter = new \NumberFormatter('EN', -1); - } catch (\IntlException $e) { - throw new \RuntimeException($e->getMessage()); - } - - $this->assertNull($formatter); - - $localeDetector = $this->createMock(LocaleDetectorInterface::class); - $localeDetector - ->method('getLocale')->willReturn('en'); - - $helper = new NumberHelper('UTF-8', $localeDetector, ['fraction_digits' => 2], ['negative_prefix' => 'MINUS']); - - $helper->format(10.49, -1); + $formatter = new \NumberFormatter('EN', -1); } /** * @dataProvider provideConstantValues */ - public function testParseConstantValue($constantName, $expectedConstant, $exceptionExpected) + public function testParseConstantValue(string $constantName, int $expectedConstant, bool $exceptionExpected): void { - $localeDetector = $this->createLocaleDetectorMock(); - $helper = new NumberHelper('UTF-8', $localeDetector); + $helper = new NumberHelper('UTF-8', $this->localeDetector); $method = new \ReflectionMethod($helper, 'parseConstantValue'); $method->setAccessible(true); @@ -125,7 +176,7 @@ public function testParseConstantValue($constantName, $expectedConstant, $except $this->assertSame($expectedConstant, $method->invoke($helper, $constantName)); } - public function provideConstantValues() + public function provideConstantValues(): iterable { return [ ['positive_prefix', \NumberFormatter::POSITIVE_PREFIX, false], @@ -136,10 +187,9 @@ public function provideConstantValues() /** * @dataProvider provideAttributeValues */ - public function testParseAttributes($attributes, $expectedAttributes, $exceptionExpected) + public function testParseAttributes(array $attributes, array $expectedAttributes, bool $exceptionExpected): void { - $localeDetector = $this->createLocaleDetectorMock(); - $helper = new NumberHelper('UTF-8', $localeDetector); + $helper = new NumberHelper('UTF-8', $this->localeDetector); $method = new \ReflectionMethod($helper, 'parseAttributes'); $method->setAccessible(true); @@ -150,7 +200,7 @@ public function testParseAttributes($attributes, $expectedAttributes, $exception $this->assertSame($expectedAttributes, $method->invoke($helper, $attributes)); } - public function provideAttributeValues() + public function provideAttributeValues(): iterable { return [ [ @@ -177,10 +227,9 @@ public function provideAttributeValues() /** * @dataProvider provideFormatMethodArguments */ - public function testFormatMethodSignatures($arguments, $expectedArguments, $exceptionExpected) + public function testFormatMethodSignatures(array $arguments, array $expectedArguments, bool $exceptionExpected): void { - $localeDetector = $this->createLocaleDetectorMock(); - $helper = new NumberHelper('UTF-8', $localeDetector); + $helper = new NumberHelper('UTF-8', $this->localeDetector); if ($exceptionExpected) { $this->expectException(\BadMethodCallException::class); @@ -189,7 +238,7 @@ public function testFormatMethodSignatures($arguments, $expectedArguments, $exce $this->assertSame($expectedArguments, \call_user_func_array([$helper, 'normalizeMethodSignature'], $arguments)); } - public function provideFormatMethodArguments() + public function provideFormatMethodArguments(): iterable { return [ [ @@ -220,10 +269,9 @@ public function provideFormatMethodArguments() ]; } - public function testFormatMethodWithDefaultArguments() + public function testFormatMethodWithDefaultArguments(): void { - $localeDetector = $this->createLocaleDetectorMock(); - $helper = new NumberHelper('UTF-8', $localeDetector); + $helper = new NumberHelper('UTF-8', $this->localeDetector); $method = new \ReflectionMethod($helper, 'format'); $method->setAccessible(true); @@ -231,14 +279,4 @@ public function testFormatMethodWithDefaultArguments() $this->assertSame('10', $method->invoke($helper, 10, \NumberFormatter::DECIMAL, [], [], 'fr')); $this->assertSame('10', $method->invoke($helper, 10, \NumberFormatter::DECIMAL, [], [], [])); } - - private function createLocaleDetectorMock() - { - $localeDetector = $this->createMock(LocaleDetectorInterface::class); - $localeDetector - ->method('getLocale')->willReturn('en') - ; - - return $localeDetector; - } } From 1ef2029fc6dc82f2e46c4196cb2529d9a57181bd Mon Sep 17 00:00:00 2001 From: Javier Spagnoletti Date: Sun, 7 Jun 2020 14:52:55 -0300 Subject: [PATCH 2/2] Respect `$textAttributes` arguments --- src/Templating/Helper/NumberHelper.php | 45 +++++++++++++++----------- tests/Helper/NumberHelperTest.php | 4 ++- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/Templating/Helper/NumberHelper.php b/src/Templating/Helper/NumberHelper.php index c3dc683c..01df3e13 100644 --- a/src/Templating/Helper/NumberHelper.php +++ b/src/Templating/Helper/NumberHelper.php @@ -78,8 +78,9 @@ public function formatPercent($number, array $attributes = [], array $textAttrib { if ($this->intlExtension) { $attributes = self::processLegacyAttributes($attributes); + $intlExtension = $this->getIntlExtension($locale, \NumberFormatter::PERCENT, $textAttributes); - return $this->fixCharset($this->intlExtension->formatNumberStyle('percent', $number, $attributes, 'default', $locale ?: $this->localeDetector->getLocale())); + return $this->fixCharset($intlExtension->formatNumberStyle('percent', $number, $attributes, 'default', $locale ?: $this->localeDetector->getLocale())); } // NEXT_MAJOR: Execute the previous block unconditionally and remove following lines in this method. @@ -113,8 +114,9 @@ public function formatDuration($number, array $attributes = [], array $textAttri { if ($this->intlExtension) { $attributes = self::processLegacyAttributes($attributes); + $intlExtension = $this->getIntlExtension($locale, \NumberFormatter::DURATION, $textAttributes); - return $this->fixCharset($this->intlExtension->formatNumberStyle('duration', $number, $attributes, 'default', $locale ?: $this->localeDetector->getLocale())); + return $this->fixCharset($intlExtension->formatNumberStyle('duration', $number, $attributes, 'default', $locale ?: $this->localeDetector->getLocale())); } // NEXT_MAJOR: Execute the previous block unconditionally and remove following lines in this method. @@ -148,8 +150,9 @@ public function formatDecimal($number, array $attributes = [], array $textAttrib { if ($this->intlExtension) { $attributes = self::processLegacyAttributes($attributes); + $intlExtension = $this->getIntlExtension($locale, \NumberFormatter::DECIMAL, $textAttributes); - return $this->fixCharset($this->intlExtension->formatNumberStyle('decimal', $number, $attributes, 'default', $locale ?: $this->localeDetector->getLocale())); + return $this->fixCharset($intlExtension->formatNumberStyle('decimal', $number, $attributes, 'default', $locale ?: $this->localeDetector->getLocale())); } // NEXT_MAJOR: Execute the previous block unconditionally and remove following lines in this method. @@ -183,8 +186,9 @@ public function formatSpellout($number, array $attributes = [], array $textAttri { if ($this->intlExtension) { $attributes = self::processLegacyAttributes($attributes); + $intlExtension = $this->getIntlExtension($locale, \NumberFormatter::SPELLOUT, $textAttributes); - return $this->fixCharset($this->intlExtension->formatNumberStyle('spellout', $number, $attributes, 'default', $locale ?: $this->localeDetector->getLocale())); + return $this->fixCharset($intlExtension->formatNumberStyle('spellout', $number, $attributes, 'default', $locale ?: $this->localeDetector->getLocale())); } // NEXT_MAJOR: Execute the previous block unconditionally and remove following lines in this method. @@ -219,8 +223,9 @@ public function formatCurrency($number, $currency, array $attributes = [], array { if ($this->intlExtension) { $attributes = self::processLegacyAttributes($attributes); + $intlExtension = $this->getIntlExtension($locale, \NumberFormatter::CURRENCY, $textAttributes); - return $this->fixCharset($this->intlExtension->formatCurrency($number, $currency, $attributes, $locale ?: $this->localeDetector->getLocale())); + return $this->fixCharset($intlExtension->formatCurrency($number, $currency, $attributes, $locale ?: $this->localeDetector->getLocale())); } // NEXT_MAJOR: Execute the previous block unconditionally and remove following lines in this method. @@ -261,8 +266,9 @@ public function formatScientific($number, array $attributes = [], array $textAtt { if ($this->intlExtension) { $attributes = self::processLegacyAttributes($attributes); + $intlExtension = $this->getIntlExtension($locale, \NumberFormatter::SCIENTIFIC, $textAttributes); - return $this->fixCharset($this->intlExtension->formatNumberStyle('scientific', $number, $attributes, 'default', $locale ?: $this->localeDetector->getLocale())); + return $this->fixCharset($intlExtension->formatNumberStyle('scientific', $number, $attributes, 'default', $locale ?: $this->localeDetector->getLocale())); } // NEXT_MAJOR: Execute the previous block unconditionally and remove following lines in this method. @@ -296,8 +302,9 @@ public function formatOrdinal($number, array $attributes = [], array $textAttrib { if ($this->intlExtension) { $attributes = self::processLegacyAttributes($attributes); + $intlExtension = $this->getIntlExtension($locale, \NumberFormatter::ORDINAL, $textAttributes); - return $this->fixCharset($this->intlExtension->formatNumberStyle('ordinal', $number, $attributes, 'default', $locale ?: $this->localeDetector->getLocale())); + return $this->fixCharset($intlExtension->formatNumberStyle('ordinal', $number, $attributes, 'default', $locale ?: $this->localeDetector->getLocale())); } // NEXT_MAJOR: Execute the previous block unconditionally and remove following lines in this method. @@ -332,8 +339,9 @@ public function format($number, $style, array $attributes = [], array $textAttri { if ($this->intlExtension) { $attributes = self::processLegacyAttributes($attributes); + $intlExtension = $this->getIntlExtension($locale, $style, $textAttributes); - return $this->fixCharset($this->intlExtension->formatNumber($number, $attributes, $style, 'default', $locale ?: $this->localeDetector->getLocale())); + return $this->fixCharset($intlExtension->formatNumberStyle($style, $number, $attributes, $locale ?: $this->localeDetector->getLocale())); } // NEXT_MAJOR: Execute the previous block unconditionally and remove following lines in this method. @@ -406,13 +414,9 @@ public function getName() } /** - * NEXT_MAJOR: Remove this method. - * * Gets an instance of \NumberFormatter set with the given attributes and * style. * - * @deprecated since sonata-project/intl-bundle 2.x - * * @param string $culture The culture used by \NumberFormatter * @param string $style The style used by \NumberFormatter * @param array $attributes The attributes used by \NumberFormatter @@ -423,12 +427,6 @@ public function getName() */ protected function getFormatter($culture, $style, $attributes = [], $textAttributes = [], $symbols = []) { - @trigger_error(sprintf( - 'Method "%s()" is deprecated since sonata-project/intl-bundle 2.x.' - .' and will be removed in version 3.x.', - __METHOD__ - )); - $attributes = $this->parseAttributes(array_merge($this->attributes, $attributes)); $textAttributes = $this->parseAttributes(array_merge($this->textAttributes, $textAttributes)); $symbols = $this->parseAttributes(array_merge($this->symbols, $symbols)); @@ -529,4 +527,15 @@ private static function processLegacyAttributes(array $attributes): array return $attributes; } + + private function getIntlExtension(?string $locale = null, int $style, array $textAttributes): IntlExtension + { + if (empty($textAttributes)) { + return $this->intlExtension; + } + + $numberFormatterProto = $this->getFormatter($locale ?: $this->localeDetector->getLocale(), $style, [], $textAttributes); + + return new IntlExtension(null, $numberFormatterProto); + } } diff --git a/tests/Helper/NumberHelperTest.php b/tests/Helper/NumberHelperTest.php index 3270951a..6ec2fe60 100644 --- a/tests/Helper/NumberHelperTest.php +++ b/tests/Helper/NumberHelperTest.php @@ -76,6 +76,7 @@ public function testLocale(): void // percent $this->assertSame('10%', $helper->formatPercent(0.1)); + $this->assertSame('+ 10%', $helper->formatPercent(0.1, [], ['positive_prefix' => '+ '])); $this->assertSame('200%', $helper->formatPercent(1.999)); $this->assertSame('99%', $helper->formatPercent(0.99)); @@ -104,7 +105,7 @@ public function testLegacyLocale(): void $this->assertSame('€10.49', $helper->formatCurrency(10.49, 'EUR', [ // the fraction_digits is not supported by the currency lib, https://bugs.php.net/bug.php?id=63140 - 'fraction_digits' => 0, + 'fraction_digits' => 2, ])); // decimal @@ -130,6 +131,7 @@ public function testLegacyLocale(): void // percent $this->assertSame('10%', $helper->formatPercent(0.1)); + $this->assertSame('+ 10%', $helper->formatPercent(0.1, [], ['positive_prefix' => '+ '])); $this->assertSame('200%', $helper->formatPercent(1.999)); $this->assertSame('99%', $helper->formatPercent(0.99));