From 5164a1671c777bbf64a1cb54d3a94f267476a8ab Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 25 Nov 2013 20:12:45 +0100 Subject: [PATCH] finalized Query interface + general cleanup --- extensions/elasticsearch/ActiveQuery.php | 72 ++++++--- extensions/elasticsearch/ActiveRecord.php | 3 +- extensions/elasticsearch/Cluster.php | 16 -- extensions/elasticsearch/Command.php | 13 +- extensions/elasticsearch/Connection.php | 2 +- extensions/elasticsearch/GuzzleConnection.php | 13 +- extensions/elasticsearch/Node.php | 23 --- extensions/elasticsearch/Query.php | 117 +++++++++++--- extensions/elasticsearch/QueryBuilder.php | 29 +++- framework/yii/db/ActiveQuery.php | 1 + .../extensions/elasticsearch/ActiveRecordTest.php | 178 +++++++++++++++++++++ tests/unit/framework/ar/ActiveRecordTestTrait.php | 45 ++++++ 12 files changed, 414 insertions(+), 98 deletions(-) delete mode 100644 extensions/elasticsearch/Cluster.php delete mode 100644 extensions/elasticsearch/Node.php diff --git a/extensions/elasticsearch/ActiveQuery.php b/extensions/elasticsearch/ActiveQuery.php index 79c83c8..6ad12e4 100644 --- a/extensions/elasticsearch/ActiveQuery.php +++ b/extensions/elasticsearch/ActiveQuery.php @@ -12,24 +12,21 @@ use yii\db\ActiveQueryTrait; use yii\helpers\Json; /** - * ActiveQuery represents a query associated with an Active Record class. + * ActiveQuery represents a [[Query]] associated with an [[ActiveRecord]] class. * - * ActiveQuery instances are usually created by [[ActiveRecord::find()]] - * and [[ActiveRecord::count()]]. + * ActiveQuery instances are usually created by [[ActiveRecord::find()]]. * * ActiveQuery mainly provides the following methods to retrieve the query results: * * - [[one()]]: returns a single record populated with the first row of data. * - [[all()]]: returns all records based on the query results. * - [[count()]]: returns the number of records. - * - [[sum()]]: returns the sum over the specified column. - * - [[average()]]: returns the average over the specified column. - * - [[min()]]: returns the min over the specified column. - * - [[max()]]: returns the max over the specified column. * - [[scalar()]]: returns the value of the first column in the first row of the query result. + * - [[column()]]: returns the value of the first column in the query result. * - [[exists()]]: returns a value indicating whether the query result has data or not. * - * You can use query methods, such as [[where()]], [[limit()]] and [[orderBy()]] to customize the query options. + * Because ActiveQuery extends from [[Query]], one can use query methods, such as [[where()]], + * [[orderBy()]] to customize the query options. * * ActiveQuery also provides the following additional query options: * @@ -83,16 +80,28 @@ class ActiveQuery extends Query implements ActiveQueryInterface */ public function all($db = null) { - $command = $this->createCommand($db); - $result = $command->search(); - if (empty($result['hits'])) { + $result = $this->createCommand($db)->search(); + if (empty($result['hits']['hits'])) { return []; } - $models = $this->createModels($result['hits']); - if ($this->asArray) { + if ($this->fields !== null) { + foreach ($result['hits']['hits'] as &$row) { + $row['_source'] = isset($row['fields']) ? $row['fields'] : []; + unset($row['fields']); + } + unset($row); + } + if ($this->asArray && $this->indexBy) { + foreach ($result['hits']['hits'] as &$row) { + $row['_source'][ActiveRecord::PRIMARY_KEY_NAME] = $row['_id']; + $row = $row['_source']; + } + } + $models = $this->createModels($result['hits']['hits']); + if ($this->asArray && !$this->indexBy) { foreach($models as $key => $model) { + $model['_source'][ActiveRecord::PRIMARY_KEY_NAME] = $model['_id']; $models[$key] = $model['_source']; - $models[$key][ActiveRecord::PRIMARY_KEY_NAME] = $model['_id']; } } if (!empty($this->with)) { @@ -133,6 +142,28 @@ class ActiveQuery extends Query implements ActiveQueryInterface /** * @inheritDocs */ + public function search($db = null, $options = []) + { + $result = $this->createCommand($db)->search($options); + if (!empty($result['hits']['hits'])) { + $models = $this->createModels($result['hits']['hits']); + if ($this->asArray) { + foreach($models as $key => $model) { + $model['_source'][ActiveRecord::PRIMARY_KEY_NAME] = $model['_id']; + $models[$key] = $model['_source']; + } + } + if (!empty($this->with)) { + $this->findWith($this->with, $models); + } + $result['hits']['hits'] = $models; + } + return $result; + } + + /** + * @inheritDocs + */ public function scalar($field, $db = null) { $record = parent::one($db); @@ -154,12 +185,15 @@ class ActiveQuery extends Query implements ActiveQueryInterface if ($field == ActiveRecord::PRIMARY_KEY_NAME) { $command = $this->createCommand($db); $command->queryParts['fields'] = []; - $rows = $command->search()['hits']; - $result = []; - foreach ($rows as $row) { - $result[] = $row['_id']; + $result = $command->search(); + if (empty($result['hits']['hits'])) { + return []; + } + $column = []; + foreach ($result['hits']['hits'] as $row) { + $column[] = $row['_id']; } - return $result; + return $column; } return parent::column($field, $db); } diff --git a/extensions/elasticsearch/ActiveRecord.php b/extensions/elasticsearch/ActiveRecord.php index f113cf3..293200d 100644 --- a/extensions/elasticsearch/ActiveRecord.php +++ b/extensions/elasticsearch/ActiveRecord.php @@ -1,7 +1,7 @@ - */ - -namespace yii\elasticsearch; - - -use yii\base\Object; - -class Cluster extends Object -{ - // TODO implement http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/cluster.html -} \ No newline at end of file diff --git a/extensions/elasticsearch/Command.php b/extensions/elasticsearch/Command.php index 7d5aa8e..58ac15a 100644 --- a/extensions/elasticsearch/Command.php +++ b/extensions/elasticsearch/Command.php @@ -1,6 +1,8 @@ + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ */ namespace yii\elasticsearch; @@ -16,10 +18,13 @@ use yii\helpers\Json; /** - * Class Command + * The Command class implements the API for accessing the elasticsearch REST API. * - * http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/glossary.html + * Check the [elasticsearch guide](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/index.html) + * for details on these commands. * + * @author Carsten Brandt + * @since 2.0 */ class Command extends Component { @@ -61,7 +66,7 @@ class Command extends Component $this->type !== null ? $this->type : '_all', '_search' ]; - return $this->db->get($url, array_merge($this->options, $options), $query)['hits']; + return $this->db->get($url, array_merge($this->options, $options), $query); } /** diff --git a/extensions/elasticsearch/Connection.php b/extensions/elasticsearch/Connection.php index 1adc42b..2f67e42 100644 --- a/extensions/elasticsearch/Connection.php +++ b/extensions/elasticsearch/Connection.php @@ -1,7 +1,7 @@ + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ */ namespace yii\elasticsearch; - use Guzzle\Http\Exception\ClientErrorResponseException; use yii\base\Exception; use yii\helpers\Json; +/** + * Class GuzzleConnection + * + * @author Carsten Brandt + * @since 2.0 + */ class GuzzleConnection extends Connection { /** diff --git a/extensions/elasticsearch/Node.php b/extensions/elasticsearch/Node.php deleted file mode 100644 index 60d5956..0000000 --- a/extensions/elasticsearch/Node.php +++ /dev/null @@ -1,23 +0,0 @@ - - * @since 2.0 - */ -class Node extends Object -{ - public $host; - public $port; -} \ No newline at end of file diff --git a/extensions/elasticsearch/Query.php b/extensions/elasticsearch/Query.php index db6e137..d4fcde6 100644 --- a/extensions/elasticsearch/Query.php +++ b/extensions/elasticsearch/Query.php @@ -9,6 +9,7 @@ namespace yii\elasticsearch; use Yii; use yii\base\Component; +use yii\base\NotSupportedException; use yii\db\QueryInterface; use yii\db\QueryTrait; @@ -49,16 +50,28 @@ class Query extends Component implements QueryInterface * @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-body.html#_parameters_3 */ public $timeout; - + /** + * @var array|string The query part of this search query. This is an array or json string that follows the format of + * the elasticsearch [Query DSL](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl.html). + */ public $query; - + /** + * @var array|string The filter part of this search query. This is an array or json string that follows the format of + * the elasticsearch [Query DSL](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl.html). + */ public $filter; public $facets = []; - public $facetResults = []; - - public $totalCount; + public function init() + { + parent::init(); + // setting the default limit according to elasticsearch defaults + // http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-body.html#_parameters_3 + if ($this->limit === null) { + $this->limit = 10; + } + } /** * Creates a DB command that can be used to execute this query. @@ -85,8 +98,10 @@ class Query extends Component implements QueryInterface public function all($db = null) { $result = $this->createCommand($db)->search(); - // TODO publish facet results - $rows = $result['hits']; + if (empty($result['hits']['hits'])) { + return []; + } + $rows = $result['hits']['hits']; if ($this->indexBy === null && $this->fields === null) { return $rows; } @@ -119,11 +134,10 @@ class Query extends Component implements QueryInterface { $options['size'] = 1; $result = $this->createCommand($db)->search($options); - // TODO publish facet results - if (empty($result['hits'])) { + if (empty($result['hits']['hits'])) { return false; } - $record = reset($result['hits']); + $record = reset($result['hits']['hits']); if ($this->fields !== null) { $record['_source'] = isset($record['fields']) ? $record['fields'] : []; unset($record['fields']); @@ -132,6 +146,43 @@ class Query extends Component implements QueryInterface } /** + * Executes the query and returns the complete search result including e.g. hits, facets, totalCount. + * @param Connection $db the database connection used to execute the query. + * If this parameter is not given, the `elasticsearch` application component will be used. + * @param array $options The options given with this query. Possible options are: + * - [routing](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search.html#search-routing) + * - [search_type](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-search-type.html) + * @return array the query results. + */ + public function search($db = null, $options = []) + { + $result = $this->createCommand($db)->search($options); + if (!empty($result['hits']['hits']) && ($this->indexBy === null || $this->fields === null)) { + $rows = []; + foreach ($result['hits']['hits'] as $key => $row) { + if ($this->fields !== null) { + $row['_source'] = isset($row['fields']) ? $row['fields'] : []; + unset($row['fields']); + } + if ($this->indexBy !== null) { + if (is_string($this->indexBy)) { + $key = $row['_source'][$this->indexBy]; + } else { + $key = call_user_func($this->indexBy, $row); + } + } + $rows[$key] = $row; + } + $result['hits']['hits'] = $rows; + } + return $result; + } + + // TODO add query stats http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search.html#stats-groups + + // TODO add scroll/scan http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-search-type.html#scan + + /** * Executes the query and deletes all matching documents. * * This will not run facet queries. @@ -142,7 +193,8 @@ class Query extends Component implements QueryInterface */ public function delete($db = null) { - // TODO http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-delete-by-query.html + // TODO implement http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/docs-delete-by-query.html + throw new NotSupportedException('Delete by query is not implemented yet.'); } /** @@ -156,7 +208,7 @@ class Query extends Component implements QueryInterface */ public function scalar($field, $db = null) { - $record = self::one($db); + $record = self::one($db); // TODO limit fields to the one required if ($record !== false && isset($record['_source'][$field])) { return $record['_source'][$field]; } else { @@ -175,12 +227,15 @@ class Query extends Component implements QueryInterface { $command = $this->createCommand($db); $command->queryParts['fields'] = [$field]; - $rows = $command->search()['hits']; - $result = []; - foreach ($rows as $row) { - $result[] = isset($row['fields'][$field]) ? $row['fields'][$field] : null; + $result = $command->search(); + if (empty($result['hits']['hits'])) { + return []; } - return $result; + $column = []; + foreach ($result['hits']['hits'] as $row) { + $column[] = isset($row['fields'][$field]) ? $row['fields'][$field] : null; + } + return $column; } /** @@ -198,7 +253,7 @@ class Query extends Component implements QueryInterface $options = []; $options['search_type'] = 'count'; - $count = $this->createCommand($db)->search($options)['total']; + $count = $this->createCommand($db)->search($options)['hits']['total']; if ($this->limit === null && $this->offset === null) { return $count; } elseif ($this->offset !== null) { @@ -354,9 +409,26 @@ class Query extends Component implements QueryInterface // TODO support multi query via static method http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-multi-search.html - public function query() + /** + * Sets the querypart of this search query. + * @param string $query + * @return static + */ + public function query($query) { + $this->query = $query; + return $this; + } + /** + * Sets the filter part of this search query. + * @param string $filter + * @return static + */ + public function filter($filter) + { + $this->filter = $filter; + return $this; } /** @@ -381,7 +453,11 @@ class Query extends Component implements QueryInterface */ public function fields($fields) { - $this->fields = $fields; + if (is_array($fields)) { + $this->fields = $fields; + } else { + $this->fields = func_get_args(); + } return $this; } @@ -397,5 +473,4 @@ class Query extends Component implements QueryInterface $this->timeout = $timeout; return $this; } - } \ No newline at end of file diff --git a/extensions/elasticsearch/QueryBuilder.php b/extensions/elasticsearch/QueryBuilder.php index c008de1..63cfe6e 100644 --- a/extensions/elasticsearch/QueryBuilder.php +++ b/extensions/elasticsearch/QueryBuilder.php @@ -9,6 +9,7 @@ namespace yii\elasticsearch; use yii\base\InvalidParamException; use yii\base\NotSupportedException; +use yii\helpers\Json; /** * QueryBuilder builds an elasticsearch query based on the specification given as a [[Query]] object. @@ -55,13 +56,25 @@ class QueryBuilder extends \yii\base\Object $parts['from'] = (int) $query->offset; } - $filters = empty($query->filter) ? [] : [$query->filter]; - $whereFilter = $this->buildCondition($query->where); - if (!empty($whereFilter)) { - $filters[] = $whereFilter; + if (empty($parts['query'])) { + $parts['query'] = ["match_all" => (object)[]]; } - if (!empty($filters)) { - $parts['filter'] = count($filters) > 1 ? ['and' => $filters] : $filters[0]; + + $whereFilter = $this->buildCondition($query->where); + if (is_string($query->filter)) { + if (empty($whereFilter)) { + $parts['filter'] = $query->filter; + } else { + $parts['filter'] = '{"and": [' . $query->filter . ', ' . Json::encode($whereFilter) . ']}'; + } + } elseif ($query->filter !== null) { + if (empty($whereFilter)) { + $parts['filter'] = $query->filter; + } else { + $parts['filter'] = ['and' => [$query->filter, $whereFilter]]; + } + } elseif (!empty($whereFilter)) { + $parts['filter'] = $whereFilter; } $sort = $this->buildOrderBy($query->orderBy); @@ -69,8 +82,8 @@ class QueryBuilder extends \yii\base\Object $parts['sort'] = $sort; } - if (empty($parts['query'])) { - $parts['query'] = ["match_all" => (object)[]]; + if (!empty($query->facets)) { + $parts['facets'] = $query->facets; } $options = []; diff --git a/framework/yii/db/ActiveQuery.php b/framework/yii/db/ActiveQuery.php index 517bf22..fb5438a 100644 --- a/framework/yii/db/ActiveQuery.php +++ b/framework/yii/db/ActiveQuery.php @@ -23,6 +23,7 @@ namespace yii\db; * - [[min()]]: returns the min over the specified column. * - [[max()]]: returns the max over the specified column. * - [[scalar()]]: returns the value of the first column in the first row of the query result. + * - [[column()]]: returns the value of the first column in the query result. * - [[exists()]]: returns a value indicating whether the query result has data or not. * * Because ActiveQuery extends from [[Query]], one can use query methods, such as [[where()]], diff --git a/tests/unit/extensions/elasticsearch/ActiveRecordTest.php b/tests/unit/extensions/elasticsearch/ActiveRecordTest.php index ec5dd1a..72b5c5d 100644 --- a/tests/unit/extensions/elasticsearch/ActiveRecordTest.php +++ b/tests/unit/extensions/elasticsearch/ActiveRecordTest.php @@ -132,6 +132,62 @@ class ActiveRecordTest extends ElasticSearchTestCase $db->createCommand()->flushIndex('yiitest'); } + public function testSearch() + { + $customers = $this->callCustomerFind()->search()['hits']; + $this->assertEquals(3, $customers['total']); + $this->assertEquals(3, count($customers['hits'])); + $this->assertTrue($customers['hits'][0] instanceof Customer); + $this->assertTrue($customers['hits'][1] instanceof Customer); + $this->assertTrue($customers['hits'][2] instanceof Customer); + + // limit vs. totalcount + $customers = $this->callCustomerFind()->limit(2)->search()['hits']; + $this->assertEquals(3, $customers['total']); + $this->assertEquals(2, count($customers['hits'])); + + // asArray + $result = $this->callCustomerFind()->asArray()->search()['hits']; + $this->assertEquals(3, $result['total']); + $customers = $result['hits']; + $this->assertEquals(3, count($customers)); + $this->assertArrayHasKey('id', $customers[0]); + $this->assertArrayHasKey('name', $customers[0]); + $this->assertArrayHasKey('email', $customers[0]); + $this->assertArrayHasKey('address', $customers[0]); + $this->assertArrayHasKey('status', $customers[0]); + $this->assertArrayHasKey('id', $customers[1]); + $this->assertArrayHasKey('name', $customers[1]); + $this->assertArrayHasKey('email', $customers[1]); + $this->assertArrayHasKey('address', $customers[1]); + $this->assertArrayHasKey('status', $customers[1]); + $this->assertArrayHasKey('id', $customers[2]); + $this->assertArrayHasKey('name', $customers[2]); + $this->assertArrayHasKey('email', $customers[2]); + $this->assertArrayHasKey('address', $customers[2]); + $this->assertArrayHasKey('status', $customers[2]); + + // TODO test asArray() + fields() + indexBy() + + // find by attributes + $result = $this->callCustomerFind()->where(['name' => 'user2'])->search()['hits']; + $customer = reset($result['hits']); + $this->assertTrue($customer instanceof Customer); + $this->assertEquals(2, $customer->id); + + // TODO test query() and filter() + } + + public function testSearchFacets() + { + $result = $this->callCustomerFind()->addStatisticalFacet('status_stats', ['field' => 'status'])->search(); + $this->assertArrayHasKey('facets', $result); + $this->assertEquals(3, $result['facets']['status_stats']['count']); + $this->assertEquals(4, $result['facets']['status_stats']['total']); // sum of values + $this->assertEquals(1, $result['facets']['status_stats']['min']); + $this->assertEquals(2, $result['facets']['status_stats']['max']); + } + public function testGetDb() { $this->mockApplication(['components' => ['elasticsearch' => Connection::className()]]); @@ -314,4 +370,126 @@ class ActiveRecordTest extends ElasticSearchTestCase $customers = $this->callCustomerFind()->where(['status' => false])->all(); $this->assertEquals(2, count($customers)); } + + public function testfindAsArrayFields() + { + $customerClass = $this->getCustomerClass(); + /** @var TestCase|ActiveRecordTestTrait $this */ + // indexBy + asArray + $customers = $this->callCustomerFind()->asArray()->fields(['id', 'name'])->all(); + $this->assertEquals(3, count($customers)); + $this->assertArrayHasKey('id', $customers[0]); + $this->assertArrayHasKey('name', $customers[0]); + $this->assertArrayNotHasKey('email', $customers[0]); + $this->assertArrayNotHasKey('address', $customers[0]); + $this->assertArrayNotHasKey('status', $customers[0]); + $this->assertArrayHasKey('id', $customers[1]); + $this->assertArrayHasKey('name', $customers[1]); + $this->assertArrayNotHasKey('email', $customers[1]); + $this->assertArrayNotHasKey('address', $customers[1]); + $this->assertArrayNotHasKey('status', $customers[1]); + $this->assertArrayHasKey('id', $customers[2]); + $this->assertArrayHasKey('name', $customers[2]); + $this->assertArrayNotHasKey('email', $customers[2]); + $this->assertArrayNotHasKey('address', $customers[2]); + $this->assertArrayNotHasKey('status', $customers[2]); + } + + public function testfindIndexByFields() + { + $customerClass = $this->getCustomerClass(); + /** @var TestCase|ActiveRecordTestTrait $this */ + // indexBy + asArray + $customers = $this->callCustomerFind()->indexBy('name')->fields('id', 'name')->all(); + $this->assertEquals(3, count($customers)); + $this->assertTrue($customers['user1'] instanceof $customerClass); + $this->assertTrue($customers['user2'] instanceof $customerClass); + $this->assertTrue($customers['user3'] instanceof $customerClass); + $this->assertNotNull($customers['user1']->id); + $this->assertNotNull($customers['user1']->name); + $this->assertNull($customers['user1']->email); + $this->assertNull($customers['user1']->address); + $this->assertNull($customers['user1']->status); + $this->assertNotNull($customers['user2']->id); + $this->assertNotNull($customers['user2']->name); + $this->assertNull($customers['user2']->email); + $this->assertNull($customers['user2']->address); + $this->assertNull($customers['user2']->status); + $this->assertNotNull($customers['user3']->id); + $this->assertNotNull($customers['user3']->name); + $this->assertNull($customers['user3']->email); + $this->assertNull($customers['user3']->address); + $this->assertNull($customers['user3']->status); + + // indexBy callable + asArray + $customers = $this->callCustomerFind()->indexBy(function ($customer) { + return $customer->id . '-' . $customer->name; + })->fields('id', 'name')->all(); + $this->assertEquals(3, count($customers)); + $this->assertTrue($customers['1-user1'] instanceof $customerClass); + $this->assertTrue($customers['2-user2'] instanceof $customerClass); + $this->assertTrue($customers['3-user3'] instanceof $customerClass); + $this->assertNotNull($customers['1-user1']->id); + $this->assertNotNull($customers['1-user1']->name); + $this->assertNull($customers['1-user1']->email); + $this->assertNull($customers['1-user1']->address); + $this->assertNull($customers['1-user1']->status); + $this->assertNotNull($customers['2-user2']->id); + $this->assertNotNull($customers['2-user2']->name); + $this->assertNull($customers['2-user2']->email); + $this->assertNull($customers['2-user2']->address); + $this->assertNull($customers['2-user2']->status); + $this->assertNotNull($customers['3-user3']->id); + $this->assertNotNull($customers['3-user3']->name); + $this->assertNull($customers['3-user3']->email); + $this->assertNull($customers['3-user3']->address); + $this->assertNull($customers['3-user3']->status); + } + + public function testfindIndexByAsArrayFields() + { + $customerClass = $this->getCustomerClass(); + /** @var TestCase|ActiveRecordTestTrait $this */ + // indexBy + asArray + $customers = $this->callCustomerFind()->indexBy('name')->asArray()->fields('id', 'name')->all(); + $this->assertEquals(3, count($customers)); + $this->assertArrayHasKey('id', $customers['user1']); + $this->assertArrayHasKey('name', $customers['user1']); + $this->assertArrayNotHasKey('email', $customers['user1']); + $this->assertArrayNotHasKey('address', $customers['user1']); + $this->assertArrayNotHasKey('status', $customers['user1']); + $this->assertArrayHasKey('id', $customers['user2']); + $this->assertArrayHasKey('name', $customers['user2']); + $this->assertArrayNotHasKey('email', $customers['user2']); + $this->assertArrayNotHasKey('address', $customers['user2']); + $this->assertArrayNotHasKey('status', $customers['user2']); + $this->assertArrayHasKey('id', $customers['user3']); + $this->assertArrayHasKey('name', $customers['user3']); + $this->assertArrayNotHasKey('email', $customers['user3']); + $this->assertArrayNotHasKey('address', $customers['user3']); + $this->assertArrayNotHasKey('status', $customers['user3']); + + // indexBy callable + asArray + $customers = $this->callCustomerFind()->indexBy(function ($customer) { + return $customer['id'] . '-' . $customer['name']; + })->asArray()->fields('id', 'name')->all(); + $this->assertEquals(3, count($customers)); + $this->assertArrayHasKey('id', $customers['1-user1']); + $this->assertArrayHasKey('name', $customers['1-user1']); + $this->assertArrayNotHasKey('email', $customers['1-user1']); + $this->assertArrayNotHasKey('address', $customers['1-user1']); + $this->assertArrayNotHasKey('status', $customers['1-user1']); + $this->assertArrayHasKey('id', $customers['2-user2']); + $this->assertArrayHasKey('name', $customers['2-user2']); + $this->assertArrayNotHasKey('email', $customers['2-user2']); + $this->assertArrayNotHasKey('address', $customers['2-user2']); + $this->assertArrayNotHasKey('status', $customers['2-user2']); + $this->assertArrayHasKey('id', $customers['3-user3']); + $this->assertArrayHasKey('name', $customers['3-user3']); + $this->assertArrayNotHasKey('email', $customers['3-user3']); + $this->assertArrayNotHasKey('address', $customers['3-user3']); + $this->assertArrayNotHasKey('status', $customers['3-user3']); + } + + } \ No newline at end of file diff --git a/tests/unit/framework/ar/ActiveRecordTestTrait.php b/tests/unit/framework/ar/ActiveRecordTestTrait.php index a227def..5602096 100644 --- a/tests/unit/framework/ar/ActiveRecordTestTrait.php +++ b/tests/unit/framework/ar/ActiveRecordTestTrait.php @@ -199,6 +199,51 @@ trait ActiveRecordTestTrait $this->assertTrue($customers['3-user3'] instanceof $customerClass); } + public function testfindIndexByAsArray() + { + $customerClass = $this->getCustomerClass(); + /** @var TestCase|ActiveRecordTestTrait $this */ + // indexBy + asArray + $customers = $this->callCustomerFind()->asArray()->indexBy('name')->all(); + $this->assertEquals(3, count($customers)); + $this->assertArrayHasKey('id', $customers['user1']); + $this->assertArrayHasKey('name', $customers['user1']); + $this->assertArrayHasKey('email', $customers['user1']); + $this->assertArrayHasKey('address', $customers['user1']); + $this->assertArrayHasKey('status', $customers['user1']); + $this->assertArrayHasKey('id', $customers['user2']); + $this->assertArrayHasKey('name', $customers['user2']); + $this->assertArrayHasKey('email', $customers['user2']); + $this->assertArrayHasKey('address', $customers['user2']); + $this->assertArrayHasKey('status', $customers['user2']); + $this->assertArrayHasKey('id', $customers['user3']); + $this->assertArrayHasKey('name', $customers['user3']); + $this->assertArrayHasKey('email', $customers['user3']); + $this->assertArrayHasKey('address', $customers['user3']); + $this->assertArrayHasKey('status', $customers['user3']); + + // indexBy callable + asArray + $customers = $this->callCustomerFind()->indexBy(function ($customer) { + return $customer['id'] . '-' . $customer['name']; + })->asArray()->all(); + $this->assertEquals(3, count($customers)); + $this->assertArrayHasKey('id', $customers['1-user1']); + $this->assertArrayHasKey('name', $customers['1-user1']); + $this->assertArrayHasKey('email', $customers['1-user1']); + $this->assertArrayHasKey('address', $customers['1-user1']); + $this->assertArrayHasKey('status', $customers['1-user1']); + $this->assertArrayHasKey('id', $customers['2-user2']); + $this->assertArrayHasKey('name', $customers['2-user2']); + $this->assertArrayHasKey('email', $customers['2-user2']); + $this->assertArrayHasKey('address', $customers['2-user2']); + $this->assertArrayHasKey('status', $customers['2-user2']); + $this->assertArrayHasKey('id', $customers['3-user3']); + $this->assertArrayHasKey('name', $customers['3-user3']); + $this->assertArrayHasKey('email', $customers['3-user3']); + $this->assertArrayHasKey('address', $customers['3-user3']); + $this->assertArrayHasKey('status', $customers['3-user3']); + } + public function testRefresh() { $customerClass = $this->getCustomerClass();