创建组件

如果你有一些代码,看上去可以被复用,但是你不知道它是一个行为、小组件还是其它东西,很有可能它是一个组件。一个组件应该是继承了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格式的响应解析出汇率,并返回一个目标汇率:

  1. 在你的应用结构中创建components文件夹。
  2. 使用如下interface创建组件类例子:
<?php
namespace app\components;
use yii\base\Component;
class Exchange extends Component
{
    public function getRate($source, $destination, $date = null)
    {
    }
}
  1. 实现这个组件的功能:
<?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];
    }
}
  1. 附加这个组件到你的config/console.php或者config/web.php配置文件中:
'components' => [
    'cache' => [
        'class' => 'yii\caching\FileCache',
    ],
    'exchange' => [
        'class' => 'app\components\Exchange',
        'enableCaching' => true,
    ],
    // ...
    db' => $db,
],
  1. 现在,我们可以直接使用一个新的组件,或者使用get方法:
echo \Yii::$app->exchange->getRate('USD', 'EUR');
echo \Yii::$app->get('exchange')->getRate('USD', 'EUR', '2014-04-12');
  1. 创建一个实例控制台控制器:
<?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;
    }
}
  1. 现在尝试运行任何命令:
$ ./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方法,你可以使用如下步骤:

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

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

<?= \yii\grid\GridView::widget([
    'dataProvider' => $dataProvider,
    'columns' => [
        'id',
        'created_at:datetime',
        'title',
        'value:number',
    ],
]) ?>
  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类的源代码,参考创建帮助类小节。

results matching ""

    No results matching ""