'en', 'use_grouping' => true, 'minimum_fraction_digits' => null, 'maximum_fraction_digits' => null, 'rounding_mode' => PHP_ROUND_HALF_UP, 'style' => 'standard', 'currency_display' => 'symbol', ]; /** * Creates a CurrencyFormatter instance. * * @param NumberFormatRepositoryInterface $numberFormatRepository The number format repository. * @param CurrencyRepositoryInterface $currencyRepository The currency repository. * @param array $defaultOptions The default options. * * @throws \RuntimeException */ public function __construct(NumberFormatRepositoryInterface $numberFormatRepository, CurrencyRepositoryInterface $currencyRepository, array $defaultOptions = []) { if (!extension_loaded('bcmath')) { throw new \RuntimeException('The bcmath extension is required by CurrencyFormatter.'); } $this->validateOptions($defaultOptions); $this->numberFormatRepository = $numberFormatRepository; $this->currencyRepository = $currencyRepository; $this->defaultOptions = array_replace($this->defaultOptions, $defaultOptions); } /** * {@inheritdoc} */ public function format($number, $currencyCode, array $options = []) { if (!is_numeric($number)) { $message = sprintf('The provided value "%s" is not a valid number or numeric string.', $number); throw new InvalidArgumentException($message); } $this->validateOptions($options); $options = array_replace($this->defaultOptions, $options); $numberFormat = $this->getNumberFormat($options['locale']); $currency = $this->getCurrency($currencyCode, $options['locale']); // Use the currency defaults if the values weren't set by the caller. if (!isset($options['minimum_fraction_digits'])) { $options['minimum_fraction_digits'] = $currency->getFractionDigits(); } if (!isset($options['maximum_fraction_digits'])) { $options['maximum_fraction_digits'] = $currency->getFractionDigits(); } $number = (string) $number; $number = $this->formatNumber($number, $numberFormat, $options); if ($options['currency_display'] == 'symbol') { $number = str_replace('¤', $currency->getSymbol(), $number); } elseif ($options['currency_display'] == 'code') { $number = str_replace('¤', $currency->getCurrencyCode(), $number); } else { // No symbol should be displayed. Remove leftover whitespace. $number = str_replace('¤', '', $number); $number = trim($number, " \xC2\xA0"); } return $number; } /** * {@inheritdoc} */ public function parse($number, $currencyCode, array $options = []) { $this->validateOptions($options); $options = array_replace($this->defaultOptions, $options); $numberFormat = $this->getNumberFormat($options['locale']); $currency = $this->getCurrency($currencyCode, $options['locale']); $replacements = [ // Strip the currency code or symbol. $currency->getCurrencyCode() => '', $currency->getSymbol() => '', ]; $number = strtr($number, $replacements); $number = $this->parseNumber($number, $numberFormat); return $number; } /** * Gets the number format for the provided locale. * * @param string $locale The locale. * * @return NumberFormat */ protected function getNumberFormat($locale) { if (!isset($this->numberFormats[$locale])) { $this->numberFormats[$locale] = $this->numberFormatRepository->get($locale); } return $this->numberFormats[$locale]; } /** * Gets the currency for the provided currency code and locale. * * @param string $currencyCode The currency code. * @param string $locale The locale. * * @return Currency */ protected function getCurrency($currencyCode, $locale) { if (!isset($this->currencies[$currencyCode][$locale])) { try { $currency = $this->currencyRepository->get($currencyCode, $locale); } catch (UnknownCurrencyException $e) { // The requested currency was not found. Fall back // to a dummy object to show just the currency code. $currency = new Currency([ 'currency_code' => $currencyCode, 'name' => $currencyCode, 'numeric_code' => '000', 'locale' => $locale, ]); } $this->currencies[$currencyCode][$locale] = $currency; } return $this->currencies[$currencyCode][$locale]; } /** * {@inheritdoc} */ protected function getAvailablePatterns(NumberFormat $numberFormat) { return [ 'standard' => $numberFormat->getCurrencyPattern(), 'accounting' => $numberFormat->getAccountingCurrencyPattern(), ]; } /** * Validates the provided options. * * Ensures the absence of unknown keys, correct data types and values. * * @param array $options The options. * * @throws \InvalidArgumentException */ protected function validateOptions(array $options) { foreach ($options as $option => $value) { if (!array_key_exists($option, $this->defaultOptions)) { throw new InvalidArgumentException(sprintf('Unrecognized option "%s".', $option)); } } if (isset($options['use_grouping']) && !is_bool($options['use_grouping'])) { throw new InvalidArgumentException('The option "use_grouping" must be a boolean.'); } foreach (['minimum_fraction_digits', 'maximum_fraction_digits'] as $option) { if (array_key_exists($option, $options) && !is_numeric($options[$option])) { throw new InvalidArgumentException(sprintf('The option "%s" must be numeric.', $option)); } } $roundingModes = [ PHP_ROUND_HALF_UP, PHP_ROUND_HALF_DOWN, PHP_ROUND_HALF_EVEN, PHP_ROUND_HALF_ODD, 'none', ]; if (!empty($options['rounding_mode']) && !in_array($options['rounding_mode'], $roundingModes)) { throw new InvalidArgumentException(sprintf('Unrecognized rounding mode "%s".', $options['rounding_mode'])); } if (!empty($options['style']) && !in_array($options['style'], ['standard', 'accounting'])) { throw new InvalidArgumentException(sprintf('Unrecognized style "%s".', $options['style'])); } if (!empty($options['currency_display']) && !in_array($options['currency_display'], ['code', 'symbol', 'none'])) { throw new InvalidArgumentException(sprintf('Unrecognized currency display "%s".', $options['currency_display'])); } } }