创建组件

如果你有一些代码,看上去可以被复用,但是你不知道它是一个行为、小组件还是其它东西,很有可能它是一个组件。一个组件应该是继承了yii\base\Component类。然后,这个组件可以被附加到应用上,并使用配置文件中的components部分进行配置。这就是同只是使用纯PHP类相比最主要的优点。此外,我们可以得到行为、事件、getter、setter的支持。

在我们的例子中,我们将会实现一个简单的交换应用组件,它能从http://fixer.io获取最新的汇率,将它附加在应用上,并使用它。

准备

按照官方指南http://www.yiiframework.com/doc-2.0/guide-start-installation.html的描述,使用Composer包管理器创建一个新的yii2-app-basic应用。

如何做…

为了得到汇率,我们的组件应该发送一个HTTP GET请求到一个服务地址上,例如http://api.fixer.io/2016-05-14?base=USD

这个服务会返回所有支持的汇率在最近一天的情况:

  1. {
  2. "base":"USD",
  3. "date":"2016-05-13",
  4. "rates": {
  5. "AUD":1.3728,
  6. "BGN":1.7235,
  7. ...
  8. "ZAR":15.168,
  9. "EUR":0.88121
  10. }
  11. }

这个组件应该从JSON格式的响应解析出汇率,并返回一个目标汇率:

  1. 在你的应用结构中创建components文件夹。
  2. 使用如下interface创建组件类例子:
  1. <?php
  2. namespace app\components;
  3. use yii\base\Component;
  4. class Exchange extends Component
  5. {
  6. public function getRate($source, $destination, $date = null)
  7. {
  8. }
  9. }
  1. 实现这个组件的功能:
  1. <?php
  2. namespace app\components;
  3. use yii\base\Component;
  4. use yii\base\InvalidConfigException;
  5. use yii\base\InvalidParamException;
  6. use yii\caching\Cache;
  7. use yii\di\Instance;
  8. use yii\helpers\Json;
  9. class Exchange extends Component
  10. {
  11. /**
  12. * @var string remote host
  13. */
  14. public $host = 'http://api.fixer.io';
  15. /**
  16. * @var bool cache results or not
  17. */
  18. public $enableCaching = false;
  19. /**
  20. * @var string|Cache component ID
  21. */
  22. public $cache = 'cache';
  23. public function init()
  24. {
  25. if (empty($this->host)) {
  26. throw new InvalidConfigException('Host must be set.');
  27. }
  28. if ($this->enableCaching) {
  29. $this->cache = Instance::ensure($this->cache,
  30. Cache::className());
  31. }
  32. parent::init();
  33. }
  34. public function getRate($source, $destination, $date = null)
  35. {
  36. $this->validateCurrency($source);
  37. $this->validateCurrency($destination);
  38. $date = $this->validateDate($date);
  39. $cacheKey = $this->generateCacheKey($source,
  40. $destination, $date);
  41. if (!$this->enableCaching || ($result =
  42. $this->cache->get($cacheKey)) === false) {
  43. $result = $this->getRemoteRate($source,
  44. $destination, $date);
  45. if ($this->enableCaching) {
  46. $this->cache->set($cacheKey, $result);
  47. }
  48. }
  49. return $result;
  50. }
  51. private function getRemoteRate($source, $destination, $date)
  52. {
  53. $url = $this->host . '/' . $date . '?base=' . $source;
  54. $response = Json::decode(file_get_contents($url));
  55. if (!isset($response['rates'][$destination])) {
  56. throw new \RuntimeException('Rate not found.');
  57. }
  58. return $response['rates'][$destination];
  59. }
  60. private function validateCurrency($source)
  61. {
  62. if (!preg_match('#^[A-Z]{3}$#s', $source)) {
  63. throw new InvalidParamException('Invalid currency format.');
  64. }
  65. }
  66. private function validateDate($date)
  67. {
  68. if (!empty($date) &&
  69. !preg_match('#\d{4}\-\d{2}-\d{2}#s', $date)) {
  70. throw new InvalidParamException('Invalid date format.');
  71. }
  72. if (empty($date)) {
  73. $date = date('Y-m-d');
  74. }
  75. return $date;
  76. }
  77. private function generateCacheKey($source, $destination,
  78. $date)
  79. {
  80. return [__CLASS__, $source, $destination, $date];
  81. }
  82. }
  1. 附加这个组件到你的config/console.php或者config/web.php配置文件中:
  1. 'components' => [
  2. 'cache' => [
  3. 'class' => 'yii\caching\FileCache',
  4. ],
  5. 'exchange' => [
  6. 'class' => 'app\components\Exchange',
  7. 'enableCaching' => true,
  8. ],
  9. // ...
  10. db' => $db,
  11. ],
  1. 现在,我们可以直接使用一个新的组件,或者使用get方法:
  1. echo \Yii::$app->exchange->getRate('USD', 'EUR');
  2. echo \Yii::$app->get('exchange')->getRate('USD', 'EUR', '2014-04-12');
  1. 创建一个实例控制台控制器:
  1. <?php
  2. namespace app\commands;
  3. use yii\console\Controller;
  4. class ExchangeController extends Controller
  5. {
  6. public function actionTest($currency, $date = null)
  7. {
  8. echo \Yii::$app->exchange->getRate('USD', $currency,
  9. $date) . PHP_EOL;
  10. }
  11. }
  1. 现在尝试运行任何命令:
  1. $ ./yii exchange/test EUR
  2. > 0.90196
  3. $ ./yii exchange/test EUR 2015-11-24
  4. > 0.93888
  5. $ ./yii exchange/test OTHER
  6. > Exception 'yii\base\InvalidParamException' with message 'Invalid currency format.'
  7. $ ./yii exchange/test EUR 2015/24/11
  8. Exception 'yii\base\InvalidParamException' with message 'Invalid date format.'
  9. $ ./yii exchange/test ASD
  10. > Exception 'RuntimeException' with message 'Rate not found.'

作为结果,成功的话,你可以看到汇率值;如果失败你会看到指定的异常错误。此外创建你自己的组件,你可以做的更多。

覆盖已经存在的应用组件

大部分情况下,没有必须创建你自己的应用组件,因为其它类型的扩展,例如小组件或者行为,涵盖了几乎所有类型的可复用代码。但是,复写核心框架组件是一个常用的实践,并且可以被用于自定义框架的行为,用于特殊的需求,而不需要修改核心代码。

例如,为了能够使用Yii::app()->formatter->asNumber($value)格式化数字,而不是在创建帮助类小节中的NumberHelper::format方法,你可以使用如下步骤:

  1. 继承yii\i18n\Formatter组件:
  1. <?php
  2. namespace app\components;
  3. class Formatter extends \yii\i18n\Formatter
  4. {
  5. public function asNumber($value, $decimal = 2)
  6. {
  7. return number_format($value, $decimal, '.', ',');
  8. }
  9. }
  1. 复写内置formatter组件的类:
  1. 'components' => [
  2. // ...
  3. formatter => [
  4. 'class' => 'app\components\Formatter,
  5. ],
  6. // ...
  7. ],
  1. 现在,我们可以直接使用这个方法:
  1. echo Yii::app()->formatter->asNumber(1534635.2, 3);

或者,它可以被用做一个新的格式,用在GridViewDetailView小组件中:

  1. <?= \yii\grid\GridView::widget([
  2. 'dataProvider' => $dataProvider,
  3. 'columns' => [
  4. 'id',
  5. 'created_at:datetime',
  6. 'title',
  7. 'value:number',
  8. ],
  9. ]) ?>
  1. 此外,你可以扩展任何已有的组件,而不需要修改源代码。

工作原理…

为了能附加一个组件到一个应用中,它可以从yii\base\Component进行继承。附加非常简单,只需要添加一个新的数组到配置的组件部分。这里class的值指定了组件的类,其它值用于设置这个组件的公共属性和setter方法。

继承它自己非常直接:我们包裹了http://api.fixer.io调用到一个非常舒适的API中,并可以进行校验和缓存。我们可以通过Yii::$app和组件的名称访问我们的类。在我们的例子中,它会是Yii::$app->exchange

参考

欲了解关于组件的官方信息,参考http://www.yiiframework.com/doc-2.0/guideconcept-components.html

对于NumberHelper类的源代码,参考创建帮助类小节。