按地区处理货币

处理货币的技术与处理数字的技术类似。我们甚至会使用相同的NumberFormatter类。然而,有一个主要的区别,它是一个引人注目的问题:为了正确地格式化货币,你需要手头有货币代码。

如何做…

1.首先要做的是以某种格式提供货币代码。一种可能性是简单地将货币代码添加为Application\I18n\Locale类构造函数参数。

  1. const FALLBACK_CURRENCY = 'GBP';
  2. protected $currencyCode;
  3. public function __construct($localeString = NULL, $currencyCode = NULL)
  4. {
  5. // add this to the existing code:
  6. $this->currencyCode = $currencyCode ?? self::FALLBACK_CURRENCY;
  7. }

{% hint style=”info” %} 这种方法虽然明显是稳妥可行的,但往往属于所谓的半途而废或易如反掌! 这种方法也倾向于消除完全自动化,因为货币代码无法从HTTP头中获得。正如你可能已经从本书中的其他事例中收集到的,我们并不回避更复杂的解决方案,所以,正如俗话说的那样,系上你的安全带吧!我们的解决方案是:”你可以在你的网站上找到你想要的东西”。 {% endhint %}

2.我们首先需要建立某种查找机制,给定一个国家代码,我们就可以获得其主要的货币代码。在这个例子中,我们将使用适配器软件设计模式。根据这种模式,我们应该能够创建不同的类,这些类有可能以完全不同的方式进行操作,但却产生相同的结果。据此,我们需要定义所需的结果。为此,我们引入一个类,Application\I18n\IsoCodes。正如你所看到的,这个类有所有相关的属性,还有一个通用的构造函数。

  1. namespace Application\I18n;
  2. class IsoCodes
  3. {
  4. public $name;
  5. public $iso2;
  6. public $iso3;
  7. public $iso_numeric;
  8. public $iso_3166;
  9. public $currency_name;
  10. public $currency_code;
  11. public $currency_number;
  12. public function __construct(array $data)
  13. {
  14. $vars = get_object_vars($this);
  15. foreach ($vars as $key => $value) {
  16. $this->$key = $data[$key] ?? NULL;
  17. }
  18. }
  19. }

3.接下来我们定义一个接口,它有我们需要的方法来执行国家代码到货币代码的查询。在这种情况下,我们引入Application\I18n\IsoCodesInterface

  1. namespace Application\I18n;
  2. interface IsoCodesInterface
  3. {
  4. public function getCurrencyCodeFromIso2CountryCode($iso2) : IsoCodes;
  5. }
  1. **现在我们准备建立一个查找适配器类,我们将把它称为Application\I18n\IsoCodesDb。它实现了上述接口,并接受一个Application\Database\Connection实例(参见第1章,建立基础),用于执行查找。构造函数设置了所需的信息,包括连接、查找表名和代表ISO2代码的列。然后,接口所需的查找方法会发出一条SQL语句并返回一个数组,然后用这个数组来构建一个IsoCodes实例。
  1. namespace Application\I18n;
  2. use PDO;
  3. use Application\Database\Connection;
  4. class IsoCodesDb implements IsoCodesInterface
  5. {
  6. protected $isoTableName;
  7. protected $iso2FieldName;
  8. protected $connection;
  9. public function __construct(Connection $connection, $isoTableName, $iso2FieldName)
  10. {
  11. $this->connection = $connection;
  12. $this->isoTableName = $isoTableName;
  13. $this->iso2FieldName = $iso2FieldName;
  14. }
  15. public function getCurrencyCodeFromIso2CountryCode($iso2) : IsoCodes
  16. {
  17. $sql = sprintf('SELECT * FROM %s WHERE %s = ?', $this->isoTableName, $this->iso2FieldName);
  18. $stmt = $this->connection->pdo->prepare($sql);
  19. $stmt->execute([$iso2]);
  20. return new IsoCodes($stmt->fetch(PDO::FETCH_ASSOC);
  21. }
  22. }
  1. 现在我们把注意力转回Application\I18n\Locale类。我们首先添加一些新的属性和类常数。
  1. const ERROR_UNABLE_TO_PARSE = 'ERROR: Unable to parse';
  2. const FALLBACK_CURRENCY = 'GBP';
  3. protected $currencyFormatter;
  4. protected $currencyLookup;
  5. protected $currencyCode;
  1. 我们添加新的方法,从locale字符串中获取国家代码。我们可以利用getRegion()方法,它来自于PHP Locale类(我们扩展了它)。为了以防万一,我们还添加了一个方法getCurrencyCode()
  1. public function getCountryCode()
  2. {
  3. return $this->getRegion($this->getLocaleCode());
  4. }
  5. public function getCurrencyCode()
  6. {
  7. return $this->currencyCode;
  8. }
  1. 与格式化数字一样,我们定义一个getCurrencyFormatter(I),就像我们定义getNumberFormatter()一样(如前所示)。注意,$currencyFormatter是用NumberFormatter定义的,但第二个参数不同。
  1. public function getCurrencyFormatter()
  2. {
  3. if (!$this->currencyFormatter) {
  4. $this->currencyFormatter = new NumberFormatter($this->getLocaleCode(), NumberFormatter::CURRENCY);
  5. }
  6. return $this->currencyFormatter;
  7. }
  1. 然后,如果已经定义了查询类,我们就在类的构造函数中添加货币代码查询。
  1. public function __construct($localeString = NULL, IsoCodesInterface $currencyLookup = NULL)
  2. {
  3. // add this to the existing code:
  4. $this->currencyLookup = $currencyLookup;
  5. if ($this->currencyLookup) {
  6. $this->currencyCode = $this->currencyLookup->getCurrencyCodeFromIso2CountryCode($this->getCountryCode())->currency_code;
  7. } else {
  8. $this->currencyCode = self::FALLBACK_CURRENCY;
  9. }
  10. }
  1. 然后添加适当的货币格式和解析方法。注意,解析货币与解析数字不同,如果解析操作不成功,将返回FALSE
  1. public function formatCurrency($currency)
  2. {
  3. return $this->getCurrencyFormatter()->formatCurrency($currency, $this->currencyCode);
  4. }
  5. public function parseCurrency($string)
  6. {
  7. $result = $this->getCurrencyFormatter()->parseCurrency($string, $this->currencyCode);
  8. return ($result) ? $result : self::ERROR_UNABLE_TO_PARSE;
  9. }

如何运行…

如前几个要点中所涉及的那样,创建以下类。

Class 讨论要点
Application\I18n\IsoCodes 3
Application\I18n\IsoCodesInterface 4
Application\I18n\IsoCodesDb 5

为便于说明,我们假设我们有一个已填充的MySQL数据库表`iso_country_codes`,其结构如下。

  1. CREATE TABLE `iso_country_codes` (
  2. `name` varchar(128) NOT NULL,
  3. `iso2` varchar(2) NOT NULL,
  4. `iso3` varchar(3) NOT NULL,
  5. `iso_numeric` int(11) NOT NULL AUTO_INCREMENT,
  6. `iso_3166` varchar(32) NOT NULL,
  7. `currency_name` varchar(32) DEFAULT NULL,
  8. `currency_code` char(3) DEFAULT NULL,
  9. `currency_number` int(4) DEFAULT NULL,
  10. PRIMARY KEY (`iso_numeric`)
  11. ) ENGINE=InnoDB AUTO_INCREMENT=895 DEFAULT CHARSET=utf8;

Application\I18n\Locale类中进行添加,如前面第6至9点中所讨论的那样。然后你可以创建一个chap_08_formatting_currency.php文件,该文件设置了自动加载并使用了相应的类。

  1. <?php
  2. define('DB_CONFIG_FILE', __DIR__ . '/../config/db.config.php');
  3. require __DIR__ . '/../Application/Autoload/Loader.php';
  4. Application\Autoload\Loader::init(__DIR__ . '/..');
  5. use Application\I18n\Locale;
  6. use Application\I18n\IsoCodesDb;
  7. use Application\Database\Connection;
  8. use Application\I18n\Locale;

接下来,我们创建ConnectionIsoCodesDb类的实例。

  1. $connection = new Connection(include DB_CONFIG_FILE);
  2. $isoLookup = new IsoCodesDb($connection, 'iso_country_codes', 'iso2');

在这个例子中,创建两个Locale实例,一个用于英国,另一个用于法国。你也可以指定一个大的数量来用于测试。

  1. $localeFr = new Locale('fr-FR', $isoLookup);
  2. $localeUk = new Locale('en_GB', $isoLookup);
  3. $number = 1234567.89;
  4. ?>

最后,你可以将formatCurrency()parseCurrency()方法包在适当的HTML显示逻辑中,并查看结果。这里是最终的输出。

按地区处理货币 - 图1

更多…

最新的货币代码列表由ISO(国际标准组织)维护。你可以用XML或XLS(即微软Excel电子表格格式)获得这份清单。这里是可以找到这些清单的网页:http://www.currency-iso.org/en/home/tables/table-a1.html