创建组件
如果你有一些代码,看上去可以被复用,但是你不知道它是一个行为、小组件还是其它东西,很有可能它是一个组件。一个组件应该是继承了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
。
这个服务会返回所有支持的汇率在最近一天的情况:
{
"base":"USD",
"date":"2016-05-13",
"rates": {
"AUD":1.3728,
"BGN":1.7235,
...
"ZAR":15.168,
"EUR":0.88121
}
}
这个组件应该从JSON格式的响应解析出汇率,并返回一个目标汇率:
- 在你的应用结构中创建
components
文件夹。 - 使用如下interface创建组件类例子:
<?php
namespace app\components;
use yii\base\Component;
class Exchange extends Component
{
public function getRate($source, $destination, $date = null)
{
}
}
- 实现这个组件的功能:
<?php
namespace app\components;
use yii\base\Component;
use yii\base\InvalidConfigException;
use yii\base\InvalidParamException;
use yii\caching\Cache;
use yii\di\Instance;
use yii\helpers\Json;
class Exchange extends Component
{
/**
* @var string remote host
*/
public $host = 'http://api.fixer.io';
/**
* @var bool cache results or not
*/
public $enableCaching = false;
/**
* @var string|Cache component ID
*/
public $cache = 'cache';
public function init()
{
if (empty($this->host)) {
throw new InvalidConfigException('Host must be set.');
}
if ($this->enableCaching) {
$this->cache = Instance::ensure($this->cache,
Cache::className());
}
parent::init();
}
public function getRate($source, $destination, $date = null)
{
$this->validateCurrency($source);
$this->validateCurrency($destination);
$date = $this->validateDate($date);
$cacheKey = $this->generateCacheKey($source,
$destination, $date);
if (!$this->enableCaching || ($result =
$this->cache->get($cacheKey)) === false) {
$result = $this->getRemoteRate($source,
$destination, $date);
if ($this->enableCaching) {
$this->cache->set($cacheKey, $result);
}
}
return $result;
}
private function getRemoteRate($source, $destination, $date)
{
$url = $this->host . '/' . $date . '?base=' . $source;
$response = Json::decode(file_get_contents($url));
if (!isset($response['rates'][$destination])) {
throw new \RuntimeException('Rate not found.');
}
return $response['rates'][$destination];
}
private function validateCurrency($source)
{
if (!preg_match('#^[A-Z]{3}$#s', $source)) {
throw new InvalidParamException('Invalid currency format.');
}
}
private function validateDate($date)
{
if (!empty($date) &&
!preg_match('#\d{4}\-\d{2}-\d{2}#s', $date)) {
throw new InvalidParamException('Invalid date format.');
}
if (empty($date)) {
$date = date('Y-m-d');
}
return $date;
}
private function generateCacheKey($source, $destination,
$date)
{
return [__CLASS__, $source, $destination, $date];
}
}
- 附加这个组件到你的
config/console.php
或者config/web.php
配置文件中:
'components' => [
'cache' => [
'class' => 'yii\caching\FileCache',
],
'exchange' => [
'class' => 'app\components\Exchange',
'enableCaching' => true,
],
// ...
db' => $db,
],
- 现在,我们可以直接使用一个新的组件,或者使用
get
方法:
echo \Yii::$app->exchange->getRate('USD', 'EUR');
echo \Yii::$app->get('exchange')->getRate('USD', 'EUR', '2014-04-12');
- 创建一个实例控制台控制器:
<?php
namespace app\commands;
use yii\console\Controller;
class ExchangeController extends Controller
{
public function actionTest($currency, $date = null)
{
echo \Yii::$app->exchange->getRate('USD', $currency,
$date) . PHP_EOL;
}
}
- 现在尝试运行任何命令:
$ ./yii exchange/test EUR
> 0.90196
$ ./yii exchange/test EUR 2015-11-24
> 0.93888
$ ./yii exchange/test OTHER
> Exception 'yii\base\InvalidParamException' with message 'Invalid currency format.'
$ ./yii exchange/test EUR 2015/24/11
Exception 'yii\base\InvalidParamException' with message 'Invalid date format.'
$ ./yii exchange/test ASD
> Exception 'RuntimeException' with message 'Rate not found.'
作为结果,成功的话,你可以看到汇率值;如果失败你会看到指定的异常错误。此外创建你自己的组件,你可以做的更多。
覆盖已经存在的应用组件
大部分情况下,没有必须创建你自己的应用组件,因为其它类型的扩展,例如小组件或者行为,涵盖了几乎所有类型的可复用代码。但是,复写核心框架组件是一个常用的实践,并且可以被用于自定义框架的行为,用于特殊的需求,而不需要修改核心代码。
例如,为了能够使用Yii::app()->formatter->asNumber($value)
格式化数字,而不是在创建帮助类小节中的NumberHelper::format
方法,你可以使用如下步骤:
- 继承
yii\i18n\Formatter
组件:
<?php
namespace app\components;
class Formatter extends \yii\i18n\Formatter
{
public function asNumber($value, $decimal = 2)
{
return number_format($value, $decimal, '.', ',');
}
}
- 复写内置
formatter
组件的类:
'components' => [
// ...
formatter => [
'class' => 'app\components\Formatter,
],
// ...
],
- 现在,我们可以直接使用这个方法:
echo Yii::app()->formatter->asNumber(1534635.2, 3);
或者,它可以被用做一个新的格式,用在GridView
和DetailView
小组件中:
<?= \yii\grid\GridView::widget([
'dataProvider' => $dataProvider,
'columns' => [
'id',
'created_at:datetime',
'title',
'value:number',
],
]) ?>
- 此外,你可以扩展任何已有的组件,而不需要修改源代码。
工作原理…
为了能附加一个组件到一个应用中,它可以从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
类的源代码,参考创建帮助类小节。