创建组件
如果你有一些代码,看上去可以被复用,但是你不知道它是一个行为、小组件还是其它东西,很有可能它是一个组件。一个组件应该是继承了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创建组件类例子:
<?phpnamespace app\components;use yii\base\Component;class Exchange extends Component{public function getRate($source, $destination, $date = null){}}
- 实现这个组件的功能:
<?phpnamespace 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');
- 创建一个实例控制台控制器:
<?phpnamespace 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/11Exception '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组件:
<?phpnamespace 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类的源代码,参考创建帮助类小节。
