Commit 4f03b113 authored by shajiaiming's avatar shajiaiming

Merge branch 'master' into feature/ws_ticker

parents 0a844909 dfbac9fb
......@@ -33,6 +33,9 @@ class BaseController extends Controller
public function fzmCrossHeader()
{
$this->lang = \Yii::$app->request->headers->get('lang') ?? 'zh-CN';
if ('en' == strtolower($this->lang)){
$this->lang = 'en-US';
}
}
public function beforeAction($action)
......
<?php
namespace api\controllers;
use Yii;
use api\base\BaseController;
use common\models\psources\CoinClientParams;
class ClientParamsController extends BaseController
{
public function actionIndex()
{
$code = 0;
$msg = 'success';
$data = CoinClientParams::find()->asArray()->all();
if(empty($data)){
$msg = '数据不存在';
$data = null;
$code = -1;
goto doEnd;
}
foreach ($data as &$val) {
if(strpos( $val['url'], ',') !== false) {
$val['url'] = explode(',', $val['url']);
}
}
doEnd :
return ['code' => $code, 'data' => $data, 'msg' => $msg];
}
}
\ No newline at end of file
<?php
namespace api\controllers;
use Yii;
use api\base\BaseController;
use common\models\psources\LockEs;
use common\models\psources\LockCreator;
class LockController extends BaseController
{
public function actionLockCreator()
{
$lock_creator = new LockCreator();
$lock_creator->primaryKey = 1;//primaryKey 定义 _id
$lock_creator->creator = 'fei';
$lock_creator->create = [
'means' => 'fix_amount',
'fix_amount' => [
'period' => 2400,
'amount' => 153500
],
'left_proportion' => [
'period' => 2400,
'tenThousandth' => 153500
],
'total_count' => 4600000,
'start_time' => 6430000,
'asset_symbol' => 'bty',
'asset_exec' => 'coins',
];
$lock_creator->unfreeze_id = 'the-id';
$lock_creator->block = [
'hash' => '',
'height' => 2,
'ts' => 6632560,
'index' => 2
];
$lock_creator->action_type = 'create'; //create/withdraw/terminate
$lock_creator->beneficiary = 'xuan';
$lock_creator->success = true;
$lock_creator->withdraw = [
'amount' => 2000021
];
$lock_creator->terminate = [
'amount_left' => 2024121,
'amount_back' => 1536584
];
var_dump($lock_creator->save());
exit;
}
public function actionSearch()
{
$data = null;
$msg = 'ok';
$code = 0;
$action_type = Yii::$app->request->get('action_type', '');
$beneficiary = Yii::$app->request->get('beneficiary','');
if(empty($action_type) || empty($beneficiary)){
$msg = '参数错误';
$code = -1;
goto doEnd;
}
$lock_creator = new LockEs();
$query_arr = [
"bool" => [
"must" => [
['match' => ['action_type' => $action_type]],
['match' => ['beneficiary' => $beneficiary]],
['match' => ['success' => true]],
]
],
];
$resp = $lock_creator::find()->query($query_arr)->asArray()->limit(100)->all();
if(empty($resp)){
$msg = '数据为空';
$code = -1;
goto doEnd;
}
foreach ($resp as $key => $val){
$data[] = [
'block' => [
'height' => $val['_source']['block']['height'],
'ts' => $val['_source']['block']['ts'],
'hash' => $val['_source']['block']['hash'],
'index' => $val['_source']['block']['index'],
'send' => $val['_source']['block']['send'],
'txHash' => $val['_source']['block']['txHash'],
],
'creator' => $val['_source']['creator'],
'beneficiary' => $val['_source']['beneficiary'],
'unfreeze_id' => $val['_source']['unfreeze_id'],
'success' => $val['_source']['success'],
'action_type' => $val['_source']['action_type'],
'create' => [
'start_time' => $val['_source']['create']['start_time'],
'asset_exec' => $val['_source']['create']['asset_exec'],
'asset_symbol' => $val['_source']['create']['asset_symbol'],
'total_count' => $val['_source']['create']['total_count'],
'means' => $val['_source']['create']['means'],
// 'left_proportion' => [
// 'period' => $val['_source']['create']['left_proportion']['period'],
// 'tenThousandth' => $val['_source']['create']['left_proportion']['tenThousandth']
// ],
]
];
}
doEnd :
return ['code' => $code, 'data' => $data, 'msg' => $msg];;
}
public function actionDeleteCreator()
{
$lock_creator = new LockCreator();
$lock_creator_v2 = new LockEs();
var_dump($lock_creator->deleteIndex(),$lock_creator_v2->deleteIndex());
exit;
}
public function actionLockBlock()
{
}
public function actionLockBeneficiary()
{
}
}
\ No newline at end of file
......@@ -93,12 +93,13 @@ class CoinController extends BaseController
$data['optional_name'] = strtoupper($data['optional_name']);
}
$data['name'] = strtoupper(trim($data['name']));
$data['platform'] = strtolower($data['platform']);
$data['platform'] = $data['platform'];
$data['chain'] = strtoupper($data['chain']);
$lang = [
'zh-CN',
'en-US',
'ja'
'ja',
'ko'
];
$nickname_arr = $data['nickname'];
$introduce_arr = $data['introduce'];
......@@ -147,7 +148,7 @@ class CoinController extends BaseController
$req = Yii::$app->request;
$data = $req->post();
$data['name'] = strtoupper($data['name']);
$data['platform'] = strtolower($data['platform']);
$data['platform'] = $data['platform'];
$data['chain'] = strtoupper($data['chain']);
if (isset($data['optional_name'])) {
$data['optional_name'] = strtoupper($data['optional_name']);
......@@ -170,7 +171,8 @@ class CoinController extends BaseController
$lang = [
'zh-CN',
'en-US',
'ja'
'ja',
'ko'
];
$nickname_arr = $data['nickname'];
$introduce_arr = $data['introduce'];
......
......@@ -43,7 +43,8 @@ class ExchangeBusiness
14 => 'Zt',
15 => 'Tsc',
16 => 'Binance',
17 => 'Ceohk'
17 => 'Ceohk',
18 => 'Biki'
];
/**
......@@ -56,7 +57,7 @@ class ExchangeBusiness
public static function getquatation($tag = 'btc')
{
$coin_quotation_disable_items = Yii::$app->params['coin_quotation_disable_items'];
if (strtoupper($tag) == 'CCNY') {
if (strtoupper($tag) == 'CCNY' || strtoupper($tag) == 'CNYT') {
$exchange = ExchangeFactory::createExchange("Bty");
$rate = $exchange->getTicker("BTY", "USDT");
$rate = (float)$rate['rmb'] / $rate['last'];
......@@ -66,6 +67,16 @@ class ExchangeBusiness
$quotation['last'] = (float)sprintf("%0.4f", $quotation['rmb'] / $rate);
goto doEnd;
}
if (strtoupper($tag) == 'YPLUS') {
$quotation = [
'low' => 10,
'high' => 10,
'last' => 10,
'rmb' => 10,
];
goto doEnd;
}
if (strtoupper($tag) == 'BOSS') {
$quotation = [
'low' => 2000,
......@@ -86,7 +97,7 @@ class ExchangeBusiness
goto doEnd;
}
if (strtoupper($tag) == 'RYH' || strtoupper($tag) == 'CNDT' || strtoupper($tag) == 'WL' || strtoupper($tag) == 'ETS' || strtoupper($tag) == 'LIMS' || strtoupper($tag) == 'AT' || strtoupper($tag) == 'BTJ') {
if (strtoupper($tag) == 'GM' || strtoupper($tag) == 'BSTC' || strtoupper($tag) == 'RYH' || strtoupper($tag) == 'CNDT' || strtoupper($tag) == 'WL' || strtoupper($tag) == 'ETS' || strtoupper($tag) == 'LIMS' || strtoupper($tag) == 'AT' || strtoupper($tag) == 'BTJ') {
$quotation = [
'low' => 0,
'high' => 0,
......@@ -181,6 +192,12 @@ class ExchangeBusiness
goto doEnd;
}
if (in_array(strtoupper($tag), ['KPC8'])) {
$exchange = ExchangeFactory::createExchange("Biki");
$quotation = $exchange->getTicker('KPC8', 'USDT');
goto doEnd;
}
if (in_array(strtoupper($tag), ['SJPY'])) {
$exchange = ExchangeFactory::createExchange("Boc");
$quotation = $exchange->getTicker('CNY', 'JPY');
......@@ -250,7 +267,7 @@ class ExchangeBusiness
$exchange = ExchangeFactory::createExchange("Go");
$rate = $exchange->getTicker("CNY", "USD");
$cny_usd_rate = 1 / $rate['last'];
if (in_array(strtoupper($tag), ['FOLI', 'CIC'])) {
if (in_array(strtoupper($tag), ['FOLI', 'CIC', 'KPC8'])) {
$quotation['usd'] = (float)sprintf("%0.4f", $quotation['last']);
$quotation['rmb'] = (float)sprintf("%0.4f", $quotation['last'] / $cny_usd_rate);
} else if (in_array(strtoupper($tag), ['SUSD'])) {
......
<?php
namespace common\models\psources;
use Yii;
use common\core\BaseActiveRecord;
class CoinClientParams extends BaseActiveRecord
{
public static function getDb()
{
return Yii::$app->get('p_sources');
}
public static function tableName()
{
return '{{%coin_client_params}}';
}
//定义场景
const SCENARIOS_CREATE = 'create';
const SCENARIOS_UPDATE = 'update';
public function rules() {
return [
[['type','url'], 'required'],
];
}
public function scenarios() {
$scenarios = [
self:: SCENARIOS_CREATE => ['type','url'],
];
return array_merge( parent:: scenarios(), $scenarios);
}
}
<?php
namespace app\models;
use yii\elasticsearch\ActiveRecord;
class LockBeneficiary extends ActiveRecord
{
// 索引名相当于库名
public static function index()
{
return 'wangjian';
}
// 类别名相当于表名
public static function type()
{
return 'test';
}
// 属性
public function attributes()
{
$mapConfig = self::mapConfig();
return array_keys($mapConfig['properties']);
}
/**
*[mapConfig mapping配置]
*返回这个模型的映射
*/
public static function mapConfig()
{
return [
'properties' => [
'beneficiary' => ['type' => 'string'],
'terminate' => [
'type' => 'nested',
'properties' => [
'amount_left' => ['type' => 'long'],
'amount_back' => ['type' => 'long']
]
],
'success' => ['type' => 'long'],
'block' => [
'type' => 'nested',
'properties' => [
'hash' => ['type' => 'string'],
'index' => ['type' => 'long'],
'height' => ['type' => 'long'],
'ts' => ['type' => 'long']
]
],
'unfreeze_id' => ['type' => 'long'],
'action_type' => ['type' => 'long'],
'creator' => ['type' => 'string']
]
];
}
public static function mapping()
{
return [
static::type() => self::mapConfig(),
];
}
/**
* 设置(更新)此模型的映射
*/
public static function updateMapping()
{
$db = self::getDb();
$command = $db->createCommand();
if (!$command->indexExists(self::index())) {
$command->createIndex(self::index());
}
$command->setMapping(self::index(), self::type(), self::mapping());
}
//获取此模型的映射
public static function getMapping()
{
$db = self::getDb();
$command = $db->createCommand();
return $command->getMapping();
}
}
\ No newline at end of file
<?php
namespace common\models\psources;
use yii\elasticsearch\ActiveRecord;
class LockBlock extends ActiveRecord
{
// 索引名相当于库名
public static function index()
{
return 'wangjian';
}
// 类别名相当于表名
public static function type()
{
return 'test';
}
// 属性
public function attributes()
{
$mapConfig = self::mapConfig();
return array_keys($mapConfig['properties']);
}
/**
*[mapConfig mapping配置]
*返回这个模型的映射
*/
public static function mapConfig()
{
return [
'properties' => [
'block' => [
'type' => 'nested',
'properties' => [
'index' => ['type' => 'long'],
'height' => ['type' => 'long'],
'ts' => ['type' => 'long'],
'hash' => ['type' => 'string']
]
],
'beneficiary' => ['type' => 'string'],
'unfreeze_id' => ['type' => 'long'],
'creator' => ['type' => 'string'],
'action_type' => ['type' => 'long'],
'success' => ['type' => 'long'],
'withdraw' => [
'type' => 'nested',
'properties' => [
'amount' => ['type' => 'long']
]
]
]
];
}
public static function mapping()
{
return [
static::type() => self::mapConfig(),
];
}
/**
* 设置(更新)此模型的映射
*/
public static function updateMapping()
{
$db = self::getDb();
$command = $db->createCommand();
if (!$command->indexExists(self::index())) {
$command->createIndex(self::index());
}
$command->setMapping(self::index(), self::type(), self::mapping());
}
//获取此模型的映射
public static function getMapping()
{
$db = self::getDb();
$command = $db->createCommand();
return $command->getMapping();
}
}
\ No newline at end of file
<?php
namespace common\models\psources;
use yii\elasticsearch\ActiveRecord;
class LockCreator extends ActiveRecord
{
// 索引名相当于库名
public static function index()
{
return 'suo_cang';
}
// 类别名相当于表名
public static function type()
{
return 'lock_creator';
}
// 属性
public function attributes()
{
$mapConfig = self::mapConfig();
return array_keys($mapConfig['properties']);
}
/**
*[mapConfig mapping配置]
*返回这个模型的映射
*/
public static function mapConfig()
{
return [
'properties' => [
'creator' => ['type' => 'string'],
'create' => [
'type' => 'nested',
'properties' => [
'means' => ['type' => 'long'],
'fix_amount' => [
'type' => 'nested',
'properties' => [
'period' => ['type' => 'long'],
'amount' => ['type' => 'long']
]
],
'total_count' => ['type' => 'long'],
'start_time' => ['type' => 'long'],
'asset_symbol' => ['type' => 'string'],
'asset_exec' => ['type' => 'string']
]
],
'unfreeze_id' => ['type' => 'string'],
'block' => [
'type' => 'nested',
'properties' => [
'hash' => ['type' => 'string'],
'height' => ['type' => 'long'],
'ts' => ['type' => 'long'],
'index' => ['type' => 'long']
]
],
'action_type' => ['type' => 'long'],
'beneficiary' => ['type' => 'string'],
'success' => ['type' => 'string']
]
];
}
public static function mapping()
{
return [
static::type() => self::mapConfig(),
];
}
/**
* 设置(更新)此模型的映射
*/
public static function updateMapping()
{
$db = self::getDb();
$command = $db->createCommand();
if (!$command->indexExists(self::index())) {
$command->createIndex(self::index());
}
$command->setMapping(self::index(), self::type(), self::mapping());
}
//获取此模型的映射
public static function getMapping()
{
$db = self::getDb();
$command = $db->createCommand();
return $command->getMapping();
}
/**
* Delete this model's index
*/
public static function deleteIndex()
{
$db = static::getDb();
$command = $db->createCommand();
$command->deleteIndex(static::index(), static::type());
}
public static function updateRecord($book_id, $columns)
{
try {
$record = self::get($book_id);
foreach ($columns as $key => $value) {
$record->$key = $value;
}
return $record->update();
} catch (\Exception $e) { //handle error here return false; } }
return false;
}
}
}
\ No newline at end of file
<?php
namespace common\models\psources;
use yii\elasticsearch\ActiveRecord;
class LockEs extends ActiveRecord
{
// 索引名相当于库名
public static function index()
{
return 'unfreeze_tx';
}
// 类别名相当于表名
public static function type()
{
return 'unfreeze';
}
// 属性
public function attributes()
{
$mapConfig = self::mapConfig();
return array_keys($mapConfig['properties']);
}
/**
*[mapConfig mapping配置]
*返回这个模型的映射
*/
public static function mapConfig()
{
return [
'properties' => [
'creator' => ['type' => 'keyword'],
'create' => [
'type' => 'object',
'properties' => [
'means' => ['type' => 'keyword'],
'fix_amount' => [
'type' => 'object',
'properties' => [
'period' => ['type' => 'long'],
'amount' => ['type' => 'long']
]
],
'left_proportion' => [
'type' => 'object',
'properties' => [
'period' => ['type' => 'long'],
'tenThousandth' => ['type' => 'long']
]
],
'total_count' => ['type' => 'long'],
'start_time' => ['type' => 'long'],
'asset_symbol' => ['type' => 'string'],
'asset_exec' => ['type' => 'string']
]
],
'unfreeze_id' => ['type' => 'keyword'],
'block' => [
'type' => 'object',
'properties' => [
'hash' => ['type' => 'keyword'],
'height' => ['type' => 'long'],
'ts' => ['type' => 'long'],
'index' => ['type' => 'long']
]
],
'action_type' => ['type' => 'long'],
'beneficiary' => ['type' => 'keyword'],
'success' => ['type' => 'boolean'],
'withdraw' => [
'type' => 'object',
'properties' => [
'amount' => 'long'
]
],
'terminate' => [
'type' => 'object',
'properties' => [
'amount_left' => 'long',
'amount_back' => 'long'
]
]
]
];
}
public static function mapping()
{
return [
static::type() => self::mapConfig(),
];
}
/**
* 设置(更新)此模型的映射
*/
public static function updateMapping()
{
$db = self::getDb();
$command = $db->createCommand();
if (!$command->indexExists(self::index())) {
$command->createIndex(self::index());
}
$command->setMapping(self::index(), self::type(), self::mapping());
}
//获取此模型的映射
public static function getMapping()
{
$db = self::getDb();
$command = $db->createCommand();
return $command->getMapping();
}
/**
* Delete this model's index
*/
public static function deleteIndex()
{
$db = static::getDb();
$command = $db->createCommand();
$command->deleteIndex(static::index(), static::type());
}
public static function updateRecord($book_id, $columns)
{
try {
$record = self::get($book_id);
foreach ($columns as $key => $value) {
$record->$key = $value;
}
return $record->update();
} catch (\Exception $e) { //handle error here return false; } }
return false;
}
}
}
\ No newline at end of file
<?php
namespace common\service\exchange;
use linslin\yii2\curl\Curl;
class Biki extends Exchange implements ExchangeInterface
{
protected $supported_symbol = 'supported_symbol_biki';
protected $quotation_prefix = 'quotation_biki_';
protected $base_url = 'https://openapi.biki.com/open/api/get_ticker?symbol=kpc8usdt';
public function symbolExists($tag = 'KPC8', $aim = "USDT")
{
$supported = $this->redis->smembers($this->supported_symbol);
if (is_array($supported) && in_array($this->formatSymbol($tag, $aim), $supported)) {
return true;
}
return false;
}
/**
* 转化交易对为请求变量
*
* @param string $tag
* @param string $aim
* @return mixed
*/
public function formatSymbol($tag = 'KPC8', $aim = 'USDT')
{
return strtoupper($tag .'_'. $aim);
}
/**
* 保存支持的交易对到redis数据库,使用crontab定时更新
*
* @return mixed|void
*/
public function setSupportedSymbol()
{
$this->redis->sadd($this->supported_symbol, 'KPC8_USDT');
}
/**
* 更新交易对行情保存到redis,使用crontab定时更新
*
* @return mixed|void
*/
public function setQuotation()
{
$curl = new Curl();
$content = $curl->get($this->base_url, false);
if (is_array($content) && isset($content['data'])) {
$data = $content['data'];
$key = $this->quotation_prefix . 'KPC8_USDT';
$this->redis->hmset($key, 'low', $data['low'], 'high', $data['high'], 'last', $data['last']);
$this->redis->sadd($this->supported_symbol, 'KPC8_USDT');
}
}
}
\ No newline at end of file
......@@ -23,7 +23,11 @@
"yiisoft/yii2-queue": "~2.0",
"linslin/yii2-curl": "*",
"voku/simple_html_dom": "^4.5",
<<<<<<< HEAD
"yiisoft/yii2-elasticsearch": "~2.0.0"
=======
"workerman/workerman": "^3.5"
>>>>>>> master
},
"require-dev": {
"yiisoft/yii2-debug": "~2.0.0",
......
......@@ -20,6 +20,7 @@ return array(
'yii\\queue\\' => array($vendorDir . '/yiisoft/yii2-queue/src'),
'yii\\gii\\' => array($vendorDir . '/yiisoft/yii2-gii/src'),
'yii\\faker\\' => array($vendorDir . '/yiisoft/yii2-faker'),
'yii\\elasticsearch\\' => array($vendorDir . '/yiisoft/yii2-elasticsearch'),
'yii\\debug\\' => array($vendorDir . '/yiisoft/yii2-debug'),
'yii\\composer\\' => array($vendorDir . '/yiisoft/yii2-composer'),
'yii\\bootstrap\\' => array($vendorDir . '/yiisoft/yii2-bootstrap/src'),
......
......@@ -33,6 +33,7 @@ class ComposerStaticInit33057934f3e7eaaa1ce2d53797277936
'yii\\queue\\' => 10,
'yii\\gii\\' => 8,
'yii\\faker\\' => 10,
'yii\\elasticsearch\\' => 18,
'yii\\debug\\' => 10,
'yii\\composer\\' => 13,
'yii\\bootstrap\\' => 14,
......@@ -190,6 +191,10 @@ class ComposerStaticInit33057934f3e7eaaa1ce2d53797277936
array (
0 => __DIR__ . '/..' . '/yiisoft/yii2-faker',
),
'yii\\elasticsearch\\' =>
array (
0 => __DIR__ . '/..' . '/yiisoft/yii2-elasticsearch',
),
'yii\\debug\\' =>
array (
0 => __DIR__ . '/..' . '/yiisoft/yii2-debug',
......
......@@ -4864,6 +4864,57 @@
]
},
{
"name": "yiisoft/yii2-elasticsearch",
"version": "2.0.5",
"version_normalized": "2.0.5.0",
"source": {
"type": "git",
"url": "https://github.com/yiisoft/yii2-elasticsearch.git",
"reference": "82d66d17543040dda3c64f299ae251658156c2c1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/yiisoft/yii2-elasticsearch/zipball/82d66d17543040dda3c64f299ae251658156c2c1",
"reference": "82d66d17543040dda3c64f299ae251658156c2c1",
"shasum": ""
},
"require": {
"ext-curl": "*",
"yiisoft/yii2": "~2.0.14"
},
"time": "2018-03-20T11:34:58+00:00",
"type": "yii2-extension",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"yii\\elasticsearch\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Carsten Brandt",
"email": "mail@cebe.cc"
}
],
"description": "Elasticsearch integration and ActiveRecord for the Yii framework",
"keywords": [
"active-record",
"elasticsearch",
"fulltext",
"search",
"yii2"
]
},
{
"name": "yiisoft/yii2-faker",
"version": "2.0.4",
"version_normalized": "2.0.4.0",
......
......@@ -300,4 +300,13 @@ return array (
'@linslin/yii2/curl' => $vendorDir . '/linslin/yii2-curl',
),
),
'yiisoft/yii2-elasticsearch' =>
array (
'name' => 'yiisoft/yii2-elasticsearch',
'version' => '2.0.5.0',
'alias' =>
array (
'@yii/elasticsearch' => $vendorDir . '/yiisoft/yii2-elasticsearch',
),
),
);
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\elasticsearch;
use yii\base\InvalidCallException;
use yii\base\InvalidConfigException;
use yii\db\ActiveQueryInterface;
/**
* ActiveDataProvider is an enhanced version of [[\yii\data\ActiveDataProvider]] specific to the ElasticSearch.
* It allows to fetch not only rows and total rows count, but full query results including aggregations and so on.
*
* Note: this data provider fetches result models and total count using single ElasticSearch query, so results total
* count will be fetched after pagination limit applying, which eliminates ability to verify if requested page number
* actually exist. Data provider disables [[yii\data\Pagination::validatePage]] automatically because of this.
*
* @property array $aggregations All aggregations results. This property is read-only.
* @property array $queryResults Full query results.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0.5
*/
class ActiveDataProvider extends \yii\data\ActiveDataProvider
{
/**
* @var array the full query results.
*/
private $_queryResults;
/**
* @param array $results full query results
*/
public function setQueryResults($results)
{
$this->_queryResults = $results;
}
/**
* @return array full query results
*/
public function getQueryResults()
{
if (!is_array($this->_queryResults)) {
$this->prepare();
}
return $this->_queryResults;
}
/**
* @return array all aggregations results
*/
public function getAggregations()
{
$results = $this->getQueryResults();
return isset($results['aggregations']) ? $results['aggregations'] : [];
}
/**
* Returns results of the specified aggregation.
* @param string $name aggregation name.
* @return array aggregation results.
* @throws InvalidCallException if requested aggregation does not present in query results.
*/
public function getAggregation($name)
{
$aggregations = $this->getAggregations();
if (!isset($aggregations[$name])) {
throw new InvalidCallException("Aggregation '{$name}' does not present.");
}
return $aggregations[$name];
}
/**
* @inheritdoc
*/
protected function prepareModels()
{
if (!$this->query instanceof Query) {
throw new InvalidConfigException('The "query" property must be an instance "' . Query::className() . '" or its subclasses.');
}
$query = clone $this->query;
if (($pagination = $this->getPagination()) !== false) {
// pagination fails to validate page number, because total count is unknown at this stage
$pagination->validatePage = false;
$query->limit($pagination->getLimit())->offset($pagination->getOffset());
}
if (($sort = $this->getSort()) !== false) {
$query->addOrderBy($sort->getOrders());
}
if (is_array(($results = $query->search($this->db)))) {
$this->setQueryResults($results);
if ($pagination !== false) {
$pagination->totalCount = $this->getTotalCount();
}
return $results['hits']['hits'];
}
$this->setQueryResults([]);
return [];
}
/**
* @inheritdoc
*/
protected function prepareTotalCount()
{
if (!$this->query instanceof Query) {
throw new InvalidConfigException('The "query" property must be an instance "' . Query::className() . '" or its subclasses.');
}
$results = $this->getQueryResults();
return isset($results['hits']['total']) ? (int)$results['hits']['total'] : 0;
}
/**
* @inheritdoc
*/
protected function prepareKeys($models)
{
$keys = [];
if ($this->key !== null) {
foreach ($models as $model) {
if (is_string($this->key)) {
$keys[] = $model[$this->key];
} else {
$keys[] = call_user_func($this->key, $model);
}
}
return $keys;
} elseif ($this->query instanceof ActiveQueryInterface) {
/* @var $class \yii\db\ActiveRecord */
$class = $this->query->modelClass;
$pks = $class::primaryKey();
if (count($pks) === 1) {
foreach ($models as $model) {
$keys[] = $model->primaryKey;
}
} else {
foreach ($models as $model) {
$kk = [];
foreach ($pks as $pk) {
$kk[$pk] = $model[$pk];
}
$keys[] = $kk;
}
}
return $keys;
} else {
return array_keys($models);
}
}
/**
* @inheritdoc
*/
public function refresh()
{
parent::refresh();
$this->_queryResults = null;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\elasticsearch;
use Yii;
use yii\base\InvalidConfigException;
use yii\test\BaseActiveFixture;
/**
* ActiveFixture represents a fixture for testing backed up by an [[modelClass|ActiveRecord class]] or an elastic search index.
*
* Either [[modelClass]] or [[index]] and [[type]] must be set. You should also provide fixture data in the file
* specified by [[dataFile]] or overriding [[getData()]] if you want to use code to generate the fixture data.
*
* When the fixture is being loaded, it will first call [[resetIndex()]] to remove any existing data in the index for the [[type]].
* It will then populate the index with the data returned by [[getData()]].
*
* After the fixture is loaded, you can access the loaded data via the [[data]] property. If you set [[modelClass]],
* you will also be able to retrieve an instance of [[modelClass]] with the populated data via [[getModel()]].
*
* @author Carsten Brandt <mail@cebe.cc>
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0.2
*/
class ActiveFixture extends BaseActiveFixture
{
/**
* @var Connection|string the DB connection object or the application component ID of the DB connection.
* After the DbFixture object is created, if you want to change this property, you should only assign it
* with a DB connection object.
*/
public $db = 'elasticsearch';
/**
* @var string the name of the index that this fixture is about. If this property is not set,
* the name will be determined via [[modelClass]].
* @see modelClass
*/
public $index;
/**
* @var string the name of the type that this fixture is about. If this property is not set,
* the name will be determined via [[modelClass]].
* @see modelClass
*/
public $type;
/**
* @var string|boolean the file path or path alias of the data file that contains the fixture data
* to be returned by [[getData()]]. If this is not set, it will default to `FixturePath/data/Index/Type.php`,
* where `FixturePath` stands for the directory containing this fixture class, `Index` stands for the elasticsearch [[index]] name
* and `Type` stands for the [[type]] associated with this fixture.
* You can set this property to be false to prevent loading any data.
*/
public $dataFile;
/**
* @inheritdoc
*/
public function init()
{
parent::init();
if (!isset($this->modelClass) && (!isset($this->index) || !isset($this->type))) {
throw new InvalidConfigException('Either "modelClass" or "index" and "type" must be set.');
}
/* @var $modelClass ActiveRecord */
$modelClass = $this->modelClass;
if ($this->index === null) {
$this->index = $modelClass::index();
}
if ($this->type === null) {
$this->type = $modelClass::type();
}
}
/**
* Loads the fixture.
*
* The default implementation will first clean up the index by calling [[resetIndex()]].
* It will then populate the index with the data returned by [[getData()]].
*
* If you override this method, you should consider calling the parent implementation
* so that the data returned by [[getData()]] can be populated into the index.
*/
public function load()
{
$this->resetIndex();
$this->data = [];
$mapping = $this->db->createCommand()->getMapping($this->index, $this->type);
if (isset($mapping[$this->index]['mappings'][$this->type]['_id']['path'])) {
$idField = $mapping[$this->index]['mappings'][$this->type]['_id']['path'];
} else {
$idField = '_id';
}
foreach ($this->getData() as $alias => $row) {
$options = [];
$id = isset($row[$idField]) ? $row[$idField] : null;
if ($idField === '_id') {
unset($row[$idField]);
}
if (isset($row['_parent'])) {
$options['parent'] = $row['_parent'];
unset($row['_parent']);
}
try {
$response = $this->db->createCommand()->insert($this->index, $this->type, $row, $id, $options);
} catch(\yii\db\Exception $e) {
throw new \yii\base\Exception("Failed to insert fixture data \"$alias\": " . $e->getMessage() . "\n" . print_r($e->errorInfo, true), $e->getCode(), $e);
}
if ($id === null) {
$row[$idField] = $response['_id'];
}
$this->data[$alias] = $row;
}
// ensure all data is flushed and immediately available in the test
$this->db->createCommand()->flushIndex($this->index);
}
/**
* Returns the fixture data.
*
* The default implementation will try to return the fixture data by including the external file specified by [[dataFile]].
* The file should return an array of data rows (column name => column value), each corresponding to a row in the index.
*
* If the data file does not exist, an empty array will be returned.
*
* @return array the data rows to be inserted into the database index.
*/
protected function getData()
{
if ($this->dataFile === null) {
$class = new \ReflectionClass($this);
$dataFile = dirname($class->getFileName()) . "/data/{$this->index}/{$this->type}.php";
return is_file($dataFile) ? require($dataFile) : [];
} else {
return parent::getData();
}
}
/**
* Removes all existing data from the specified index and type.
* This method is called before populating fixture data into the index associated with this fixture.
*/
protected function resetIndex()
{
$this->db->createCommand([
'index' => $this->index,
'type' => $this->type,
'queryParts' => ['query' => ['match_all' => new \stdClass()]],
])->deleteByQuery();
}
}
This diff is collapsed.
This diff is collapsed.
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\elasticsearch;
use yii\base\BaseObject;
/**
* BatchQueryResult represents a batch query from which you can retrieve data in batches.
*
* You usually do not instantiate BatchQueryResult directly. Instead, you obtain it by
* calling [[Query::batch()]] or [[Query::each()]]. Because BatchQueryResult implements the [[\Iterator]] interface,
* you can iterate it to obtain a batch of data in each iteration.
*
* Batch size is determined by the [[Query::$limit]] setting. [[Query::$offset]] setting is ignored.
* New batches will be obtained until the server runs out of results.
*
* If [[Query::$orderBy]] parameter is not set, batches will be processed using the highly efficient "scan" mode.
* In this case, [[Query::$limit]] setting determines batch size per shard.
* See [elasticsearch guide](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-scroll.html)
* for more information.
*
* Example:
* ```php
* $query = (new Query)->from('user');
* foreach ($query->batch() as $i => $users) {
* // $users represents the rows in the $i-th batch
* }
* foreach ($query->each() as $user) {
* }
* ```
*
* @author Konstantin Sirotkin <beowulfenator@gmail.com>
* @since 2.0.4
*/
class BatchQueryResult extends BaseObject implements \Iterator
{
/**
* @var Connection the DB connection to be used when performing batch query.
* If null, the `elasticsearch` application component will be used.
*/
public $db;
/**
* @var Query the query object associated with this batch query.
* Do not modify this property directly unless after [[reset()]] is called explicitly.
*/
public $query;
/**
* @var boolean whether to return a single row during each iteration.
* If false, a whole batch of rows will be returned in each iteration.
*/
public $each = false;
/**
* @var DataReader the data reader associated with this batch query.
*/
private $_dataReader;
/**
* @var array the data retrieved in the current batch
*/
private $_batch;
/**
* @var mixed the value for the current iteration
*/
private $_value;
/**
* @var string|integer the key for the current iteration
*/
private $_key;
/**
* @var string the amount of time to keep the scroll window open
* (in ElasticSearch [time units](https://www.elastic.co/guide/en/elasticsearch/reference/current/common-options.html#time-units).
*/
public $scrollWindow = '1m';
/*
* @var string internal ElasticSearch scroll id
*/
private $_lastScrollId = null;
/**
* Destructor.
*/
public function __destruct()
{
// make sure cursor is closed
$this->reset();
}
/**
* Resets the batch query.
* This method will clean up the existing batch query so that a new batch query can be performed.
*/
public function reset()
{
if(isset($this->_lastScrollId)) {
$this->query->createCommand($this->db)->clearScroll(['scroll_id' => $this->_lastScrollId]);
}
$this->_batch = null;
$this->_value = null;
$this->_key = null;
$this->_lastScrollId = null;
}
/**
* Resets the iterator to the initial state.
* This method is required by the interface [[\Iterator]].
*/
public function rewind()
{
$this->reset();
$this->next();
}
/**
* Moves the internal pointer to the next dataset.
* This method is required by the interface [[\Iterator]].
*/
public function next()
{
if ($this->_batch === null || !$this->each || $this->each && next($this->_batch) === false) {
$this->_batch = $this->fetchData();
reset($this->_batch);
}
if ($this->each) {
$this->_value = current($this->_batch);
if ($this->query->indexBy !== null) {
$this->_key = key($this->_batch);
} elseif (key($this->_batch) !== null) {
$this->_key++;
} else {
$this->_key = null;
}
} else {
$this->_value = $this->_batch;
$this->_key = $this->_key === null ? 0 : $this->_key + 1;
}
}
/**
* Fetches the next batch of data.
* @return array the data fetched
*/
protected function fetchData()
{
if (null === $this->_lastScrollId) {
//first query - do search
$options = ['scroll' => $this->scrollWindow];
if(!$this->query->orderBy) {
$options['search_type'] = 'scan';
}
$result = $this->query->createCommand($this->db)->search($options);
//if using "scan" mode, make another request immediately
//(search request returned 0 results)
if(!$this->query->orderBy) {
$result = $this->query->createCommand($this->db)->scroll([
'scroll_id' => $result['_scroll_id'],
'scroll' => $this->scrollWindow,
]);
}
} else {
//subsequent queries - do scroll
$result = $this->query->createCommand($this->db)->scroll([
'scroll_id' => $this->_lastScrollId,
'scroll' => $this->scrollWindow,
]);
}
//get last scroll id
$this->_lastScrollId = $result['_scroll_id'];
//get data
return $this->query->populate($result['hits']['hits']);
}
/**
* Returns the index of the current dataset.
* This method is required by the interface [[\Iterator]].
* @return int the index of the current row.
*/
public function key()
{
return $this->_key;
}
/**
* Returns the current dataset.
* This method is required by the interface [[\Iterator]].
* @return mixed the current dataset.
*/
public function current()
{
return $this->_value;
}
/**
* Returns whether there is a valid dataset at the current position.
* This method is required by the interface [[\Iterator]].
* @return bool whether there is a valid dataset at the current position.
*/
public function valid()
{
return !empty($this->_batch);
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\elasticsearch;
use yii\base\Component;
use yii\base\InvalidCallException;
use yii\helpers\Json;
/**
* The [[BulkCommand]] class implements the API for accessing the elasticsearch bulk REST API.
*
* Further details on bulk API is available in
* [elasticsearch guide](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html).
*
* @author Konstantin Sirotkin <beowulfenator@gmail.com>
* @since 2.0.5
*/
class BulkCommand extends Component
{
/**
* @var Connection
*/
public $db;
/**
* @var string Default index to execute the queries on. Defaults to null meaning that index needs to be specified in every action.
*/
public $index;
/**
* @var string Default type to execute the queries on. Defaults to null meaning that type needs to be specified in every action.
*/
public $type;
/**
* @var array|string Actions to be executed in this bulk command, given as either an array of arrays or as one newline-delimited string.
* All actions except delete span two lines.
*/
public $actions;
/**
* @var array Options to be appended to the query URL.
*/
public $options = [];
/**
* Executes the bulk command.
* @return mixed
* @throws yii\base\InvalidCallException
*/
public function execute()
{
//valid endpoints are /_bulk, /{index}/_bulk, and {index}/{type}/_bulk
if ($this->index === null && $this->type === null) {
$endpoint = ['_bulk'];
} elseif ($this->index !== null && $this->type === null) {
$endpoint = [$this->index, '_bulk'];
} elseif ($this->index !== null && $this->type !== null) {
$endpoint = [$this->index, $this->type, '_bulk'];
} else {
throw new InvalidCallException('Invalid endpoint: if type is defined, index must be defined too.');
}
if (empty($this->actions)) {
$body = '{}';
} elseif (is_array($this->actions)) {
$body = '';
foreach ($this->actions as $action) {
$body .= Json::encode($action) . "\n";
}
} else {
$body = $this->actions;
}
return $this->db->post($endpoint, $this->options, $body);
}
/**
* Adds an action to the command. Will overwrite existing actions if they are specified as a string.
* @param array $action Action expressed as an array (will be encoded to JSON automatically).
*/
public function addAction($line1, $line2 = null)
{
if (!is_array($this->actions)) {
$this->actions = [];
}
$this->actions[] = $line1;
if ($line2 !== null) {
$this->actions[] = $line2;
}
}
/**
* Adds a delete action to the command.
* @param string $id Document ID
* @param string $index Index that the document belogs to. Can be set to null if the command has
* a default index ([[BulkCommand::$index]]) assigned.
* @param string $type Type that the document belogs to. Can be set to null if the command has
* a default type ([[BulkCommand::$type]]) assigned.
*/
public function addDeleteAction($id, $index = null, $type = null)
{
$actionData = ['_id' => $id];
if (!empty($index)) {
$actionData['_index'] = $index;
}
if (!empty($type)) {
$actionData['_type'] = $type;
}
$this->addAction(['delete' => $actionData]);
}
}
Yii Framework 2 elasticsearch extension Change Log
==================================================
2.0.5 March 20, 2018
--------------------
- Bug #120: Fix debug panel markup to be compatible with Yii 2.0.10 (drdim)
- Bug #125: Fixed `ActiveDataProvider::refresh()` to also reset `$queryResults` data (sizeg)
- Bug #134: Fix infinite query loop "ActiveDataProvider" when the index does not exist (eolitich)
- Bug #149: Changed `yii\base\Object` to `yii\base\BaseObject` (dmirogin)
- Bug: (CVE-2018-8074): Fixed possibility of manipulated condition when unfiltered input is passed to `ActiveRecord::findOne()` or `findAll()` (cebe)
- Bug: Updated debug panel classes to be consistent with yii 2.0.7 (beowulfenator)
- Bug: Added accessor method for the default elasticsearch primary key (kyle-mccarthy)
- Enh #15: Special data provider `yii\elasticsearch\ActiveDataProvider` created (klimov-paul)
- Enh #43: Elasticsearch log target (trntv, beowulfenator)
- Enh #47: Added support for post_filter option in search queries (mxkh)
- Enh #60: Minor updates to guide (devypt, beowulfenator)
- Enh #82: Support HTTPS protocol (dor-denis, beowulfenator)
- Enh #83: Support for "gt", ">", "gte", ">=", "lt", "<", "lte", "<=" operators in query (i-lie, beowulfenator)
- Enh #119: Added support for explanation on query (kyle-mccarthy)
- Enh #150: Explicitily send `Content-Type` header in HTTP requests to elasticsearch (lubobill1990)
- Enh: Bulk API implemented and used in AR (tibee, beowulfenator)
- Enh: Deserialization of raw response when text/plain is supported (Tezd)
- Enh: Added ability to work with aliases through Command class (Tezd)
2.0.4 March 17, 2016
--------------------
- Bug #8: Fixed issue with running out of sockets when running a large number of requests by reusing curl handles (cebe)
- Bug #13: Fixed wrong API call for getting all types or searching all types, `_all` works only for indexes (cebe)
- Bug #19: `DeleteAll` now deletes all entries, not first 10 (beowulfenator)
- Bug #48: `UpdateAll` now updates all entries, not first 10 (beowulfenator)
- Bug #65: Fixed warning `array to string conversion` when parsing error response (rhertogh, silverfire)
- Bug #73: Fixed debug panel exception when no data was recorded for elasticsearch panel (jafaripur)
- Enh #2: Added `min_score` option to query (knut)
- Enh #28: AWS Elasticsearch service compatibility (andrey-bahrachev)
- Enh #33: Implemented `Command::updateSettings()` and `Command::updateAnalyzers()` (githubjeka)
- Enh #50: Implemented HTTP auth (silverfire)
- Enh #62: Added support for scroll API in `batch()` and `each()` (beowulfenator, 13leaf)
- Enh #70: `Query` and `ActiveQuery` now have `$options` attribute that is passed to commands generated by queries (beowulfenator)
- Enh: Unified model creation from result set in `Query` and `ActiveQuery` with `populate()` (beowulfenator)
2.0.3 March 01, 2015
--------------------
- no changes in this release.
2.0.2 January 11, 2015
----------------------
- Enh: Added `ActiveFixture` class for testing fixture support for elasticsearch (cebe, viilveer)
2.0.1 December 07, 2014
-----------------------
- Bug #5662: Elasticsearch AR updateCounters() now uses explicitly `groovy` script for updating making it compatible with ES >1.3.0 (cebe)
- Bug #6065: `ActiveRecord::unlink()` was failing in some situations when working with relations via array valued attributes (cebe)
- Enh #5758: Allow passing custom options to `ActiveRecord::update()` and `::delete()` including support for routing needed for updating records with parent relation (cebe)
- Enh: Add support for optimistic locking (cebe)
2.0.0 October 12, 2014
----------------------
- Enh #3381: Added ActiveRecord::arrayAttributes() to define attributes that should be treated as array when retrieved via `fields` (cebe)
2.0.0-rc September 27, 2014
---------------------------
- Bug #3587: Fixed an issue with storing empty records (cebe)
- Bug #4187: Elasticsearch dynamic scripting is disabled in 1.2.0, so do not use it in query builder (cebe)
- Enh #3527: Added `highlight` property to Query and ActiveRecord. (Borales)
- Enh #4048: Added `init` event to `ActiveQuery` classes (qiangxue)
- Enh #4086: changedAttributes of afterSave Event now contain old values (dizews)
- Enh: Make error messages more readable in HTML output (cebe)
- Enh: Added support for query stats (cebe)
- Enh: Added support for query suggesters (cebe, tvdavid)
- Enh: Added support for delete by query (cebe, tvdavid)
- Chg #4451: Removed support for facets and replaced them with aggregations (cebe, tadaszelvys)
- Chg: asArray in ActiveQuery is now equal to using the normal Query. This means, that the output structure has changed and `with` is supported anymore. (cebe)
- Chg: Deletion of a record is now also considered successful if the record did not exist. (cebe)
- Chg: Requirement changes: Yii now requires elasticsearch version 1.0 or higher (cebe)
2.0.0-beta April 13, 2014
-------------------------
- Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder)
- Bug #2324: Fixed QueryBuilder bug when building a query with "query" option (mintao)
- Enh #1313: made index and type available in `ActiveRecord::instantiate()` to allow creating records based on elasticsearch type when doing cross index/type search (cebe)
- Enh #1382: Added a debug toolbar panel for elasticsearch (cebe)
- Enh #1765: Added support for primary key path mapping, pk can now be part of the attributes when mapping is defined (cebe)
- Enh #2002: Added filterWhere() method to yii\elasticsearch\Query to allow easy addition of search filter conditions by ignoring empty search fields (samdark, cebe)
- Enh #2892: ActiveRecord dirty attributes are now reset after call to `afterSave()` so information about changed attributes is available in `afterSave`-event (cebe)
- Chg #1765: Changed handling of ActiveRecord primary keys, removed getId(), use getPrimaryKey() instead (cebe)
- Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe)
- Chg #2146: Removed `ActiveRelation` class and moved the functionality to `ActiveQuery`.
All relational queries are now directly served by `ActiveQuery` allowing to use
custom scopes in relations (cebe)
2.0.0-alpha, December 1, 2013
-----------------------------
- Initial release.
This diff is collapsed.
This diff is collapsed.
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\elasticsearch;
use yii\base\Action;
use yii\base\NotSupportedException;
use yii\helpers\ArrayHelper;
use yii\web\HttpException;
use yii\web\Response;
use Yii;
/**
* Debug Action is used by [[DebugPanel]] to perform elasticsearch queries using ajax.
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class DebugAction extends Action
{
/**
* @var string the connection id to use
*/
public $db;
/**
* @var DebugPanel
*/
public $panel;
/**
* @var \yii\debug\controllers\DefaultController
*/
public $controller;
public function run($logId, $tag)
{
$this->controller->loadData($tag);
$timings = $this->panel->calculateTimings();
ArrayHelper::multisort($timings, 3, SORT_DESC);
if (!isset($timings[$logId])) {
throw new HttpException(404, 'Log message not found.');
}
$message = $timings[$logId][1];
if (($pos = mb_strpos($message, "#")) !== false) {
$url = mb_substr($message, 0, $pos);
$body = mb_substr($message, $pos + 1);
} else {
$url = $message;
$body = null;
}
$method = mb_substr($url, 0, $pos = mb_strpos($url, ' '));
$url = mb_substr($url, $pos + 1);
$options = ['pretty' => true];
/* @var $db Connection */
$db = \Yii::$app->get($this->db);
$time = microtime(true);
switch ($method) {
case 'GET': $result = $db->get($url, $options, $body, true); break;
case 'POST': $result = $db->post($url, $options, $body, true); break;
case 'PUT': $result = $db->put($url, $options, $body, true); break;
case 'DELETE': $result = $db->delete($url, $options, $body, true); break;
case 'HEAD': $result = $db->head($url, $options, $body); break;
default:
throw new NotSupportedException("Request method '$method' is not supported by elasticsearch.");
}
$time = microtime(true) - $time;
if ($result === true) {
$result = '<span class="label label-success">success</span>';
} elseif ($result === false) {
$result = '<span class="label label-danger">no success</span>';
}
Yii::$app->response->format = Response::FORMAT_JSON;
return [
'time' => sprintf('%.1f ms', $time * 1000),
'result' => $result,
];
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\elasticsearch;
use yii\debug\Panel;
use yii\helpers\ArrayHelper;
use yii\helpers\Url;
use yii\log\Logger;
use yii\helpers\Html;
use yii\web\View;
/**
* Debugger panel that collects and displays elasticsearch queries performed.
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class DebugPanel extends Panel
{
public $db = 'elasticsearch';
public function init()
{
$this->actions['elasticsearch-query'] = [
'class' => 'yii\\elasticsearch\\DebugAction',
'panel' => $this,
'db' => $this->db,
];
}
/**
* @inheritdoc
*/
public function getName()
{
return 'Elasticsearch';
}
/**
* @inheritdoc
*/
public function getSummary()
{
$timings = $this->calculateTimings();
$queryCount = count($timings);
$queryTime = 0;
foreach ($timings as $timing) {
$queryTime += $timing[3];
}
$queryTime = number_format($queryTime * 1000) . ' ms';
$url = $this->getUrl();
$output = <<<EOD
<div class="yii-debug-toolbar__block">
<a href="$url" title="Executed $queryCount elasticsearch queries which took $queryTime.">
ES <span class="yii-debug-toolbar__label yii-debug-toolbar__ajax_counter yii-debug-toolbar__label_info">$queryCount</span> <span class="yii-debug-toolbar__label">$queryTime</span>
</a>
</div>
EOD;
return $queryCount > 0 ? $output : '';
}
/**
* @inheritdoc
*/
public function getDetail()
{
$timings = $this->calculateTimings();
ArrayHelper::multisort($timings, 3, SORT_DESC);
$rows = [];
$i = 0;
foreach ($timings as $logId => $timing) {
$duration = sprintf('%.1f ms', $timing[3] * 1000);
$message = $timing[1];
$traces = $timing[4];
if (($pos = mb_strpos($message, "#")) !== false) {
$url = mb_substr($message, 0, $pos);
$body = mb_substr($message, $pos + 1);
} else {
$url = $message;
$body = null;
}
$traceString = '';
if (!empty($traces)) {
$traceString .= Html::ul($traces, [
'class' => 'trace',
'item' => function ($trace) {
return "<li>{$trace['file']}({$trace['line']})</li>";
},
]);
}
$ajaxUrl = Url::to(['elasticsearch-query', 'logId' => $logId, 'tag' => $this->tag]);
\Yii::$app->view->registerJs(<<<JS
$('#elastic-link-$i').on('click', function () {
var result = $('#elastic-result-$i');
result.html('Sending request...');
result.parent('tr').show();
$.ajax({
type: "POST",
url: "$ajaxUrl",
success: function (data) {
$('#elastic-time-$i').html(data.time);
$('#elastic-result-$i').html(data.result);
},
error: function (jqXHR, textStatus, errorThrown) {
$('#elastic-time-$i').html('');
$('#elastic-result-$i').html('<span style="color: #c00;">Error: ' + errorThrown + ' - ' + textStatus + '</span><br />' + jqXHR.responseText);
},
dataType: "json"
});
return false;
});
JS
, View::POS_READY);
$runLink = Html::a('run query', '#', ['id' => "elastic-link-$i"]) . '<br/>';
$rows[] = <<<HTML
<tr>
<td style="width: 10%;">$duration</td>
<td style="width: 75%;"><div><b>$url</b><br/><p>$body</p>$traceString</div></td>
<td style="width: 15%;">$runLink</td>
</tr>
<tr style="display: none;"><td id="elastic-time-$i"></td><td colspan="3" id="elastic-result-$i"></td></tr>
HTML;
$i++;
}
$rows = implode("\n", $rows);
return <<<HTML
<h1>Elasticsearch Queries</h1>
<table class="table table-condensed table-bordered table-striped table-hover" style="table-layout: fixed;">
<thead>
<tr>
<th style="width: 10%;">Time</th>
<th style="width: 75%;">Url / Query</th>
<th style="width: 15%;">Run Query on node</th>
</tr>
</thead>
<tbody>
$rows
</tbody>
</table>
HTML;
}
private $_timings;
public function calculateTimings()
{
if ($this->_timings !== null) {
return $this->_timings;
}
$messages = isset($this->data['messages']) ? $this->data['messages'] : [];
$timings = [];
$stack = [];
foreach ($messages as $i => $log) {
list($token, $level, $category, $timestamp) = $log;
$log[5] = $i;
if ($level == Logger::LEVEL_PROFILE_BEGIN) {
$stack[] = $log;
} elseif ($level == Logger::LEVEL_PROFILE_END) {
if (($last = array_pop($stack)) !== null && $last[0] === $token) {
$timings[$last[5]] = [count($stack), $token, $last[3], $timestamp - $last[3], $last[4]];
}
}
}
$now = microtime(true);
while (($last = array_pop($stack)) !== null) {
$delta = $now - $last[3];
$timings[$last[5]] = [count($stack), $last[0], $last[2], $delta, $last[4]];
}
ksort($timings);
return $this->_timings = $timings;
}
/**
* @inheritdoc
*/
public function save()
{
$target = $this->module->logTarget;
$messages = $target->filterMessages($target->messages, Logger::LEVEL_PROFILE, ['yii\elasticsearch\Connection::httpRequest']);
return ['messages' => $messages];
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\elasticsearch;
use Yii;
use yii\base\InvalidConfigException;
use yii\di\Instance;
use yii\helpers\ArrayHelper;
use yii\helpers\Json;
use yii\helpers\VarDumper;
use yii\log\Logger;
use yii\log\Target;
/**
* ElasticsearchTarget stores log messages in a elasticsearch index.
*
* @author Eugene Terentev <eugene@terentev.net>
* @since 2.0.5
*/
class ElasticsearchTarget extends Target
{
/**
* @var string Elasticsearch index name.
*/
public $index = 'yii';
/**
* @var string Elasticsearch type name.
*/
public $type = 'log';
/**
* @var Connection|array|string the elasticsearch connection object or the application component ID
* of the elasticsearch connection.
*/
public $db = 'elasticsearch';
/**
* @var array $options URL options.
*/
public $options = [];
/**
* @var boolean If true, context will be logged as a separate message after all other messages.
*/
public $logContext = true;
/**
* @var boolean If true, context will be included in every message.
* This is convenient if you log application errors and analyze them with tools like Kibana.
*/
public $includeContext = false;
/**
* @var boolean If true, context message will cached once it's been created. Makes sense to use with [[includeContext]].
*/
public $cacheContext = false;
/**
* @var string Context message cache (can be used multiple times if context is appended to every message)
*/
protected $_contextMessage = null;
/**
* This method will initialize the [[elasticsearch]] property to make sure it refers to a valid Elasticsearch connection.
* @throws InvalidConfigException if [[elasticsearch]] is invalid.
*/
public function init()
{
parent::init();
$this->db = Instance::ensure($this->db, Connection::className());
}
/**
* @inheritdoc
*/
public function export()
{
$messages = array_map([$this, 'prepareMessage'], $this->messages);
$body = implode("\n", $messages) . "\n";
$this->db->post([$this->index, $this->type, '_bulk'], $this->options, $body);
}
/**
* If [[includeContext]] property is false, returns context message normally.
* If [[includeContext]] is true, returns an empty string (so that context message in [[collect]] is not generated),
* expecting that context will be appended to every message in [[prepareMessage]].
* @return array the context information
*/
protected function getContextMessage()
{
if (null === $this->_contextMessage || !$this->cacheContext) {
$this->_contextMessage = ArrayHelper::filter($GLOBALS, $this->logVars);
}
return $this->_contextMessage;
}
/**
* Processes the given log messages.
* This method will filter the given messages with [[levels]] and [[categories]].
* And if requested, it will also export the filtering result to specific medium (e.g. email).
* Depending on the [[includeContext]] attribute, a context message will be either created or ignored.
* @param array $messages log messages to be processed. See [[Logger::messages]] for the structure
* of each message.
* @param bool $final whether this method is called at the end of the current application
*/
public function collect($messages, $final)
{
$this->messages = array_merge($this->messages, static::filterMessages($messages, $this->getLevels(), $this->categories, $this->except));
$count = count($this->messages);
if ($count > 0 && ($final || $this->exportInterval > 0 && $count >= $this->exportInterval)) {
if (!$this->includeContext && $this->logContext) {
$context = $this->getContextMessage();
if (!empty($context)) {
$this->messages[] = [$context, Logger::LEVEL_INFO, 'application', YII_BEGIN_TIME];
}
}
// set exportInterval to 0 to avoid triggering export again while exporting
$oldExportInterval = $this->exportInterval;
$this->exportInterval = 0;
$this->export();
$this->exportInterval = $oldExportInterval;
$this->messages = [];
}
}
/**
* Prepares a log message.
* @param array $message The log message to be formatted.
* @return string
*/
public function prepareMessage($message)
{
list($text, $level, $category, $timestamp) = $message;
$result = [
'category' => $category,
'level' => Logger::getLevelName($level),
'@timestamp' => date('c', $timestamp),
];
if (isset($message[4])) {
$result['trace'] = $message[4];
}
//Exceptions get parsed into an array, text and arrays are passed as is, other types are var_dumped
if ($text instanceof \Exception) {
//convert exception to array for easier analysis
$result['message'] = [
'message' => $text->getMessage(),
'file' => $text->getFile(),
'line' => $text->getLine(),
'trace' => $text->getTraceAsString(),
];
} elseif (is_array($text) || is_string($text)) {
$result['message'] = $text;
} else {
$result['message'] = VarDumper::export($text);
}
if ($this->includeContext) {
$result['context'] = $this->getContextMessage();
}
$message = implode("\n", [
Json::encode([
'index' => new \stdClass()
]),
Json::encode($result)
]);
return $message;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\elasticsearch;
/**
* Exception represents an exception that is caused by elasticsearch-related operations.
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class Exception extends \yii\db\Exception
{
/**
* @return string the user-friendly name of this exception
*/
public function getName()
{
return 'Elasticsearch Database Exception';
}
}
The Yii framework is free software. It is released under the terms of
the following BSD License.
Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of Yii Software LLC nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
This diff is collapsed.
This diff is collapsed.
<p align="center">
<a href="https://www.elastic.co/products/elasticsearch" target="_blank" rel="external">
<img src="https://static-www.elastic.co/assets/blt45b0886c90beceee/logo-elastic.svg" height="80px">
</a>
<h1 align="center">Elasticsearch Query and ActiveRecord for Yii 2</h1>
<br>
</p>
This extension provides the [elasticsearch](https://www.elastic.co/products/elasticsearch) integration for the [Yii framework 2.0](http://www.yiiframework.com).
It includes basic querying/search support and also implements the `ActiveRecord` pattern that allows you to store active
records in elasticsearch.
For license information check the [LICENSE](LICENSE.md)-file.
Documentation is at [docs/guide/README.md](docs/guide/README.md).
[![Latest Stable Version](https://poser.pugx.org/yiisoft/yii2-elasticsearch/v/stable.png)](https://packagist.org/packages/yiisoft/yii2-elasticsearch)
[![Total Downloads](https://poser.pugx.org/yiisoft/yii2-elasticsearch/downloads.png)](https://packagist.org/packages/yiisoft/yii2-elasticsearch)
[![Build Status](https://travis-ci.org/yiisoft/yii2-elasticsearch.svg?branch=master)](https://travis-ci.org/yiisoft/yii2-elasticsearch)
Requirements
------------
Dependent on the version of elasticsearch you are using you need a different version of this extension.
- Extension version 2.0.x works with elasticsearch version 1.0 to 4.x.
- Extension version 2.1.x requires at least elasticsearch version 5.0.
Installation
------------
The preferred way to install this extension is through [composer](http://getcomposer.org/download/).
Either run
```
php composer.phar require --prefer-dist yiisoft/yii2-elasticsearch
```
or add
```json
"yiisoft/yii2-elasticsearch": "~2.0.0"
```
to the require section of your composer.json.
Configuration
-------------
To use this extension, you have to configure the Connection class in your application configuration:
```php
return [
//....
'components' => [
'elasticsearch' => [
'class' => 'yii\elasticsearch\Connection',
'nodes' => [
['http_address' => '127.0.0.1:9200'],
// configure more hosts if you have a cluster
],
],
]
];
```
{
"name": "yiisoft/yii2-elasticsearch",
"description": "Elasticsearch integration and ActiveRecord for the Yii framework",
"keywords": ["yii2", "elasticsearch", "active-record", "search", "fulltext"],
"type": "yii2-extension",
"license": "BSD-3-Clause",
"support": {
"issues": "https://github.com/yiisoft/yii2-elasticsearch/issues",
"forum": "http://www.yiiframework.com/forum/",
"wiki": "http://www.yiiframework.com/wiki/",
"irc": "irc://irc.freenode.net/yii",
"source": "https://github.com/yiisoft/yii2-elasticsearch"
},
"authors": [
{
"name": "Carsten Brandt",
"email": "mail@cebe.cc"
}
],
"require": {
"yiisoft/yii2": "~2.0.14",
"ext-curl": "*"
},
"autoload": {
"psr-4": { "yii\\elasticsearch\\": "" }
},
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment