Skip to content

Commit c640062

Browse files
authored
Merge pull request #6753 from magento-performance/MCP-288
[Performance] MCP-288: Replace Zend_Currency component with Intl NumberFormatter
2 parents f25fc03 + 884bb7e commit c640062

File tree

4 files changed

+355
-39
lines changed

4 files changed

+355
-39
lines changed

app/code/Magento/Directory/Model/Currency.php

Lines changed: 154 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
use Magento\Framework\App\ObjectManager;
1010
use Magento\Framework\Exception\InputException;
1111
use Magento\Directory\Model\Currency\Filter;
12+
use Magento\Framework\Locale\Currency as LocaleCurrency;
13+
use Magento\Framework\Locale\ResolverInterface as LocalResolverInterface;
14+
use Magento\Framework\NumberFormatterFactory;
15+
use Magento\Framework\Serialize\Serializer\Json;
1216

1317
/**
1418
* Currency model
@@ -71,6 +75,31 @@ class Currency extends \Magento\Framework\Model\AbstractModel
7175
*/
7276
private $currencyConfig;
7377

78+
/**
79+
* @var LocalResolverInterface
80+
*/
81+
private $localeResolver;
82+
83+
/**
84+
* @var NumberFormatterFactory
85+
*/
86+
private $numberFormatterFactory;
87+
88+
/**
89+
* @var \Magento\Framework\NumberFormatter
90+
*/
91+
private $numberFormatter;
92+
93+
/**
94+
* @var array
95+
*/
96+
private $numberFormatterCache;
97+
98+
/**
99+
* @var Json
100+
*/
101+
private $serializer;
102+
74103
/**
75104
* @param \Magento\Framework\Model\Context $context
76105
* @param \Magento\Framework\Registry $registry
@@ -79,10 +108,13 @@ class Currency extends \Magento\Framework\Model\AbstractModel
79108
* @param \Magento\Directory\Helper\Data $directoryHelper
80109
* @param Currency\FilterFactory $currencyFilterFactory
81110
* @param \Magento\Framework\Locale\CurrencyInterface $localeCurrency
82-
* @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
83-
* @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
111+
* @param \Magento\Framework\Model\ResourceModel\AbstractResource|null $resource
112+
* @param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection
84113
* @param array $data
85-
* @param CurrencyConfig $currencyConfig
114+
* @param CurrencyConfig|null $currencyConfig
115+
* @param LocalResolverInterface|null $localeResolver
116+
* @param NumberFormatterFactory|null $numberFormatterFactory
117+
* @param Json|null $serializer
86118
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
87119
*/
88120
public function __construct(
@@ -96,7 +128,10 @@ public function __construct(
96128
\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
97129
\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
98130
array $data = [],
99-
CurrencyConfig $currencyConfig = null
131+
CurrencyConfig $currencyConfig = null,
132+
LocalResolverInterface $localeResolver = null,
133+
\Magento\Framework\NumberFormatterFactory $numberFormatterFactory = null,
134+
Json $serializer = null
100135
) {
101136
parent::__construct(
102137
$context,
@@ -111,6 +146,9 @@ public function __construct(
111146
$this->_currencyFilterFactory = $currencyFilterFactory;
112147
$this->_localeCurrency = $localeCurrency;
113148
$this->currencyConfig = $currencyConfig ?: ObjectManager::getInstance()->get(CurrencyConfig::class);
149+
$this->localeResolver = $localeResolver ?: ObjectManager::getInstance()->get(LocalResolverInterface::class);
150+
$this->numberFormatterFactory = $numberFormatterFactory ?: ObjectManager::getInstance()->get(NumberFormatterFactory::class);
151+
$this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class);
114152
}
115153

116154
/**
@@ -326,9 +364,121 @@ public function formatTxt($price, $options = [])
326364
* %F - the argument is treated as a float, and presented as a floating-point number (non-locale aware).
327365
*/
328366
$price = sprintf("%F", $price);
367+
368+
if ($this->canUseNumberFormatter($options)) {
369+
return $this->formatCurrency($price, $options);
370+
}
371+
329372
return $this->_localeCurrency->getCurrency($this->getCode())->toCurrency($price, $options);
330373
}
331374

375+
/**
376+
* Check if to use Intl.NumberFormatter to format currency.
377+
*
378+
* @param array $options
379+
* @return bool
380+
*/
381+
private function canUseNumberFormatter(array $options): bool
382+
{
383+
$allowedOptions = [
384+
'precision',
385+
LocaleCurrency::CURRENCY_OPTION_DISPLAY,
386+
LocaleCurrency::CURRENCY_OPTION_SYMBOL
387+
];
388+
389+
if (!empty(array_diff(array_keys($options), $allowedOptions))) {
390+
return false;
391+
}
392+
393+
if (array_key_exists('display', $options)
394+
&& $options['display'] !== \Magento\Framework\Currency::NO_SYMBOL
395+
&& $options['display'] !== \Magento\Framework\Currency::USE_SYMBOL
396+
) {
397+
return false;
398+
}
399+
400+
return true;
401+
}
402+
403+
/**
404+
* Format currency.
405+
*
406+
* @param string $price
407+
* @param array $options
408+
* @return string
409+
*/
410+
private function formatCurrency(string $price, array $options): string
411+
{
412+
$customerOptions = new \Magento\Framework\DataObject([]);
413+
414+
$this->_eventManager->dispatch(
415+
'currency_display_options_forming',
416+
['currency_options' => $customerOptions, 'base_code' => $this->getCode()]
417+
);
418+
$options += $customerOptions->toArray();
419+
420+
$this->numberFormatter = $this->getNumberFormatter($options);
421+
422+
$formattedCurrency = $this->numberFormatter->formatCurrency(
423+
$price, $this->getCode() ?? $this->numberFormatter->getTextAttribute(\NumberFormatter::CURRENCY_CODE)
424+
);
425+
426+
if (array_key_exists(LocaleCurrency::CURRENCY_OPTION_SYMBOL, $options)) {
427+
// remove only one non-breaking space from custom currency symbol to allow custom NBSP in currency symbol
428+
$formattedCurrency = preg_replace('/ /u', '', $formattedCurrency, 1);
429+
}
430+
431+
if ((array_key_exists(LocaleCurrency::CURRENCY_OPTION_DISPLAY, $options)
432+
&& $options[LocaleCurrency::CURRENCY_OPTION_DISPLAY] === \Magento\Framework\Currency::NO_SYMBOL)) {
433+
$formattedCurrency = str_replace(' ', '', $formattedCurrency);
434+
}
435+
436+
return preg_replace('/^\s+|\s+$/u', '', $formattedCurrency);
437+
}
438+
439+
/**
440+
* Get NumberFormatter object from cache.
441+
*
442+
* @param array $options
443+
* @return \Magento\Framework\NumberFormatter
444+
*/
445+
private function getNumberFormatter(array $options): \Magento\Framework\NumberFormatter
446+
{
447+
$key = 'currency_' . md5($this->localeResolver->getLocale() . $this->serializer->serialize($options));
448+
if (!isset($this->numberFormatterCache[$key])) {
449+
$this->numberFormatter = $this->numberFormatterFactory->create(
450+
['locale' => $this->localeResolver->getLocale(), 'style' => \NumberFormatter::CURRENCY]
451+
);
452+
453+
$this->setOptions($options);
454+
$this->numberFormatterCache[$key] = $this->numberFormatter;
455+
}
456+
457+
return $this->numberFormatterCache[$key];
458+
}
459+
460+
/**
461+
* Set number formatter custom options.
462+
*
463+
* @param array $options
464+
* @return void
465+
*/
466+
private function setOptions(array $options): void
467+
{
468+
if (array_key_exists(LocaleCurrency::CURRENCY_OPTION_SYMBOL, $options)) {
469+
$this->numberFormatter->setSymbol(
470+
\NumberFormatter::CURRENCY_SYMBOL, $options[LocaleCurrency::CURRENCY_OPTION_SYMBOL]
471+
);
472+
}
473+
if (array_key_exists(LocaleCurrency::CURRENCY_OPTION_DISPLAY, $options)
474+
&& $options[LocaleCurrency::CURRENCY_OPTION_DISPLAY] === \Magento\Framework\Currency::NO_SYMBOL) {
475+
$this->numberFormatter->setSymbol(\NumberFormatter::CURRENCY_SYMBOL, '');
476+
}
477+
if (array_key_exists('precision', $options)) {
478+
$this->numberFormatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, $options['precision']);
479+
}
480+
}
481+
332482
/**
333483
* Return currency symbol for current locale and currency code
334484
*

0 commit comments

Comments
 (0)