diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 52ec8692f..cd11ad642 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,14 +1,18 @@ + + ### What steps will reproduce the problem? ### What's expected? ### What do you get instead? - ### Additional info | Q | A | ---------------- | --- -| Yii vesion | +| Yii version | | PHP version | | Operating system | diff --git a/.travis.yml b/.travis.yml index 884a43cff..add96f640 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,7 @@ +env: + global: + secure: cIGYsPd8OZ3X/ugDZyySmlMl/6gMcMMZQupTgPzHeq24mx+NR7gbVfQmTVnBjmXIpEemQFSe1KwQA2xdvkJ+rhObs8e1tldWFxjXd95Drlf1rKx1oy1LehPmXN4IpbY7PqEbncoSJywb7aLe18u+/2NFA27JsMh83y2xv6YfTQE= + language: php php: @@ -7,8 +11,7 @@ php: - 7.0 - 7.1 - 7.2 - - nightly - - hhvm + - 7.3 services: - docker diff --git a/ActiveDataProvider.php b/ActiveDataProvider.php index 428e07e39..be082d270 100644 --- a/ActiveDataProvider.php +++ b/ActiveDataProvider.php @@ -19,8 +19,8 @@ * 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 $queryResults the query results. - * @property array $aggregations all aggregations results. + * @property array $aggregations All aggregations results. This property is read-only. + * @property array $queryResults Full query results. * * @author Paul Klimov * @since 2.0.5 @@ -95,14 +95,15 @@ protected function prepareModels() $query->addOrderBy($sort->getOrders()); } - $results = $query->search($this->db); - $this->setQueryResults($results); - - if ($pagination !== false) { - $pagination->totalCount = $this->getTotalCount(); + if (is_array(($results = $query->search($this->db)))) { + $this->setQueryResults($results); + if ($pagination !== false) { + $pagination->totalCount = $this->getTotalCount(); + } + return $results['hits']['hits']; } - - return $results['hits']['hits']; + $this->setQueryResults([]); + return []; } /** @@ -115,7 +116,7 @@ protected function prepareTotalCount() } $results = $this->getQueryResults(); - return (int)$results['hits']['total']; + return isset($results['hits']['total']) ? (int)$results['hits']['total'] : 0; } /** @@ -157,4 +158,13 @@ protected function prepareKeys($models) return array_keys($models); } } -} \ No newline at end of file + + /** + * @inheritdoc + */ + public function refresh() + { + parent::refresh(); + $this->_queryResults = null; + } +} diff --git a/ActiveQuery.php b/ActiveQuery.php index 2c6919d1d..f3e74fdce 100644 --- a/ActiveQuery.php +++ b/ActiveQuery.php @@ -287,6 +287,15 @@ public function one($db = null) */ public function search($db = null, $options = []) { + if ($this->emulateExecution) { + return [ + 'hits' => [ + 'total' => 0, + 'hits' => [], + ], + ]; + } + $command = $this->createCommand($db); $result = $command->search($options); if ($result === false) { diff --git a/ActiveRecord.php b/ActiveRecord.php index 27cd63b97..fc797e4e1 100644 --- a/ActiveRecord.php +++ b/ActiveRecord.php @@ -61,6 +61,7 @@ class ActiveRecord extends BaseActiveRecord private $_highlight; private $_explanation; + /** * Returns the database connection used by this AR class. * By default, the "elasticsearch" application component is used as the database connection. @@ -432,7 +433,7 @@ public static function instantiate($row) * $customer->insert(); * ~~~ * - * @param boolean $runValidation whether to perform validation before saving the record. + * @param bool $runValidation whether to perform validation before saving the record. * If the validation fails, the record will not be inserted into the database. * @param array $attributes list of attributes that need to be saved. Defaults to null, * meaning all attributes will be saved. @@ -446,7 +447,7 @@ public static function instantiate($row) * for more details on these options. * * By default the `op_type` is set to `create` if model primary key is present. - * @return boolean whether the attributes are valid and the record is inserted successfully. + * @return bool whether the attributes are valid and the record is inserted successfully. */ public function insert($runValidation = true, $attributes = null, $options = [ ]) { @@ -488,7 +489,7 @@ public function insert($runValidation = true, $attributes = null, $options = [ ] /** * @inheritdoc * - * @param boolean $runValidation whether to perform validation before saving the record. + * @param bool $runValidation whether to perform validation before saving the record. * If the validation fails, the record will not be inserted into the database. * @param array $attributeNames list of attribute names that need to be saved. Defaults to null, * meaning all attributes that are loaded from DB will be saved. @@ -515,7 +516,7 @@ public function insert($runValidation = true, $attributes = null, $options = [ ] * Make sure the record has been fetched with a [[version]] before. This is only the case * for records fetched via [[get()]] and [[mget()]] by default. For normal queries, the `_version` field has to be fetched explicitly. * - * @return integer|boolean the number of rows affected, or false if validation fails + * @return int|bool the number of rows affected, or false if validation fails * or [[beforeSave()]] stops the updating process. * @throws StaleObjectException if optimistic locking is enabled and the data being updated is outdated. * @throws InvalidParamException if no [[version]] is available and optimistic locking is enabled. @@ -534,7 +535,7 @@ public function update($runValidation = true, $attributeNames = null, $options = * @param array $attributes attributes to update * @param array $options options given in this parameter are passed to elasticsearch * as request URI parameters. See [[update()]] for details. - * @return integer|false the number of rows affected, or false if [[beforeSave()]] stops the updating process. + * @return int|false the number of rows affected, or false if [[beforeSave()]] stops the updating process. * @throws StaleObjectException if optimistic locking is enabled and the data being updated is outdated. * @throws InvalidParamException if no [[version]] is available and optimistic locking is enabled. * @throws Exception in case update failed. @@ -632,7 +633,7 @@ protected static function primaryKeysByCondition($condition) * @param array $condition the conditions that will be passed to the `where()` method when building the query. * Please refer to [[ActiveQuery::where()]] on how to specify this parameter. * @see [[ActiveRecord::primaryKeysByCondition()]] - * @return integer the number of rows updated + * @return int the number of rows updated * @throws Exception on error. */ public static function updateAll($attributes, $condition = []) @@ -680,7 +681,7 @@ public static function updateAll($attributes, $condition = []) * @param array $condition the conditions that will be passed to the `where()` method when building the query. * Please refer to [[ActiveQuery::where()]] on how to specify this parameter. * @see [[ActiveRecord::primaryKeysByCondition()]] - * @return integer the number of rows updated + * @return int the number of rows updated * @throws Exception on error. */ public static function updateAllCounters($counters, $condition = []) @@ -750,7 +751,7 @@ public static function updateAllCounters($counters, $condition = []) * Make sure the record has been fetched with a [[version]] before. This is only the case * for records fetched via [[get()]] and [[mget()]] by default. For normal queries, the `_version` field has to be fetched explicitly. * - * @return integer|boolean the number of rows deleted, or false if the deletion is unsuccessful for some reason. + * @return int|bool the number of rows deleted, or false if the deletion is unsuccessful for some reason. * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful. * @throws StaleObjectException if optimistic locking is enabled and the data being deleted is outdated. * @throws Exception in case delete failed. @@ -808,7 +809,7 @@ public function delete($options = []) * @param array $condition the conditions that will be passed to the `where()` method when building the query. * Please refer to [[ActiveQuery::where()]] on how to specify this parameter. * @see [[ActiveRecord::primaryKeysByCondition()]] - * @return integer the number of rows deleted + * @return int the number of rows deleted * @throws Exception on error. */ public static function deleteAll($condition = []) diff --git a/BatchQueryResult.php b/BatchQueryResult.php index dee56432d..69a8148dd 100644 --- a/BatchQueryResult.php +++ b/BatchQueryResult.php @@ -181,7 +181,7 @@ protected function fetchData() /** * Returns the index of the current dataset. * This method is required by the interface [[\Iterator]]. - * @return integer the index of the current row. + * @return int the index of the current row. */ public function key() { @@ -201,7 +201,7 @@ public function current() /** * Returns whether there is a valid dataset at the current position. * This method is required by the interface [[\Iterator]]. - * @return boolean whether there is a valid dataset at the current position. + * @return bool whether there is a valid dataset at the current position. */ public function valid() { diff --git a/BulkCommand.php b/BulkCommand.php index 56903ae8e..39b170852 100644 --- a/BulkCommand.php +++ b/BulkCommand.php @@ -44,6 +44,7 @@ class BulkCommand extends Component */ public $options = []; + /** * Executes the bulk command. * @return mixed diff --git a/CHANGELOG.md b/CHANGELOG.md index f7a0a6775..051f3b7cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,19 +13,35 @@ Yii Framework 2 elasticsearch extension Change Log - Enh #222: Added collapse support (walkskyer) 2.0.5 under development +2.0.6 under development ----------------------- +- Bug #180: Fixed `count()` compatibility with PHP 7.2 to not call it on scalar values (cebe) +- Bug #227: Fixed `Bad Request (#400): Unable to verify your data submission.` in debug details panel 'run query' (rhertogh) +- Enh #117: Add support for `QueryInterface::emulateExecution()` (cebe) + + +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 #83: Support for "gt", ">", "gte", ">=", "lt", "<", "lte", "<=" operators in query (i-lie, beowulfenator) -- Enh: Bulk API implemented and used in AR (tibee, beowulfenator) - Enh #82: Support HTTPS protocol (dor-denis, beowulfenator) -- Enh #43: Elasticsearch log target (trntv, beowulfenator) -- Bug: Added accessor method for the default elasticsearch primary key (kyle-mccarthy) +- 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 diff --git a/Command.php b/Command.php index a0c212024..066d693e5 100644 --- a/Command.php +++ b/Command.php @@ -295,7 +295,114 @@ public function typeExists($index, $type) return $this->db->head([$index, $type]); } - // TODO http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html + /** + * @param string $alias + * + * @return bool + */ + public function aliasExists($alias) + { + $indexes = $this->getIndexesByAlias($alias); + + return !empty($indexes); + } + + /** + * @return array + * @see https://www.elastic.co/guide/en/elasticsearch/reference/2.0/indices-aliases.html#alias-retrieving + */ + public function getAliasInfo() + { + $aliasInfo = $this->db->get(['_alias', '*']); + return $aliasInfo ?: []; + } + + /** + * @param string $alias + * + * @return array + * @see https://www.elastic.co/guide/en/elasticsearch/reference/2.0/indices-aliases.html#alias-retrieving + */ + public function getIndexInfoByAlias($alias) + { + $responseData = $this->db->get(['_alias', $alias]); + if (empty($responseData)) { + return []; + } + + return $responseData; + } + + /** + * @param string $alias + * + * @return array + */ + public function getIndexesByAlias($alias) + { + return array_keys($this->getIndexInfoByAlias($alias)); + } + + /** + * @param string $index + * + * @return array + * @see https://www.elastic.co/guide/en/elasticsearch/reference/2.0/indices-aliases.html#alias-retrieving + */ + public function getIndexAliases($index) + { + $responseData = $this->db->get([$index, '_alias', '*']); + if (empty($responseData)) { + return []; + } + + return $responseData[$index]['aliases']; + } + + /** + * @param $index + * @param $alias + * @param array $aliasParameters + * + * @return bool + * @see https://www.elastic.co/guide/en/elasticsearch/reference/2.0/indices-aliases.html#alias-adding + */ + public function addAlias($index, $alias, $aliasParameters = []) + { + return (bool)$this->db->put([$index, '_alias', $alias], [], json_encode((object)$aliasParameters)); + } + + /** + * @param string $index + * @param string $alias + * + * @return bool + * @see https://www.elastic.co/guide/en/elasticsearch/reference/2.0/indices-aliases.html#deleting + */ + public function removeAlias($index, $alias) + { + return (bool)$this->db->delete([$index, '_alias', $alias]); + } + + /** + * Runs alias manipulations. + * If you want to add alias1 to index1 + * and remove alias2 from index2 you can use following commands: + * ~~~ + * $actions = [ + * ['add' => ['index' => 'index1', 'alias' => 'alias1']], + * ['remove' => ['index' => 'index2', 'alias' => 'alias2']], + * ]; + * ~~~ + * @param array $actions + * + * @return bool + * @see https://www.elastic.co/guide/en/elasticsearch/reference/2.0/indices-aliases.html#indices-aliases + */ + public function aliasActions(array $actions) + { + return (bool)$this->db->post(['_aliases'], [], json_encode(['actions' => $actions])); + } /** * Change specific index level settings in real time. @@ -356,7 +463,7 @@ public function updateAnalyzers($index, $setting, $options = []) $this->openIndex($index); return $result; } - + // TODO http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-settings.html // TODO http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-warmers.html @@ -518,7 +625,7 @@ public function getMapping($index = '_all', $type = null) * @param $pattern * @param $settings * @param $mappings - * @param integer $order + * @param int $order * @return mixed * @see http://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates.html */ diff --git a/Connection.php b/Connection.php index f2071eea2..34eacb16c 100644 --- a/Connection.php +++ b/Connection.php @@ -17,7 +17,7 @@ * elasticsearch Connection is used to connect to an elasticsearch cluster version 0.20 or higher * * @property string $driverName Name of the DB driver. This property is read-only. - * @property boolean $isActive Whether the DB connection is established. This property is read-only. + * @property bool $isActive Whether the DB connection is established. This property is read-only. * @property QueryBuilder $queryBuilder This property is read-only. * * @author Carsten Brandt @@ -130,7 +130,7 @@ public function __sleep() /** * Returns a value indicating whether the DB connection is established. - * @return boolean whether the DB connection is established + * @return bool whether the DB connection is established */ public function getIsActive() { @@ -189,7 +189,7 @@ protected function populateNodes() } $node['http_address'] = $node['http']['publish_address']; - //Protocol is not a standard ES node property, so we add it manually + // Protocol is not a standard ES node property, so we add it manually $node['protocol'] = $this->defaultProtocol; } @@ -197,7 +197,7 @@ protected function populateNodes() $this->nodes = array_values($nodes); } else { curl_close($this->_curl); - throw new Exception('Cluster autodetection did not find any active nodes.'); + throw new Exception('Cluster autodetection did not find any active node. Make sure a GET /_nodes reguest on the hosts defined in the config returns the "http_address" field for each node.'); } } @@ -291,7 +291,7 @@ public function getQueryBuilder() * @param string|array $url URL * @param array $options URL options * @param string $body request body - * @param boolean $raw if response body contains JSON and should be decoded + * @param bool $raw if response body contains JSON and should be decoded * @return mixed response * @throws Exception * @throws InvalidConfigException @@ -324,7 +324,7 @@ public function head($url, $options = [], $body = null) * @param string|array $url URL * @param array $options URL options * @param string $body request body - * @param boolean $raw if response body contains JSON and should be decoded + * @param bool $raw if response body contains JSON and should be decoded * @return mixed response * @throws Exception * @throws InvalidConfigException @@ -341,7 +341,7 @@ public function post($url, $options = [], $body = null, $raw = false) * @param string|array $url URL * @param array $options URL options * @param string $body request body - * @param boolean $raw if response body contains JSON and should be decoded + * @param bool $raw if response body contains JSON and should be decoded * @return mixed response * @throws Exception * @throws InvalidConfigException @@ -358,7 +358,7 @@ public function put($url, $options = [], $body = null, $raw = false) * @param string|array $url URL * @param array $options URL options * @param string $body request body - * @param boolean $raw if response body contains JSON and should be decoded + * @param bool $raw if response body contains JSON and should be decoded * @return mixed response * @throws Exception * @throws InvalidConfigException @@ -405,7 +405,7 @@ private function createUrl($path, $options = []) * @param string $method method name * @param string $url URL * @param string $requestBody request body - * @param boolean $raw if response body contains JSON and should be decoded + * @param bool $raw if response body contains JSON and should be decoded * @return mixed if request failed * @throws Exception if request failed * @throws InvalidConfigException @@ -529,8 +529,13 @@ protected function httpRequest($method, $url, $requestBody = null, $raw = false) 'responseBody' => $body, ]); } - if (isset($headers['content-type']) && (!strncmp($headers['content-type'], 'application/json', 16) || !strncmp($headers['content-type'], 'text/plain', 10))) { - return $raw ? $body : Json::decode($body); + if (isset($headers['content-type'])) { + if (!strncmp($headers['content-type'], 'application/json', 16)) { + return $raw ? $body : Json::decode($body); + } + if (!strncmp($headers['content-type'], 'text/plain', 10)) { + return $raw ? $body : array_filter(explode("\n", $body)); + } } throw new Exception('Unsupported data received from elasticsearch: ' . $headers['content-type'], [ 'requestMethod' => $method, diff --git a/DebugPanel.php b/DebugPanel.php index 7436aaaa3..d55e8ee5e 100644 --- a/DebugPanel.php +++ b/DebugPanel.php @@ -71,6 +71,9 @@ public function getSummary() */ public function getDetail() { + //Register YiiAsset in order to inject csrf token in ajax requests + YiiAsset::register(\Yii::$app->view); + $timings = $this->calculateTimings(); ArrayHelper::multisort($timings, 3, SORT_DESC); $rows = []; diff --git a/ElasticsearchTarget.php b/ElasticsearchTarget.php index f85d066dd..e1bf8cb44 100644 --- a/ElasticsearchTarget.php +++ b/ElasticsearchTarget.php @@ -1,4 +1,9 @@ emulateExecution) { + return []; + } $result = $this->createCommand($db)->search(); if ($result === false) { throw new Exception('Elasticsearch search query failed.'); @@ -303,11 +306,14 @@ public function populate($rows) * @param Connection $db the database connection used to execute the query. * If this parameter is not given, the `elasticsearch` application * component will be used. - * @return array|boolean the first row (in terms of an array) of the query + * @return array|bool the first row (in terms of an array) of the query * result. False is returned if the query results in nothing. */ public function one($db = null) { + if ($this->emulateExecution) { + return false; + } $result = $this->createCommand($db)->search(['size' => 1]); if ($result === false) { throw new Exception('Elasticsearch search query failed.'); @@ -336,6 +342,14 @@ public function one($db = null) */ public function search($db = null, $options = []) { + if ($this->emulateExecution) { + return [ + 'hits' => [ + 'total' => 0, + 'hits' => [], + ], + ]; + } $result = $this->createCommand($db)->search($options); if ($result === false) { throw new Exception('Elasticsearch search query failed.'); @@ -369,6 +383,9 @@ public function search($db = null, $options = []) */ public function delete($db = null, $options = []) { + if ($this->emulateExecution) { + return []; + } return $this->createCommand($db)->deleteByQuery($options); } @@ -385,6 +402,9 @@ public function delete($db = null, $options = []) */ public function scalar($field, $db = null) { + if ($this->emulateExecution) { + return null; + } $record = self::one($db); if ($record !== false) { if ($field === '_id') { @@ -409,6 +429,9 @@ public function scalar($field, $db = null) */ public function column($field, $db = null) { + if ($this->emulateExecution) { + return []; + } $command = $this->createCommand($db); $command->queryParts['_source'] = [$field]; $result = $command->search(); @@ -437,10 +460,13 @@ public function column($field, $db = null) * @param Connection $db the database connection used to execute the query. * If this parameter is not given, the `elasticsearch` application * component will be used. - * @return integer number of records + * @return int number of records */ public function count($q = '*', $db = null) { + if ($this->emulateExecution) { + return 0; + } // performing a query with return size of 0, is equal to getting result stats such as count // https://www.elastic.co/guide/en/elasticsearch/reference/5.6/breaking_50_search_changes.html#_literal_search_type_literal $count = $this->createCommand($db)->search(['size' => 0])['hits']['total']; @@ -456,7 +482,7 @@ public function count($q = '*', $db = null) * @param Connection $db the database connection used to execute the query. * If this parameter is not given, the `elasticsearch` application * component will be used. - * @return boolean whether the query result contains any row of data. + * @return bool whether the query result contains any row of data. */ public function exists($db = null) { @@ -497,7 +523,7 @@ public function highlight($highlight) * @param string|array $options the configuration options for this * aggregation. Can be an array or a json string. * @return $this the query object itself - * @see http://www.elastic.co/guide/en/elasticsearch/reference/1.x/search-aggregations.html + * @see http://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html */ public function addAggregation($name, $type, $options) { @@ -516,7 +542,7 @@ public function addAggregation($name, $type, $options) * @param string|array $options the configuration options for this * aggregation. Can be an array or a json string. * @return $this the query object itself - * @see http://www.elastic.co/guide/en/elasticsearch/reference/1.x/search-aggregations.html + * @see http://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html */ public function addAgg($name, $type, $options) { @@ -571,7 +597,7 @@ public function addCollapse($collapse) /** * Sets the query part of this search query. * @param string|array $query - * @return $this the query object itself. + * @return $this the query object itself */ public function query($query) { @@ -729,7 +755,7 @@ public function source($source) /** * Sets the search timeout. - * @param integer $timeout A search timeout, bounding the search request to + * @param int $timeout A search timeout, bounding the search request to * be executed within the specified time value and bail with the hits * accumulated up to that point when expired. Defaults to no timeout. * @return $this the query object itself diff --git a/README.md b/README.md index bba086753..484c65fde 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ -Elasticsearch Query and ActiveRecord for Yii 2 -============================================== +

+ + + +

Elasticsearch Query and ActiveRecord for Yii 2

+
+

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 @@ -18,28 +23,21 @@ 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.0.x works with elasticsearch version 1.6.0 to 1.7.6. - Extension version 2.1.x requires at least elasticsearch version 5.0. +Note: to provide the work necessary a requests to the delete by query, in elasticsearch [since version 2.0](https://www.elastic.co/guide/en/elasticsearch/reference/1.7/docs-delete-by-query.html), you should to install the plugin [delete-by-query](https://www.elastic.co/guide/en/elasticsearch/plugins/2.3/plugins-delete-by-query.html) + Installation ------------ -The preferred way to install this extension is through [composer](http://getcomposer.org/download/). +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:"~2.1.0" +composer require --prefer-dist yiisoft/yii2-elasticsearch:"~2.1.0" ``` -or add - -```json -"yiisoft/yii2-elasticsearch": "~2.1.0" -``` - -to the require section of your composer.json. - Configuration ------------- diff --git a/docs/guide-ja/README.md b/docs/guide-ja/README.md index 4d6c44f60..24e899eb4 100644 --- a/docs/guide-ja/README.md +++ b/docs/guide-ja/README.md @@ -2,7 +2,8 @@ Yii 2.0 elasticsearch エクステンション ====================================== このエクステンションは、Yii 2 フレームワークに対する [elasticsearch](https://www.elastic.co/products/elasticsearch) の統合を提供します。 -基本的なクエリや検索をサポートするとともに、`ActiveRecord` パターンを実装して、アクティブレコードを elasticsearch に保存することを可能にしています。 +基本的なクエリや検索をサポートするとともに、`ActiveRecord` パターンを実装して、 +アクティブレコードを elasticsearch に保存することを可能にしています。 始めよう -------- @@ -14,11 +15,11 @@ Yii 2.0 elasticsearch エクステンション * [データのマッピングとインデクシング](mapping-indexing.md) * [クエリを使う](usage-query.md) * [アクティブレコードを使う](usage-ar.md) -* [データプロバイダと連携する](usage-data-providers.md) +* [データ・プロバイダを扱う](usage-data-providers.md) 追加のトピック -------------- -* [Elasticsearch DebugPanel を使う](topics-debug.md) -* プライマリキーが属性に含まれていないレコードとのリレーションの定義 +* [Elasticsearch デバッグ・パネルを使う](topics-debug.md) +* プライマリ・キーが属性に含まれていないレコードとのリレーションの定義 * さまざまな index/type からレコードを取得する diff --git a/docs/guide-ja/installation.md b/docs/guide-ja/installation.md index be1c71b46..c188f9ed1 100644 --- a/docs/guide-ja/installation.md +++ b/docs/guide-ja/installation.md @@ -15,12 +15,14 @@ Elasticsearch バージョン 1.0 以降が必要です。 php composer.phar require --prefer-dist yiisoft/yii2-elasticsearch ``` -または、あなたの `composer.json` ファイルの `require` セクションに、下記を追加してください。 +または、あなたの `composer.json` ファイルの `require` セクションに、 ```json "yiisoft/yii2-elasticsearch": "~2.0.0" ``` +を追加してください。 + ## アプリケーションを構成する このエクステンションを使用するためには、アプリケーションの構成情報で `Connection` クラスを構成する必要があります。 @@ -39,3 +41,13 @@ return [ ] ]; ``` + +この接続は elasticsearch クラスタの自動的な検出をサポートしており、自動検出はデフォルトで有効になっています。 +全てのクラスタ・ノードを手作業で指定する必要はありません。 +Yii は、デフォルトで他のクラスタ・ノードを検出して、ランダムに選ばれたノードに接続します。 +この機能は [[yii\elasticsearch\Connection::$autodetectCluster]] を `false` に設定することによって無効化することが出来ます。 + +クラスタの自動検出が正しく働くためには、設定情報で指定されたノードに対する `GET / _nodes` リクエストに対して、 +各ノードの `http_address` フィールドが返されなければならないことに留意して下さい。 +このフィールドは、デフォルトでは、素の elasticsearch インスタンスによって返される筈のものですが、AWS のような環境では取得できないことが報告されています。 +そのような場合には、クラスタの自動検出を無効にして、ホストを手作業で指定しなければなりません。 diff --git a/docs/guide-ja/mapping-indexing.md b/docs/guide-ja/mapping-indexing.md index b9a7b6438..8ab07d465 100644 --- a/docs/guide-ja/mapping-indexing.md +++ b/docs/guide-ja/mapping-indexing.md @@ -3,9 +3,7 @@ ## インデックスとマッピングを生成する -ElasticSearch のマッピングを漸進的に更新することは常に可能であるとは限りません。 -ですから、あなたのモデルの中に、インデックスの生成と更新を扱ういくつかの静的なメソッドを作っておくというのは、良いアイデアです。 -どのようにすればそれが出来るかの一例を次に示します。 +ElasticSearch のマッピングを漸進的に更新することは常に可能であるとは限りません。ですから、あなたのモデルの中に、インデックスの生成と更新を扱ういくつかの静的なメソッドを作っておくというのは、良いアイデアです。どのようにすればそれが出来るかの一例を次に示します。 ```php Class Book extends yii\elasticsearch\ActiveRecord @@ -50,7 +48,7 @@ Class Book extends yii\elasticsearch\ActiveRecord $db = static::getDb(); $command = $db->createCommand(); $command->createIndex(static::index(), [ - 'settings' => [ /* ... */ ], + //'settings' => [ /* ... */ ], 'mappings' => static::mapping(), //'warmers' => [ /* ... */ ], //'aliases' => [ /* ... */ ], @@ -70,11 +68,9 @@ Class Book extends yii\elasticsearch\ActiveRecord } ``` -適切なマッピングでインデックスを生成するためには、`Book::createIndex()` を呼びます。 -マッピングの更新を許すような仕方でマッピングを変更した場合 (例えば、新しいプロパティを作成した場合など) は、`Book::updateMapping()` を呼びます。 +適切なマッピングでインデックスを生成するためには、`Book::createIndex()` を呼びます。マッピングの更新を許すような仕方でマッピングを変更した場合 (例えば、新しいプロパティを作成した場合など) は、`Book::updateMapping()` を呼びます。 -しかし、プロパティを変更した場合 (例えば、`string` から `date` に変えた場合など) は、ElasticSearch はマッピングを更新することが出来ません。 -この場合は、インデックスを削除し (`Book::deleteIndex()` を呼びます)、更新されたマッピングでインデックスを新規に作成し (`Book::createIndex()` を呼びます)、そして、データを投入しなければなりません。 +しかし、プロパティを変更した場合 (例えば、`string` から `date` に変えた場合など) は、ElasticSearch はマッピングを更新することが出来ません。この場合は、インデックスを削除し (`Book::deleteIndex()` を呼びます)、更新されたマッピングでインデックスを新規に作成し (`Book::createIndex()` を呼びます)、そして、データを投入しなければなりません。 ## インデクシング -TBD \ No newline at end of file +TBD diff --git a/docs/guide-ja/topics-debug.md b/docs/guide-ja/topics-debug.md index c27d6a7d7..9b5ed9be2 100644 --- a/docs/guide-ja/topics-debug.md +++ b/docs/guide-ja/topics-debug.md @@ -1,11 +1,12 @@ -Elasticsearch DebugPanel を使う -------------------------------- +Elasticsearch デバッグ・パネルを使う +------------------------------------ -Yii 2 elasticsearch エクステンションは、yii のデバッグモジュールと統合可能な `DebugPanel` を提供しています。 -これは、実行された elasticsearch のクエリを表示するだけでなく、クエリを実行して結果を表示することも出来ます。 +Yii 2 elasticsearch エクステンションは、yii のデバッグ・モジュールと統合可能な `DebugPanel` を提供しています。 +これは、実行された elasticsearch のクエリを表示するだけでなく、 +クエリを実行して結果を表示することも出来ます。 `DebugPanel` を有効にするためには、下記の構成をアプリケーションの構成情報に追加してください -(デバッグモジュールを既に有効にしている場合は、パネルの構成情報を追加するだけで十分です)。 +(デバッグ・モジュールを既に有効にしている場合は、パネルの構成情報を追加するだけで十分です)。 ```php // ... diff --git a/docs/guide-ja/usage-ar.md b/docs/guide-ja/usage-ar.md index b02889a33..72402d760 100644 --- a/docs/guide-ja/usage-ar.md +++ b/docs/guide-ja/usage-ar.md @@ -3,13 +3,15 @@ Yii のアクティブレコードの使用方法に関する一般的な情報については、[ガイド](https://github.com/yiisoft/yii2/blob/master/docs/guide-ja/db-active-record.md) を参照してください。 -Elasticsearch のアクティブレコードを定義するためには、あなたのレコードクラスを [[yii\elasticsearch\ActiveRecord]] から拡張して、最低限、レコードの属性を定義するための [[yii\elasticsearch\ActiveRecord::attributes()|attributes()]] メソッドを実装する必要があります。 -Elasticsearch ではプライマリキーの扱いが通常と異なります。 -というのは、プライマリキー (elasticsearch の用語では `_id` フィールド) が、デフォルトでは属性のうちに入らないからです。 -ただし、`_id` フィールドを属性に含めるための [パスマッピング](http://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-id-field.html) を定義することは出来ます。 -パスマッピングの定義の仕方については、[elasticsearch のドキュメント](http://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-id-field.html) を参照してください。 -document または record の `_id` フィールドは、[[yii\elasticsearch\ActiveRecord::getPrimaryKey()|getPrimaryKey()]] および [[yii\elasticsearch\ActiveRecord::setPrimaryKey()|setPrimaryKey()]] を使ってアクセスすることが出来ます。 -パスマッピングが定義されている場合は、[[yii\elasticsearch\ActiveRecord::primaryKey()|primaryKey()]] メソッドを使って属性の名前を定義することが出来ます。 +Elasticsearch のアクティブレコードを定義するためには、あなたのレコード・クラスを [[yii\elasticsearch\ActiveRecord]] から拡張して、 +最低限、レコードの属性を定義するための [[yii\elasticsearch\ActiveRecord::attributes()|attributes()]] メソッドを実装する必要があります。 +Elasticsearch ではプライマリ・キーの扱いが通常と異なります。 +というのは、プライマリ・キー (elasticsearch の用語では `_id` フィールド) が、デフォルトでは属性のうちに入らないからです。 +ただし、`_id` フィールドを属性に含めるための [パス・マッピング](http://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-id-field.html) を定義することは出来ます。 +パス・マッピングの定義の仕方については、[elasticsearch のドキュメント](http://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-id-field.html) を参照してください。 +document または record の `_id` フィールドは、[[yii\elasticsearch\ActiveRecord::getPrimaryKey()|getPrimaryKey()]] +および [[yii\elasticsearch\ActiveRecord::setPrimaryKey()|setPrimaryKey()]] を使ってアクセスすることが出来ます。 +パス・マッピングが定義されている場合は、[[yii\elasticsearch\ActiveRecord::primaryKey()|primaryKey()]] メソッドを使って属性の名前を定義することが出来ます。 以下は `Customer` と呼ばれるモデルの例です。 @@ -21,13 +23,12 @@ class Customer extends \yii\elasticsearch\ActiveRecord */ public function attributes() { - // '_id' に対するパスマッピングis setup to field 'id' + // '_id' に対するパス・マッピングは 'id' フィールドに設定される return ['id', 'name', 'address', 'registration_date']; } /** - * @return ActiveQuery Order レコード へのリレーションを定義 - * (Order は他のデータベース、例えば、redis や通常の SQLDB にあっても良い) + * @return ActiveQuery Order レコード へのリレーションを定義 (Order は他のデータベース、例えば、redis や通常の SQLDB にあっても良い) */ public function getOrders() { @@ -44,24 +45,30 @@ class Customer extends \yii\elasticsearch\ActiveRecord } ``` -[[yii\elasticsearch\ActiveRecord::index()|index()]] と [[yii\elasticsearch\ActiveRecord::type()|type()]] をオーバーライドして、このレコードが表すインデックスとタイプを定義することが出来ます。 +[[yii\elasticsearch\ActiveRecord::index()|index()]] と [[yii\elasticsearch\ActiveRecord::type()|type()]] をオーバーライドして、 +このレコードが表すインデックスとタイプを定義することが出来ます。 -elasticsearch のアクティブレコードの一般的な使用方法は、[ガイド](https://github.com/yiisoft/yii2/blob/master/docs/guide-ja/active-record.md) で説明されたデータベースのアクティブレコードの場合と非常によく似ています。 -以下の制限と拡張 (*!*) があることを除けば、同じインターフェイスと機能をサポートしています。 +elasticsearch のアクティブレコードの一般的な使用方法は、[ガイド](https://github.com/yiisoft/yii2/blob/master/docs/guide-ja/active-record.md) +で説明されたデータベースのアクティブレコードの場合と非常によく似ています。 +以下の制限と拡張 (*!*) があることを除けば、同じインタフェイスと機能をサポートしています。 - elasticsearch は SQL をサポートしていないため、クエリの API は `join()`、`groupBy()`、`having()` および `union()` をサポートしません。 並べ替え、リミット、オフセット、条件付き WHERE は、すべてサポートされています。 - [[yii\elasticsearch\ActiveQuery::from()|from()]] はテーブルを選択しません。 - そうではなく、クエリ対象の [インデックス](http://www.elastic.co/guide/en/elasticsearch/reference/current/glossary.html#glossary-index) と [タイプ](http://www.elastic.co/guide/en/elasticsearch/reference/current/glossary.html#glossary-type) を選択します。 + そうではなく、クエリ対象の [インデックス](http://www.elastic.co/guide/en/elasticsearch/reference/current/glossary.html#glossary-index) と + [タイプ](http://www.elastic.co/guide/en/elasticsearch/reference/current/glossary.html#glossary-type) を選択します。 - `select()` は [[yii\elasticsearch\ActiveQuery::fields()|fields()]] に置き換えられています。 基本的には同じことをするものですが、`fields` の方が elasticsearch の用語として相応しいでしょう。 ドキュメントから取得するフィールドを定義します。 - Elasticsearch にはテーブルがありませんので、テーブルを通じての [[yii\elasticsearch\ActiveQuery::via()|via]] リレーションは定義することが出来ません。 -- Elasticsearch はデータストレージであると同時に検索エンジンでもありますので、当然ながら、レコードの検索に対するサポートが追加されています。 - Elasticsearch のクエリを構成するための [[yii\elasticsearch\ActiveQuery::query()|query()]]、[[yii\elasticsearch\ActiveQuery::filter()|filter()]] そして [[yii\elasticsearch\ActiveQuery::addFacet()|addFacet()]] というメソッドがあります。 +- Elasticsearch はデータ・ストレージであると同時に検索エンジンでもありますので、当然ながら、レコードの検索に対するサポートが追加されています。 + Elasticsearch のクエリを構成するための [[yii\elasticsearch\ActiveQuery::query()|query()]]、 + [[yii\elasticsearch\ActiveQuery::filter()|filter()]] そして + [[yii\elasticsearch\ActiveQuery::addFacet()|addFacet()]] というメソッドがあります。 これらがどのように働くかについて、下の使用例を見てください。 - また、`query` と `filter` の部分を構成する方法については、[クエリ DSL](http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html) を参照してください。 -- Elasticsearch のアクティブレコードから通常のアクティブレコードクラスへのリレーションを定義することも可能です。また、その逆も可能です。 + また、`query` と `filter` の部分を構成する方法については、[クエリ DSL](http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html) + を参照してください。 +- Elasticsearch のアクティブレコードから通常のアクティブレコード・クラスへのリレーションを定義することも可能です。また、その逆も可能です。 > Note: デフォルトでは、elasticsearch は、どんなクエリでも、返されるレコードの数を 10 に限定しています。 > もっと多くのレコードを取得することを期待する場合は、リレーションの定義で上限を明示的に指定しなければなりません。 @@ -104,11 +111,8 @@ $query->search(); // 全てのレコード、および、visit_count フィー ## 複雑なクエリ -どのようなクエリでも、ElasticSearch のクエリ DSL を使って作成して `ActiveRecord::query()` メソッドに渡すことが出来ます。 -しかし、ES のクエリ DSL は冗長さで悪名高いものです。 -長すぎるクエリは、すぐに管理できないものになってしまいます。 -クエリをもっと保守しやすくする方法があります。 -SQL ベースの `ActiveRecord` のために定義されているようなクエリクラスを定義することから始めましょう。 +どのようなクエリでも、ElasticSearch のクエリ DSL を使って作成して `ActiveRecord::query()` メソッドに渡すことが出来ます。しかし、ES のクエリ DSL は冗長さで悪名高いものです。長すぎるクエリは、すぐに管理できないものになってしまいます。 +クエリをもっと保守しやすくする方法があります。SQL ベースの `ActiveRecord` のために定義されているようなクエリクラスを定義することから始めましょう。 ```php class CustomerQuery extends ActiveQuery @@ -134,7 +138,7 @@ class CustomerQuery extends ActiveQuery ``` -こうすれば、これらのクエリコンポーネントを、結果となるクエリやフィルタを組み上げるために使用することが出来ます。 +こうすれば、これらのクエリ・コンポーネントを、結果となるクエリやフィルタを組み上げるために使用することが出来ます。 ```php $customers = Customer::find()->filter([ @@ -154,11 +158,9 @@ $customers = Customer::find()->filter([ ## 集合 (Aggregations) -[集合フレームワーク](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html) が、検索クエリに基づいた集合データを提供するのを助けてくれます。 -これは集合 (aggregation) と呼ばれる単純な構成要素に基づくもので、複雑なデータの要約を構築するために作成することが出来るものです。 +[集合フレームワーク](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html) が、検索クエリに基づいた集合データを提供するのを助けてくれます。これは集合 (aggregation) と呼ばれる単純な構成要素に基づくもので、複雑なデータの要約を構築するために作成することが出来るものです。 -以前に定義された `Customer` クラスを使って、毎日何人の顧客が登録されているかを検索しましょう。 -そうするために `terms` 集合を使います。 +以前に定義された `Customer` クラスを使って、毎日何人の顧客が登録されているかを検索しましょう。そうするために `terms` 集合を使います。 ```php @@ -170,8 +172,7 @@ $aggData = Customer::find()->addAggregation('customers_by_date', 'terms', [ ``` -この例では、集合の結果だけを特にリクエストしています。 -データを更に処理するために次のコードを使います。 +この例では、集合の結果だけを特にリクエストしています。データを更に処理するために次のコードを使います。 ```php $customersByDate = ArrayHelper::map($aggData['aggregations']['customers_by_date']['buckets'], 'key', 'doc_count'); diff --git a/docs/guide-ja/usage-data-providers.md b/docs/guide-ja/usage-data-providers.md index 409b98709..4bb7b6e04 100644 --- a/docs/guide-ja/usage-data-providers.md +++ b/docs/guide-ja/usage-data-providers.md @@ -1,5 +1,5 @@ -データプロバイダと連携する -========================== +データ・プロバイダを扱う +======================== [[\yii\elasticsearch\Query]] や [[\yii\elasticsearch\ActiveQuery]] を [[\yii\data\ActiveDataProvider]] で使用することが出来ます。 @@ -33,8 +33,7 @@ $models = $provider->getModels(); ただし、ページネーションを有効にして [[\yii\data\ActiveDataProvider]] を使用するのは非効率的です。 何故なら、ページネーションのためには、総アイテム数を取得するための余計なクエリが追加で必要になるからです。 -また、クエリの集合 (Aggregations) の結果にアクセスすることも出来ません。 -代りに、 `yii\elasticsearch\ActiveDataProvider` を使うことが出来ます。 +また、クエリの集合 (Aggregations) の結果にアクセスすることも出来ません。代りに、 `yii\elasticsearch\ActiveDataProvider` を使うことが出来ます。 こちらであれば、'meta' 情報のクエリを使って総アイテム数を準備したり、集合の結果を取得したりすることが出来ます。 ```php diff --git a/docs/guide-ru/README.md b/docs/guide-ru/README.md new file mode 100644 index 000000000..92d351042 --- /dev/null +++ b/docs/guide-ru/README.md @@ -0,0 +1,22 @@ +Расширение Elasticsearch для Yii 2 +================================= + +Расширение обеспечивает интеграцию [Elasticsearch](https://www.elastic.co/products/elasticsearch) в фреймворк Yii2. +Включает в себя базовую поддержку запросов/поиска, а также реализует шаблон `ActiveRecord`, который позволяет сохранять активные записи в Elasticsearch. + +Как начать +---------- +* [Установка](installation.md) + +Использование +------------- +* [Сопоставление данных и индексация](mapping-indexing.md) +* [Использование Query](usage-query.md) +* [Использование ActiveRecord](usage-ar.md) +* [Работа с провайдерами данных](usage-data-providers.md) + +Дополнительно +------------- +* [Использование Elasticsearch DebugPanel](topics-debug.md) +* Определение отношений с записями, чьи первичные ключи не являются частью атрибутов +* Получение записей из разных индексов/типов diff --git a/docs/guide-ru/images/debug.png b/docs/guide-ru/images/debug.png new file mode 100644 index 000000000..8877a604a Binary files /dev/null and b/docs/guide-ru/images/debug.png differ diff --git a/docs/guide-ru/installation.md b/docs/guide-ru/installation.md new file mode 100644 index 000000000..0a7990e36 --- /dev/null +++ b/docs/guide-ru/installation.md @@ -0,0 +1,49 @@ +Установка +============ + +## Требования + +Требуется версия Elasticsearch 1.0 или выше. + +## Получение с помощью Composer + +Предпочтительный способ установки расширения через [composer](http://getcomposer.org/download/). + +Для этого запустите +``` +php composer.phar require --prefer-dist yiisoft/yii2-elasticsearch +``` + +или добавьте + +```json +"yiisoft/yii2-elasticsearch": "~2.0.0" +``` + +в секцию **require** вашего composer.json. + +## Настройка приложения + +Для использования расширения, просто добавьте этот код в конфигурацию вашего приложения: + +```php +return [ + //.... + 'components' => [ + 'elasticsearch' => [ + 'class' => 'yii\elasticsearch\Connection', + 'nodes' => [ + ['http_address' => '127.0.0.1:9200'], + //настройте несколько хостов, если у вас есть кластер + ], + ], + ] +]; +``` + +Соединение поддерживает автоматическое обнаружение кластера Elasticsearch, который включен по умолчанию. +Вам не нужно указывать все узлы кластера вручную, Yii будет обнаруживать другие узлы кластера и подключаться к случайно выбранному узлу по умолчанию. Вы можете отключить эту функцию, установив [[yii\elasticsearch\Connection::$autodetectCluster]] в `false`. + +> **NOTE:** для корректной работы автоматического обнаружения кластера, запрос узлов `GET /_nodes` должен возвращать поле `http_address` для каждого узла. +По умолчанию они возвращаются настоящими экземплярами Elasticsearch, но, они недоступны в таких средах как AWS. +В этом случае вам необходимо отключить обнаружение кластера и указать хосты вручную. \ No newline at end of file diff --git a/docs/guide-ru/mapping-indexing.md b/docs/guide-ru/mapping-indexing.md new file mode 100644 index 000000000..1ff0e7944 --- /dev/null +++ b/docs/guide-ru/mapping-indexing.md @@ -0,0 +1,76 @@ +Сопоставление и индексация +================== + +## Создание индексов и сопоставления + +Так как не всегда возможно обновлять сопоставления ElasticSearch поэтапно, рекомендуется создать несколько статических методов в вашей модели, которые занимаются созданием и обновлением индекса. Вот пример того, как это можно сделать. + +```php +class Book extends yii\elasticsearch\ActiveRecord +{ + //Другие атрибуты и методы класса идут здесь + // ... + + /** + * @return array Сопоставление для этой модели + */ + public static function mapping() + { + return [ + static::type() => [ + 'properties' => [ + 'name' => ['type' => 'string'], + 'author_name' => ['type' => 'string'], + 'publisher_name' => ['type' => 'string'], + 'created_at' => ['type' => 'long'], + 'updated_at' => ['type' => 'long'], + 'status' => ['type' => 'long'], + ] + ], + ]; + } + + /** + * Установка (update) для этой модели + */ + public static function updateMapping() + { + $db = static::getDb(); + $command = $db->createCommand(); + $command->setMapping(static::index(), static::type(), static::mapping()); + } + + /** + * Создать индекс этой модели + */ + public static function createIndex() + { + $db = static::getDb(); + $command = $db->createCommand(); + $command->createIndex(static::index(), [ + 'settings' => [ /* ... */ ], + 'mappings' => static::mapping(), + //'warmers' => [ /* ... */ ], + //'aliases' => [ /* ... */ ], + //'creation_date' => '...' + ]); + } + + /** + * Удалить индекс этой модели + */ + public static function deleteIndex() + { + $db = static::getDb(); + $command = $db->createCommand(); + $command->deleteIndex(static::index(), static::type()); + } +} +``` + +Чтобы создать индекс с соответствующими сопоставлениями, вызовите `Book::createIndex()`. Если вы изменили сопоставление таким образом, чтобы оно отображало обновление (например, создало новое свойство), вызовите `Book::updateMapping()`. + +Однако, если вы изменили свойство (например, перешли от `string` к` date`), ElasticSearch не сможет обновить сопоставление. В этом случае вам нужно удалить свой индекс (путем вызова `Book::deleteIndex()`), создать его заново с обновленным сопоставлением (путем вызова `Book::createIndex()`) и затем повторно заполнить его данными. + +## Индексация +TBD \ No newline at end of file diff --git a/docs/guide-ru/topics-debug.md b/docs/guide-ru/topics-debug.md new file mode 100644 index 000000000..d2590764c --- /dev/null +++ b/docs/guide-ru/topics-debug.md @@ -0,0 +1,24 @@ +Использование Elasticsearch DebugPanel +---------------------------------- + +Расширение Yii2 Elasticsearch предоставляет `DebugPanel`, которая может быть интегрирована с модулем `yii debug`, и показывает выполненные запросы Elasticsearch. Оно также позволяет запускать эти запросы и просматривать результаты. + +Добавьте следующий код в конфигурацию приложения, чтобы включить его(если у вас уже включен модуль отладки, достаточно просто добавить конфигурацию в секцию `panels`): + +```php + // ... + 'bootstrap' => ['debug'], + 'modules' => [ + 'debug' => [ + 'class' => 'yii\\debug\\Module', + 'panels' => [ + 'elasticsearch' => [ + 'class' => 'yii\\elasticsearch\\DebugPanel', + ], + ], + ], + ], + // ... +``` + +![elasticsearch DebugPanel](images/debug.png) diff --git a/docs/guide-ru/usage-ar.md b/docs/guide-ru/usage-ar.md new file mode 100644 index 000000000..47d5626a9 --- /dev/null +++ b/docs/guide-ru/usage-ar.md @@ -0,0 +1,162 @@ +Использование ActiveRecord +====================== + +Для получения общей информации о том, как использовать yii ActiveRecord, пожалуйста, обратитесь к [руководству](https://github.com/yiisoft/yii2/blob/master/docs/guide/db-active-record.md). + +Для определения класса Elasticsearch ActiveRecord ваш класс должен быть расширен от [[yii\elasticsearch\ActiveRecord]] и реализовывать, по крайней мере, метод [[yii\elasticsearch\ActiveRecord::attributes()|attributes()]] для определения атрибутов записи. + +Обработка первичных ключей в Elasticsearch различна, поскольку первичный ключ (поле `_id` в терминах Elasticsearch) по умолчанию не является частью атрибутов. Однако можно определить [сопоставление пути](http://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-id-field.html) для поля `_id` чтобы стать частью атрибута. + +Смотри [документацию Elasticsearch](http://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-id-field.html) как определить это. Поле `_id` документа/записи можно получить используя [[yii\elasticsearch\ActiveRecord::getPrimaryKey()|getPrimaryKey()]] и [[yii\elasticsearch\ActiveRecord::setPrimaryKey()|setPrimaryKey()]]. Когда определено сопоставление пути, имя атрибута может быть определено с помощью метода [[yii\elasticsearch\ActiveRecord::primaryKey()|primaryKey()]]. + +Ниже приведен пример модели `Customer`: + +```php +class Customer extends \yii\elasticsearch\ActiveRecord +{ + /** + * @return array список атрибутов для этой записи + */ + public function attributes() + { + // path mapping for '_id' is setup to field 'id' + return ['id', 'name', 'address', 'registration_date']; + } + + /** + * @return ActiveQuery определение связи записи Order (может быть в другой базе данных, например redis или sql) + */ + public function getOrders() + { + return $this->hasMany(Order::className(), ['customer_id' => 'id'])->orderBy('id'); + } + + /** + * Определяет область, изменяющая `$query`, которая вернет только активных (status = 1) клиентов + */ + public static function active($query) + { + $query->andWhere(['status' => 1]); + } +} +``` + +Вы можете переопределить [[yii\elasticsearch\ActiveRecord::index()|index()]] и [[yii\elasticsearch\ActiveRecord::type()|type()]] чтобы определить индекс и тип этой записи. + +Общее использование, `Elasticsearch ActiveRecord` очень похоже на `database ActiveRecord`, описано в [руководстве](https://github.com/yiisoft/yii2/blob/master/docs/guide/active-record.md). +Он поддерживает тот же интерфейс и функции, за исключением следующих ограничений и дополнений(*!*): + +- Посколку Elasticsearch не поддерживает SQL, API запросов не поддреживает `join()`, `groupBy()`, `having()` и `union()`. + Сортировка, `limit`, `offset` и условия поддерживаются. +- [[yii\elasticsearch\ActiveQuery::from()|from()]] не выбирает таблицы, но [индекс](http://www.elastic.co/guide/en/elasticsearch/reference/current/glossary.html#glossary-index) и [тип](http://www.elastic.co/guide/en/elasticsearch/reference/current/glossary.html#glossary-type) запрашивают. +- `select()` был заменен на [[yii\elasticsearch\ActiveQuery::fields()|fields()]] который, в основном, делает тоже самое, но `fields` является более подходящим в терминологии Elasticsearch. Он определяет поля для извлечения из документа. +- [[yii\elasticsearch\ActiveQuery::via()|via]] - отношения не могут быть определены через таблицу, так как в Elasticsearch нет таблиц. Вы можете определять отношения только через другие записи. +- Поскольку Elasticsearch - это не только хранилище данных, но и поисковая система, была добавлена поддержка для поиска ваших записей. Есть [[yii\elasticsearch\ActiveQuery::query()|query()]], [[yii\elasticsearch\ActiveQuery::filter()|filter()]] и [[yii\elasticsearch\ActiveQuery::addFacet()|addFacet()]] методы, которые позволяют составить запрос в Elasticsearch. См. пример использования ниже, как они работают, и проверьте [Query DSL](http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html) о том как составлять части `query` и `filter`. +- Также можно определить отношения от Elasticsearch ActiveRecords до обычных классов ActiveRecord и наоборот. + +> **NOTE:** Elasticsearch ограничивает количество записей, возвращаемых любым запросом, до 10 записей по умолчанию. +> Если вы ожидаете получить больше записей, вы должны явно указать ограничение в запросе а также определить отношения. +> Это также важно для отношений, которые используют `via()`, так что если записи `via` ограничены 10-ю, записей отношения также может быть не более 10-и. + +Пример использования: + +```php +$customer = new Customer(); +$customer->primaryKey = 1; // в этом случае эквивалентно $customer->id = 1; +$customer->attributes = ['name' => 'test']; +$customer->save(); + +$customer = Customer::get(1); // получить запись по первичному ключу +$customers = Customer::mget([1,2,3]); // получитть множественные записи по первичному ключу +$customer = Customer::find()->where(['name' => 'test'])->one(); // найти по запросу. Обратите внимание, вам необходимо настроить сопоставление для этого поля, чтобы правильно найти запись +$customers = Customer::find()->active()->all(); // найти все по запросу (используя область видимости `active`) + +// http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html +$result = Article::find()->query(["match" => ["title" => "yii"]])->all(); // статьи название которых содержит "yii" + +// http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-flt-query.html +$query = Article::find()->query([ + "fuzzy_like_this" => [ + "fields" => ["title", "description"], + "like_text" => "This query will return articles that are similar to this text :-)", + "max_query_terms" => 12 + ] +]); + +$query->all(); // вернет все документы +// вы можете добавить фасеты к вашему поиску: +$query->addStatisticalFacet('click_stats', ['field' => 'visit_count']); +$query->search(); // вернет все записи + статистику о поле visit_count. Например: среднее, сумма, мин, макс и т.д... +``` + +## Комплексные запросы + +Любой запрос может быть составлен с использованием запроса DSL ElasticSearch и передан методу `ActiveRecord::query()`. Однако DS-запрос известен своей многословностью, и эти запросы большего размера вскоре становятся неуправляемыми. +Есть способ сделать запросы более удобными. Начните с определения класса запросов так же, как это делается для SQL ActiveRecord. + +```php +class CustomerQuery extends ActiveQuery +{ + public static function name($name) + { + return ['match' => ['name' => $name]]; + } + + public static function address($address) + { + return ['match' => ['address' => $address]]; + } + + public static function registrationDateRange($dateFrom, $dateTo) + { + return ['range' => ['registration_date' => [ + 'gte' => $dateFrom, + 'lte' => $dateTo, + ]]]; + } +} + +``` + +Теперь вы можете использовать эти компоненты запроса для сборки результирующего запроса и/или фильтра. + +```php +$customers = Customer::find()->filter([ + CustomerQuery::registrationDateRange('2016-01-01', '2016-01-20'), +])->query([ + 'bool' => [ + 'should' => [ + CustomerQuery::name('John'), + CustomerQuery::address('London'), + ], + 'must_not' => [ + CustomerQuery::name('Jack'), + ], + ], +])->all(); +``` + +## Агрегирование + +[Фреймворк агрегирования](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html) +помогает предоставлять агрегированные данные на основе поискового запроса. Он основан на простых строительных блоках, называемых агрегатами, которые могут быть составлены для создания сложных сводок данных. + +Используя ранее определенный класс `Customer`, давайте выясним, сколько клиентов регистрировалось каждый день. Для этого мы используем агрегацию `terms`. + + +```php +$aggData = Customer::find()->addAggregation('customers_by_date', 'terms', [ + 'field' => 'registration_date', + 'order' => ['_count' => 'desc'], + 'size' => 10, //top 10 registration dates +])->search(null, ['search_type' => 'count']); + +``` + +В этом примере мы специально запрашиваем только результаты агрегации. Следующий код обрабатывает данные. + +```php +$customersByDate = ArrayHelper::map($aggData['aggregations']['customers_by_date']['buckets'], 'key', 'doc_count'); +``` + +Теперь `$customersByDate` содержит 10 дат, которые соответствуют наибольшему числу зарегистрированных пользователей. \ No newline at end of file diff --git a/docs/guide-ru/usage-data-providers.md b/docs/guide-ru/usage-data-providers.md new file mode 100644 index 000000000..3e06d3d90 --- /dev/null +++ b/docs/guide-ru/usage-data-providers.md @@ -0,0 +1,52 @@ +Работа с провайдерами данных +=========================== + +Вы можете использовать [[\yii\data\ActiveDataProvider]] с [[\yii\elasticsearch\Query]] и [[\yii\elasticsearch\ActiveQuery]]: + +```php +use yii\data\ActiveDataProvider; +use yii\elasticsearch\Query; + +$query = new Query(); +$query->from('yiitest', 'user'); +$provider = new ActiveDataProvider([ + 'query' => $query, + 'pagination' => [ + 'pageSize' => 10, + ] +]); +$models = $provider->getModels(); +``` + +```php +use yii\data\ActiveDataProvider; +use app\models\User; + +$provider = new ActiveDataProvider([ + 'query' => User::find(), + 'pagination' => [ + 'pageSize' => 10, + ] +]); +$models = $provider->getModels(); +``` + +Однако использование [[\yii\data\ActiveDataProvider]] с включенным разбиением на страницы неэффективно, так как для выполнения вычисления дополнительных запросов требуется выполнить лишний дополнительный запрос. Также он не сможет предоставить вам доступ к результатам агрегирования запросов. Вместо этого вы можете использовать `yii\elasticsearch\ActiveDataProvider`. Это дает возможность формировать общее количество элементов с помощью запроса 'meta' - информации и извлечения результатов агрегирования: + +```php +use yii\elasticsearch\ActiveDataProvider; +use yii\elasticsearch\Query; + +$query = new Query(); +$query->from('yiitest', 'user') + ->addAggregation('foo', 'terms', []); +$provider = new ActiveDataProvider([ + 'query' => $query, + 'pagination' => [ + 'pageSize' => 10, + ] +]); +$models = $provider->getModels(); +$aggregations = $provider->getAggregations(); +$fooAggregation = $provider->getAggregation('foo'); +``` \ No newline at end of file diff --git a/docs/guide-ru/usage-query.md b/docs/guide-ru/usage-query.md new file mode 100644 index 000000000..29d4cf216 --- /dev/null +++ b/docs/guide-ru/usage-query.md @@ -0,0 +1,6 @@ +Использование Query +=============== + +TBD + +> **NOTE:** Elasticsearch, по умолчанию, ограничивает количество записей, возвращаемых любым запросом, до 10. Если вы ожидаете получить больше записей, вы должны указать ограничение явно в определении отношения. diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 090d0e862..9802c5201 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -3,25 +3,19 @@ Installation ## Requirements -Elasticsearch version 1.0 or higher is required. - -## Getting Composer package - -The preferred way to install this extension is through [composer](http://getcomposer.org/download/). - -Either run +Elasticsearch versions from 1.6.0 to 1.7.6. The following should be added to `config/elasticsearch.yml`: ``` -php composer.phar require --prefer-dist yiisoft/yii2-elasticsearch +script.disable_dynamic: false ``` -or add +## Getting Composer package + +The preferred way to install this extension is through [composer](http://getcomposer.org/download/): -```json -"yiisoft/yii2-elasticsearch": "~2.0.0" ``` - -to the require section of your composer.json. +composer require --prefer-dist yiisoft/yii2-elasticsearch +``` ## Configuring application @@ -43,3 +37,13 @@ return [ ] ]; ``` + +The connection supports auto detection of the elasticsearch cluster, which is enabled by default. +You do not need to specify all cluster nodes manually, Yii will detect other cluster nodes and connect to +a randomly selected node by default. You can disable this feature by setting [[yii\elasticsearch\Connection::$autodetectCluster]] +to `false`. + +Note that for cluster autodetection to work properly, the `GET /_nodes` request to the nodes +specified in the configuration must return the `http_address` field for each node. +This is returned by vanilla elasticsearch instances by default, but has been reported to not be available in environments like AWS. +In that case you need to disable cluster detection and specify hosts manually. diff --git a/docs/guide/mapping-indexing.md b/docs/guide/mapping-indexing.md index 90613b379..2700c9362 100644 --- a/docs/guide/mapping-indexing.md +++ b/docs/guide/mapping-indexing.md @@ -6,7 +6,7 @@ Mapping & Indexing Since it is not always possible to update ElasticSearch mappings incrementally, it is a good idea to create several static methods in your model that deal with index creation and updates. Here is one example of how this can be done. ```php -Class Book extends yii\elasticsearch\ActiveRecord +class Book extends yii\elasticsearch\ActiveRecord { // Other class attributes and methods go here // ... @@ -48,7 +48,7 @@ Class Book extends yii\elasticsearch\ActiveRecord $db = static::getDb(); $command = $db->createCommand(); $command->createIndex(static::index(), [ - 'settings' => [ /* ... */ ], + //'settings' => [ /* ... */ ], 'mappings' => static::mapping(), //'warmers' => [ /* ... */ ], //'aliases' => [ /* ... */ ], @@ -73,4 +73,4 @@ To create the index with proper mappings, call `Book::createIndex()`. If you hav However, if you have changed a property (e.g. went from `string` to `date`), ElasticSearch will not be able to update the mapping. In this case you need to delete your index (by calling `Book::deleteIndex()`), create it anew with updated mapping (by calling `Book::createIndex()`), and then repopulate it with data. ## Indexing -TBD \ No newline at end of file +TBD diff --git a/tests/ActiveDataProviderTest.php b/tests/ActiveDataProviderTest.php index 4415757d2..7b2867231 100644 --- a/tests/ActiveDataProviderTest.php +++ b/tests/ActiveDataProviderTest.php @@ -88,4 +88,17 @@ public function testActiveQuery() $models = $provider->getModels(); $this->assertEquals(1, count($models)); } -} \ No newline at end of file + + public function testNonexistentIndex() + { + $query = new Query(); + $query->from('nonexistent', 'nonexistent'); + + $provider = new ActiveDataProvider([ + 'query' => $query, + 'db' => $this->getConnection(), + ]); + $models = $provider->getModels(); + $this->assertEquals(0, count($models)); + } +} \ No newline at end of file diff --git a/tests/ActiveRecordTest.php b/tests/ActiveRecordTest.php index 0cf133f99..503cb8e28 100644 --- a/tests/ActiveRecordTest.php +++ b/tests/ActiveRecordTest.php @@ -222,7 +222,7 @@ public function testSearch() { $customers = Customer::find()->search()['hits']; $this->assertEquals(3, $customers['total']); - $this->assertEquals(3, count($customers['hits'])); + $this->assertCount(3, $customers['hits']); $this->assertTrue($customers['hits'][0] instanceof Customer); $this->assertTrue($customers['hits'][1] instanceof Customer); $this->assertTrue($customers['hits'][2] instanceof Customer); @@ -230,13 +230,13 @@ public function testSearch() // limit vs. totalcount $customers = Customer::find()->limit(2)->search()['hits']; $this->assertEquals(3, $customers['total']); - $this->assertEquals(2, count($customers['hits'])); + $this->assertCount(2, $customers['hits']); // asArray $result = Customer::find()->asArray()->search()['hits']; $this->assertEquals(3, $result['total']); $customers = $result['hits']; - $this->assertEquals(3, count($customers)); + $this->assertCount(3, $customers); $this->assertArrayHasKey('id', $customers[0]['_source']); $this->assertArrayHasKey('name', $customers[0]['_source']); $this->assertArrayHasKey('email', $customers[0]['_source']); @@ -291,14 +291,14 @@ public function testMget() $this->assertEquals([], Customer::mget([])); $records = Customer::mget([1]); - $this->assertEquals(1, count($records)); + $this->assertCount(1, $records); $this->assertInstanceOf(Customer::className(), reset($records)); $records = Customer::mget([5]); - $this->assertEquals(0, count($records)); + $this->assertCount(0, $records); $records = Customer::mget([1, 3, 5]); - $this->assertEquals(2, count($records)); + $this->assertCount(2, $records); $this->assertInstanceOf(Customer::className(), $records[0]); $this->assertInstanceOf(Customer::className(), $records[1]); } @@ -308,21 +308,21 @@ public function testFindLazy() /* @var $customer Customer */ $customer = Customer::findOne(2); $orders = $customer->orders; - $this->assertEquals(2, count($orders)); + $this->assertCount(2, $orders); $orders = $customer->getOrders()->where(['between', 'created_at', 1325334000, 1325400000])->all(); - $this->assertEquals(1, count($orders)); + $this->assertCount(1, $orders); $this->assertEquals(2, $orders[0]->id); } public function testFindEagerViaRelation() { $orders = Order::find()->with('items')->orderBy('created_at')->all(); - $this->assertEquals(3, count($orders)); + $this->assertCount(3, $orders); $order = $orders[0]; $this->assertEquals(1, $order->id); $this->assertTrue($order->isRelationPopulated('items')); - $this->assertEquals(2, count($order->items)); + $this->assertCount(2, $order->items); $this->assertEquals(1, $order->items[0]->id); $this->assertEquals(2, $order->items[1]->id); } @@ -456,7 +456,7 @@ public function testFindAsArraySourceFilter() /* @var $this TestCase|ActiveRecordTestTrait */ // indexBy + asArray $customers = Customer::find()->asArray()->source(['id', 'name'])->all(); - $this->assertEquals(3, count($customers)); + $this->assertCount(3, $customers); $this->assertArrayHasKey('id', $customers[0]['_source']); $this->assertArrayHasKey('name', $customers[0]['_source']); $this->assertArrayNotHasKey('email', $customers[0]['_source']); @@ -480,7 +480,7 @@ public function testFindIndexBySource() /* @var $this TestCase|ActiveRecordTestTrait */ // indexBy + asArray $customers = Customer::find()->indexBy('name')->source('id', 'name')->all(); - $this->assertEquals(3, count($customers)); + $this->assertCount(3, $customers); $this->assertTrue($customers['user1'] instanceof $customerClass); $this->assertTrue($customers['user2'] instanceof $customerClass); $this->assertTrue($customers['user3'] instanceof $customerClass); @@ -504,7 +504,7 @@ public function testFindIndexBySource() $customers = Customer::find()->indexBy(function ($customer) { return $customer->id . '-' . $customer->name; })->storedFields('id', 'name')->all(); - $this->assertEquals(3, count($customers)); + $this->assertCount(3, $customers); $this->assertTrue($customers['1-user1'] instanceof $customerClass); $this->assertTrue($customers['2-user2'] instanceof $customerClass); $this->assertTrue($customers['3-user3'] instanceof $customerClass); @@ -530,7 +530,7 @@ public function testFindIndexByAsArrayFields() /* @var $this TestCase|ActiveRecordTestTrait */ // indexBy + asArray $customers = Customer::find()->indexBy('name')->asArray()->storedFields('id', 'name')->all(); - $this->assertEquals(3, count($customers)); + $this->assertCount(3, $customers); $this->assertArrayHasKey('id', $customers['user1']['fields']); $this->assertArrayHasKey('name', $customers['user1']['fields']); $this->assertArrayNotHasKey('email', $customers['user1']['fields']); @@ -551,7 +551,7 @@ public function testFindIndexByAsArrayFields() $customers = Customer::find()->indexBy(function ($customer) { return reset($customer['fields']['id']) . '-' . reset($customer['fields']['name']); })->asArray()->storedFields('id', 'name')->all(); - $this->assertEquals(3, count($customers)); + $this->assertCount(3, $customers); $this->assertArrayHasKey('id', $customers['1-user1']['fields']); $this->assertArrayHasKey('name', $customers['1-user1']['fields']); $this->assertArrayNotHasKey('email', $customers['1-user1']['fields']); @@ -577,7 +577,7 @@ public function testFindIndexByAsArray() /* @var $this TestCase|ActiveRecordTestTrait */ // indexBy + asArray $customers = $customerClass::find()->asArray()->indexBy('name')->all(); - $this->assertEquals(3, count($customers)); + $this->assertCount(3, $customers); $this->assertArrayHasKey('id', $customers['user1']['_source']); $this->assertArrayHasKey('name', $customers['user1']['_source']); $this->assertArrayHasKey('email', $customers['user1']['_source']); @@ -598,7 +598,7 @@ public function testFindIndexByAsArray() $customers = $customerClass::find()->indexBy(function ($customer) { return $customer['_source']['id'] . '-' . $customer['_source']['name']; })->asArray()->all(); - $this->assertEquals(3, count($customers)); + $this->assertCount(3, $customers); $this->assertArrayHasKey('id', $customers['1-user1']['_source']); $this->assertArrayHasKey('name', $customers['1-user1']['_source']); $this->assertArrayHasKey('email', $customers['1-user1']['_source']); @@ -655,22 +655,22 @@ public function testFindEmptyPkCondition() $this->afterSave(); $orderItems = $orderItemClass::find()->where(['_id' => [$orderItem->getPrimaryKey()]])->all(); - $this->assertEquals(1, count($orderItems)); + $this->assertCount(1, $orderItems); $orderItems = $orderItemClass::find()->where(['_id' => []])->all(); - $this->assertEquals(0, count($orderItems)); + $this->assertCount(0, $orderItems); $orderItems = $orderItemClass::find()->where(['_id' => null])->all(); - $this->assertEquals(0, count($orderItems)); + $this->assertCount(0, $orderItems); $orderItems = $orderItemClass::find()->where(['IN', '_id', [$orderItem->getPrimaryKey()]])->all(); - $this->assertEquals(1, count($orderItems)); + $this->assertCount(1, $orderItems); $orderItems = $orderItemClass::find()->where(['IN', '_id', []])->all(); - $this->assertEquals(0, count($orderItems)); + $this->assertCount(0, $orderItems); $orderItems = $orderItemClass::find()->where(['IN', '_id', [null]])->all(); - $this->assertEquals(0, count($orderItems)); + $this->assertCount(0, $orderItems); } public function testArrayAttributes() @@ -684,7 +684,7 @@ public function testArrayAttributeRelationLazy() { $order = Order::findOne(1); $items = $order->itemsByArrayValue; - $this->assertEquals(2, count($items)); + $this->assertCount(2, $items); $this->assertTrue(isset($items[1])); $this->assertTrue(isset($items[2])); $this->assertTrue($items[1] instanceof Item); @@ -692,7 +692,7 @@ public function testArrayAttributeRelationLazy() $order = Order::findOne(2); $items = $order->itemsByArrayValue; - $this->assertEquals(3, count($items)); + $this->assertCount(3, $items); $this->assertTrue(isset($items[3])); $this->assertTrue(isset($items[4])); $this->assertTrue(isset($items[5])); @@ -707,7 +707,7 @@ public function testArrayAttributeRelationEager() $order = Order::find()->with('itemsByArrayValue')->where(['id' => 1])->one(); $this->assertTrue($order->isRelationPopulated('itemsByArrayValue')); $items = $order->itemsByArrayValue; - $this->assertEquals(2, count($items)); + $this->assertCount(2, $items); $this->assertTrue(isset($items[1])); $this->assertTrue(isset($items[2])); $this->assertTrue($items[1] instanceof Item); @@ -717,7 +717,7 @@ public function testArrayAttributeRelationEager() $order = Order::find()->with('itemsByArrayValue')->where(['id' => 2])->one(); $this->assertTrue($order->isRelationPopulated('itemsByArrayValue')); $items = $order->itemsByArrayValue; - $this->assertEquals(3, count($items)); + $this->assertCount(3, $items); $this->assertTrue(isset($items[3])); $this->assertTrue(isset($items[4])); $this->assertTrue(isset($items[5])); @@ -731,7 +731,7 @@ public function testArrayAttributeRelationLink() /* @var $order Order */ $order = Order::find()->where(['id' => 1])->one(); $items = $order->itemsByArrayValue; - $this->assertEquals(2, count($items)); + $this->assertCount(2, $items); $this->assertTrue(isset($items[1])); $this->assertTrue(isset($items[2])); @@ -740,7 +740,7 @@ public function testArrayAttributeRelationLink() $this->afterSave(); $items = $order->itemsByArrayValue; - $this->assertEquals(3, count($items)); + $this->assertCount(3, $items); $this->assertTrue(isset($items[1])); $this->assertTrue(isset($items[2])); $this->assertTrue(isset($items[5])); @@ -748,7 +748,7 @@ public function testArrayAttributeRelationLink() // check also after refresh $this->assertTrue($order->refresh()); $items = $order->itemsByArrayValue; - $this->assertEquals(3, count($items)); + $this->assertCount(3, $items); $this->assertTrue(isset($items[1])); $this->assertTrue(isset($items[2])); $this->assertTrue(isset($items[5])); @@ -759,7 +759,7 @@ public function testArrayAttributeRelationUnLink() /* @var $order Order */ $order = Order::find()->where(['id' => 1])->one(); $items = $order->itemsByArrayValue; - $this->assertEquals(2, count($items)); + $this->assertCount(2, $items); $this->assertTrue(isset($items[1])); $this->assertTrue(isset($items[2])); @@ -768,14 +768,14 @@ public function testArrayAttributeRelationUnLink() $this->afterSave(); $items = $order->itemsByArrayValue; - $this->assertEquals(1, count($items)); + $this->assertCount(1, $items); $this->assertTrue(isset($items[1])); $this->assertFalse(isset($items[2])); // check also after refresh $this->assertTrue($order->refresh()); $items = $order->itemsByArrayValue; - $this->assertEquals(1, count($items)); + $this->assertCount(1, $items); $this->assertTrue(isset($items[1])); $this->assertFalse(isset($items[2])); } diff --git a/tests/TestCase.php b/tests/TestCase.php index 960edf8e1..f7a821d6c 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -7,10 +7,16 @@ use yii\helpers\ArrayHelper; use Yii; +// backward compatibility +if (!class_exists('\PHPUnit\Framework\TestCase')) { + class_alias('\PHPUnit_Framework_TestCase', '\PHPUnit\Framework\TestCase'); +} + + /** * This is the base class for all yii framework unit tests. */ -abstract class TestCase extends \PHPUnit_Framework_TestCase +abstract class TestCase extends \PHPUnit\Framework\TestCase { public static $params; diff --git a/tests/data/ar/Order.php b/tests/data/ar/Order.php index b5fe2c981..7c5e08eb1 100644 --- a/tests/data/ar/Order.php +++ b/tests/data/ar/Order.php @@ -2,6 +2,7 @@ namespace yiiunit\extensions\elasticsearch\data\ar; +use yii\elasticsearch\ActiveQuery; use yii\elasticsearch\Command; /** @@ -11,6 +12,10 @@ * @property integer $customer_id * @property integer $created_at * @property string $total + * @property array $itemsArray + * + * @property-read Item[] $expensiveItemsUsingViaWithCallable + * @property-read Item[] $cheapItemsUsingViaWithCallable */ class Order extends ActiveRecord { @@ -48,6 +53,22 @@ public function getItems() ->via('orderItems')->orderBy('id'); } + public function getExpensiveItemsUsingViaWithCallable() + { + return $this->hasMany(Item::className(), ['id' => 'item_id']) + ->via('orderItems', function (ActiveQuery $q) { + $q->where(['>=', 'subtotal', 10]); + }); + } + + public function getCheapItemsUsingViaWithCallable() + { + return $this->hasMany(Item::className(), ['id' => 'item_id']) + ->via('orderItems', function (ActiveQuery $q) { + $q->where(['<', 'subtotal', 10]); + }); + } + public function getItemsIndexed() { return $this->hasMany(Item::className(), ['id' => 'item_id']) @@ -95,16 +116,6 @@ public function getBooksWithNullFK() ->where(['category_id' => 1]); } - public function beforeSave($insert) - { - if (parent::beforeSave($insert)) { -// $this->created_at = time(); - return true; - } else { - return false; - } - } - /** * sets up the index for this record * @param Command $command @@ -113,10 +124,10 @@ public static function setUpMapping($command) { $command->setMapping(static::index(), static::type(), [ static::type() => [ - "properties" => [ - "customer_id" => ["type" => "integer"], + 'properties' => [ + 'customer_id' => ['type' => 'integer'], // "created_at" => ["type" => "string", "index" => "not_analyzed"], - "total" => ["type" => "integer"], + 'total' => ['type' => 'integer'], ] ] ]);