/src/View/Helper/CurrencyFormat.php

https://github.com/zendframework/zend-i18n · PHP · 283 lines · 134 code · 31 blank · 118 comment · 11 complexity · c1a4e1ef4afcf97802104c3ab59a2813 MD5 · raw file

  1. <?php
  2. /**
  3. * @see https://github.com/zendframework/zend-i18n for the canonical source repository
  4. * @copyright Copyright (c) 2005-2019 Zend Technologies USA Inc. (https://www.zend.com)
  5. * @license https://github.com/zendframework/zend-i18n/blob/master/LICENSE.md New BSD License
  6. */
  7. namespace Zend\I18n\View\Helper;
  8. use Locale;
  9. use NumberFormatter;
  10. use Zend\I18n\Exception;
  11. use Zend\View\Helper\AbstractHelper;
  12. /**
  13. * View helper for formatting currency.
  14. */
  15. class CurrencyFormat extends AbstractHelper
  16. {
  17. /**
  18. * The 3-letter ISO 4217 currency code indicating the currency to use
  19. *
  20. * @var string
  21. */
  22. protected $currencyCode;
  23. /**
  24. * Formatter instances
  25. *
  26. * @var array
  27. */
  28. protected $formatters = [];
  29. /**
  30. * Locale to use instead of the default
  31. *
  32. * @var string
  33. */
  34. protected $locale;
  35. /**
  36. * Currency pattern
  37. *
  38. * @var string
  39. */
  40. protected $currencyPattern;
  41. /**
  42. * If set to true, the currency will be returned with two decimals
  43. *
  44. * @var bool
  45. */
  46. protected $showDecimals = true;
  47. /**
  48. * Special condition to be checked due to ICU bug:
  49. * http://bugs.icu-project.org/trac/ticket/10997
  50. *
  51. * @var bool
  52. */
  53. protected $correctionNeeded = false;
  54. /**
  55. * @throws Exception\ExtensionNotLoadedException if ext/intl is not present
  56. */
  57. public function __construct()
  58. {
  59. if (! extension_loaded('intl')) {
  60. throw new Exception\ExtensionNotLoadedException(sprintf(
  61. '%s component requires the intl PHP extension',
  62. __NAMESPACE__
  63. ));
  64. }
  65. }
  66. /**
  67. * Format a number
  68. *
  69. * @param float $number
  70. * @param string|null $currencyCode
  71. * @param bool|null $showDecimals
  72. * @param string|null $locale
  73. * @param string|null $pattern
  74. * @return string
  75. */
  76. public function __invoke(
  77. $number,
  78. $currencyCode = null,
  79. $showDecimals = null,
  80. $locale = null,
  81. $pattern = null
  82. ) {
  83. if (null === $locale) {
  84. $locale = $this->getLocale();
  85. }
  86. if (null === $currencyCode) {
  87. $currencyCode = $this->getCurrencyCode();
  88. }
  89. if (null === $showDecimals) {
  90. $showDecimals = $this->shouldShowDecimals();
  91. }
  92. if (null === $pattern) {
  93. $pattern = $this->getCurrencyPattern();
  94. }
  95. return $this->formatCurrency($number, $currencyCode, $showDecimals, $locale, $pattern);
  96. }
  97. /**
  98. * Format a number
  99. *
  100. * @param float $number
  101. * @param string $currencyCode
  102. * @param bool $showDecimals
  103. * @param string $locale
  104. * @param string $pattern
  105. * @return string
  106. */
  107. protected function formatCurrency(
  108. $number,
  109. $currencyCode,
  110. $showDecimals,
  111. $locale,
  112. $pattern
  113. ) {
  114. $formatterId = md5($locale);
  115. if (! isset($this->formatters[$formatterId])) {
  116. $this->formatters[$formatterId] = new NumberFormatter(
  117. $locale,
  118. NumberFormatter::CURRENCY
  119. );
  120. }
  121. if ($pattern !== null) {
  122. $this->formatters[$formatterId]->setPattern($pattern);
  123. }
  124. if ($showDecimals) {
  125. $this->formatters[$formatterId]->setAttribute(NumberFormatter::FRACTION_DIGITS, 2);
  126. $this->correctionNeeded = false;
  127. } else {
  128. $this->formatters[$formatterId]->setAttribute(NumberFormatter::FRACTION_DIGITS, 0);
  129. $defaultCurrencyCode = $this->formatters[$formatterId]->getTextAttribute(NumberFormatter::CURRENCY_CODE);
  130. $this->correctionNeeded = $defaultCurrencyCode !== $currencyCode;
  131. }
  132. $formattedNumber = $this->formatters[$formatterId]->formatCurrency($number, $currencyCode);
  133. if ($this->correctionNeeded) {
  134. $formattedNumber = $this->fixICUBugForNoDecimals(
  135. $formattedNumber,
  136. $this->formatters[$formatterId],
  137. $locale,
  138. $currencyCode
  139. );
  140. }
  141. return $formattedNumber;
  142. }
  143. /**
  144. * The 3-letter ISO 4217 currency code indicating the currency to use
  145. *
  146. * @param string $currencyCode
  147. * @return $this
  148. */
  149. public function setCurrencyCode($currencyCode)
  150. {
  151. $this->currencyCode = $currencyCode;
  152. return $this;
  153. }
  154. /**
  155. * Get the 3-letter ISO 4217 currency code indicating the currency to use
  156. *
  157. * @return string
  158. */
  159. public function getCurrencyCode()
  160. {
  161. return $this->currencyCode;
  162. }
  163. /**
  164. * Set the currency pattern
  165. *
  166. * @param string $currencyPattern
  167. * @return $this
  168. */
  169. public function setCurrencyPattern($currencyPattern)
  170. {
  171. $this->currencyPattern = $currencyPattern;
  172. return $this;
  173. }
  174. /**
  175. * Get the currency pattern
  176. *
  177. * @return string
  178. */
  179. public function getCurrencyPattern()
  180. {
  181. return $this->currencyPattern;
  182. }
  183. /**
  184. * Set locale to use instead of the default
  185. *
  186. * @param string $locale
  187. * @return $this
  188. */
  189. public function setLocale($locale)
  190. {
  191. $this->locale = (string) $locale;
  192. return $this;
  193. }
  194. /**
  195. * Get the locale to use
  196. *
  197. * @return string
  198. */
  199. public function getLocale()
  200. {
  201. if ($this->locale === null) {
  202. $this->locale = Locale::getDefault();
  203. }
  204. return $this->locale;
  205. }
  206. /**
  207. * Set if the view helper should show two decimals
  208. *
  209. * @param bool $showDecimals
  210. * @return $this
  211. */
  212. public function setShouldShowDecimals($showDecimals)
  213. {
  214. $this->showDecimals = (bool) $showDecimals;
  215. return $this;
  216. }
  217. /**
  218. * Get if the view helper should show two decimals
  219. *
  220. * @return bool
  221. */
  222. public function shouldShowDecimals()
  223. {
  224. return $this->showDecimals;
  225. }
  226. /**
  227. * @param string $formattedNumber
  228. * @param NumberFormatter $formatter
  229. * @param string $locale
  230. * @param string $currencyCode
  231. * @return string
  232. */
  233. private function fixICUBugForNoDecimals($formattedNumber, NumberFormatter $formatter, $locale, $currencyCode)
  234. {
  235. $pattern = sprintf(
  236. '/\%s\d+(\s?%s)?$/u',
  237. $formatter->getSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL),
  238. preg_quote($this->getCurrencySymbol($locale, $currencyCode), '/')
  239. );
  240. return preg_replace($pattern, '$1', $formattedNumber);
  241. }
  242. /**
  243. * @param string $locale
  244. * @param string $currencyCode
  245. * @return string
  246. */
  247. private function getCurrencySymbol($locale, $currencyCode)
  248. {
  249. $numberFormatter = new NumberFormatter($locale . '@currency=' . $currencyCode, NumberFormatter::CURRENCY);
  250. return $numberFormatter->getSymbol(NumberFormatter::CURRENCY_SYMBOL);
  251. }
  252. }