From fb17bbe0abb3dd72c781712fc393548f51478371 Mon Sep 17 00:00:00 2001 From: Amr Soliman Date: Wed, 2 Mar 2016 11:41:47 +0200 Subject: [PATCH 1/6] Add deleteAllDocuments , deleteDocuments by query functions . --- readme.md | 100 ++++++++++++++++------------------ src/ElasticquentTrait.php | 109 ++++++++++++++++++++++++-------------- 2 files changed, 114 insertions(+), 95 deletions(-) diff --git a/readme.md b/readme.md index fab709a..3fdadea 100644 --- a/readme.md +++ b/readme.md @@ -1,5 +1,4 @@ # Elasticquent - _Elasticsearch for Eloquent Laravel Models_ Elasticquent makes working with [Elasticsearch](http://www.elasticsearch.org/) and [Eloquent](http://laravel.com/docs/eloquent) models easier by mapping them to Elasticsearch types. You can use the default settings or define how Elasticsearch should index and search your Eloquent models right in the model. @@ -7,38 +6,38 @@ Elasticquent makes working with [Elasticsearch](http://www.elasticsearch.org/) a Elasticquent uses the [official Elasticsearch PHP API](https://github.com/elasticsearch/elasticsearch-php). To get started, you should have a basic knowledge of how Elasticsearch works (indexes, types, mappings, etc). # Elasticsearch Requirements - -You must be running _at least_ Elasticsearch 1.0. Elasticsearch 0.9 and below *will not work* and are not supported. +You must be running _at least_ Elasticsearch 1.0. Elasticsearch 0.9 and below _will not work_ and are not supported. ## Contents +- [Overview](#overview) + - [How Elasticquent Works](#how-elasticquent-works) -* [Overview](#overview) - * [How Elasticquent Works](#how-elasticquent-works) -* [Setup](#setup) - * [Elasticsearch Configuration](#elasticsearch-configuration) - * [Indexes and Mapping](#indexes-and-mapping) - * [Setting a Custom Index Name](#setting-a-custom-index-name) - * [Setting a Custom Type Name](#setting-a-custom-type-name) -* [Indexing Documents](#indexing-documents) -* [Searching](#searching) - * [Search Collections](#search-collections) - * [Search Collection Documents](#search-collection-documents) - * [Chunking results from Elastiquent](#chunking-results-from-elastiquent) - * [Using the Search Collection Outside of Elasticquent](#using-the-search-collection-outside-of-elasticquent) -* [More Options](#more-options) - * [Document Ids](#document-ids) - * [Document Data](#document-data) - * [Using Elasticquent With Custom Collections](#using-elasticquetn-with-custom-collections) -* [Roadmap](#roadmap) +- [Setup](#setup) + - [Elasticsearch Configuration](#elasticsearch-configuration) + - [Indexes and Mapping](#indexes-and-mapping) + - [Setting a Custom Index Name](#setting-a-custom-index-name) + - [Setting a Custom Type Name](#setting-a-custom-type-name) -## Reporting Issues +- [Indexing Documents](#indexing-documents) +- [Searching](#searching) + - [Search Collections](#search-collections) + - [Search Collection Documents](#search-collection-documents) + - [Chunking results from Elastiquent](#chunking-results-from-elastiquent) + - [Using the Search Collection Outside of Elasticquent](#using-the-search-collection-outside-of-elasticquent) +- [More Options](#more-options) + - [Document Ids](#document-ids) + - [Document Data](#document-data) + - [Using Elasticquent With Custom Collections](#using-elasticquetn-with-custom-collections) + +- [Roadmap](#roadmap) + +## Reporting Issues If you do find an issue, please feel free to report it with [GitHub's bug tracker](https://github.com/elasticquent/Elasticquent/issues) for this project. Alternatively, fork the project and make a pull request :) ## Overview - Elasticquent allows you take an Eloquent model and easily index and search its contents in Elasticsearch. ```php @@ -65,16 +64,16 @@ Plus, you can still use all the Eloquent collection functionality: Check out the rest of the documentation for how to get started using Elasticsearch and Elasticquent! ### How Elasticquent Works - When using a database, Eloquent models are populated from data read from a database table. With Elasticquent, models are populated by data indexed in Elasticsearch. The whole idea behind using Elasticsearch for search is that its fast and light, so you model functionality will be dictated by what data has been indexed for your document. ## Setup - Before you start using Elasticquent, make sure you've installed [Elasticsearch](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/_installation.html). To get started, add Elasticquent to you composer.json file: - "elasticquent/elasticquent": "dev-master" +``` +"elasticquent/elasticquent": "dev-master" +``` Once you've run a `composer update`, add the Elasticquent trait to any Eloquent model that you want to be able to index in Elasticsearch: @@ -91,7 +90,6 @@ class Book extends Eloquent { Now your Eloquent model has some extra methods that make it easier to index your model's data using Elasticsearch. ### Elasticsearch Configuration - If you need to pass a special configuration array Elasticsearch, you can add that in an `elasticquent.php` config file at `/app/config/elasticquent.php` for Laravel 4, or `/config/elasticquent.php` for Laravel 5: ```php @@ -127,16 +125,16 @@ return array( 'default_index' => 'my_custom_index_name', ); - ``` ### Indexes and Mapping - While you can definitely build your indexes and mapping through the Elasticsearch API, you can also use some helper methods to build indexes and types right from your models. If you want a simple way to create indexes, Elasticquent models have a function for that: - Book::createIndex($shards = null, $replicas = null); +``` +Book::createIndex($shards = null, $replicas = null); +``` For mapping, you can set a `mappingProperties` property in your model and use some mapping functions from there: @@ -175,7 +173,6 @@ You can also get the type mapping and check if it exists. ``` ### Setting a Custom Index Name - Elastiquent will use `default` as your index name, but you can set a custom index name by creating an `elasticquent.php` config file in `/app/config/`: ```php @@ -198,7 +195,6 @@ return array( ``` ### Setting a Custom Type Name - By default, Elasticquent will use the table name of your models as the type name for indexing. If you'd like to override it, you can with the `getTypeName` function. ```php @@ -215,7 +211,6 @@ To check if the type for the Elasticquent model exists yet, use `typeExists`: ``` ## Indexing Documents - To index all the entries in an Eloquent model, use `addAllToIndex`: ```php @@ -243,11 +238,9 @@ You can also reindex an entire model: ``` ## Searching - There are three ways to search in Elasticquent. All three methods return a search collection. ### Simple term search - The first method is a simple term search that searches all fields. ```php @@ -255,7 +248,6 @@ The first method is a simple term search that searches all fields. ``` ### Query Based Search - The second is a query based search for more complex searching needs: ```php @@ -267,8 +259,8 @@ The second is a query based search for more complex searching needs: ```php $books = Book::searchByQuery(array('match' => array('title' => 'Moby Dick'))); ``` -Here's the list of available parameters: +Here's the list of available parameters: - `query` - Your ElasticSearch Query - `aggregations` - The Aggregations you wish to return. [See Aggregations for details](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-aggregations.html). - `sourceFields` - Limits returned set to the selected fields only @@ -277,9 +269,7 @@ Here's the list of available parameters: - `sort` - Your sort query ### Raw queries - -The final method is a raw query that will be sent to Elasticsearch. This method will provide you with the most flexibility -when searching for records inside Elasticsearch: +The final method is a raw query that will be sent to Elasticsearch. This method will provide you with the most flexibility when searching for records inside Elasticsearch: ```php $books = Book::complexSearch(array( @@ -294,12 +284,12 @@ when searching for records inside Elasticsearch: ``` This is the equivalent to: + ```php $books = Book::searchByQuery(array('match' => array('title' => 'Moby Dick'))); ``` ### Search Collections - When you search on an Elasticquent model, you get a search collection with some special functions. You can get total hits: @@ -339,7 +329,6 @@ And access search aggregations - [See Aggregations for details](http://www.elast ``` ### Search Collection Documents - Items in a search result collection will have some extra data that comes from Elasticsearch. You can always check and see if a model is a document or not by using the `isDocument` function: ```php @@ -353,7 +342,6 @@ You can check the document score that Elasticsearch assigned to this document wi ``` ### Chunking results from Elastiquent - Similar to `Illuminate\Support\Collection`, the `chunk` method breaks the Elasticquent collection into multiple, smaller collections of a given size: ```php @@ -361,9 +349,7 @@ Similar to `Illuminate\Support\Collection`, the `chunk` method breaks the Elasti $books = $all_books->chunk(10); ``` - ### Using the Search Collection Outside of Elasticquent - If you're dealing with raw search data from outside of Elasticquent, you can use the Elasticquent search results collection to turn that data into a collection. ```php @@ -377,17 +363,13 @@ $params = array( $params['body']['query']['match']['title'] = 'Moby Dick'; $collection = new \Elasticquent\ElasticquentResultCollection($client->search($params), new Book); - ``` ## More Options - ### Document IDs - Elasticquent will use whatever is set as the `primaryKey` for your Eloquent models as the id for your Elasticsearch documents. ### Document Data - By default, Elasticquent will use the entire attribute array for your Elasticsearch documents. However, if you want to customize how your search documents are structured, you can set a `getIndexDocumentData` function that returns you own custom document array. ```php @@ -400,10 +382,10 @@ function getIndexDocumentData() ); } ``` + Be careful with this, as Elasticquent reads the document source into the Eloquent model attributes when creating a search result collection, so make sure you are indexing enough data for your the model functionality you want to use. ### Using Elasticquent With Custom Collections - If you are using a custom collection with your Eloquent models, you just need to add the `ElasticquentCollectionTrait` to your collection so you can use `addToIndex`. ```php @@ -413,9 +395,19 @@ class MyCollection extends \Illuminate\Database\Eloquent\Collection { } ``` -## Roadmap +## Deleting Documents in A type: +If you are using elastic version >= 2, you have to install [delete-by-query plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/2.0/plugins-delete-by-query.html) +### delete All Document +```php + Book::deleteAllDocuments(); +``` +### delete Document By Query +```php + $query = array('match' => array('is_published' => 0)); + Book::deleteDocuments($query); +``` +## Roadmap Elasticquent currently needs: - -* Tests that mock ES API calls. -* Support for routes +- Tests that mock ES API calls. +- Support for routes diff --git a/src/ElasticquentTrait.php b/src/ElasticquentTrait.php index 55400fd..75c8575 100644 --- a/src/ElasticquentTrait.php +++ b/src/ElasticquentTrait.php @@ -1,8 +1,8 @@ -getElasticConfig('default_index'); if (!empty($index_name)) { @@ -110,19 +102,17 @@ public function usesTimestampsInIndex() /** * Use Timestamps In Index. */ - public function useTimestampsInIndex($shouldUse = true) + public function useTimestampsInIndex() { - $this->usesTimestampsInIndex = $shouldUse; + $this->usesTimestampsInIndex = true; } /** * Don't Use Timestamps In Index. - * - * @deprecated */ public function dontUseTimestampsInIndex() { - $this->useTimestampsInIndex(false); + $this->usesTimestampsInIndex = false; } /** @@ -230,14 +220,20 @@ public static function reindex() * @param array $query * @param array $aggregations * @param array $sourceFields - * @param int $limit - * @param int $offset + * @param int $limit + * @param int $offset * @param array $sort * - * @return ElasticquentResultCollection + * @return ResultCollection */ - public static function searchByQuery($query = null, $aggregations = null, $sourceFields = null, $limit = null, $offset = null, $sort = null) - { + public static function searchByQuery( + $query = null, + $aggregations = null, + $sourceFields = null, + $limit = null, + $offset = null, + $sort = null + ) { $instance = new static; $params = $instance->getBasicEsParams(true, true, true, $limit, $offset); @@ -260,7 +256,7 @@ public static function searchByQuery($query = null, $aggregations = null, $sourc $result = $instance->getElasticSearchClient()->search($params); - return new \Elasticquent\ElasticquentResultCollection($result, $instance = new static); + return new ResultCollection($result, $instance = new static); } /** @@ -268,7 +264,7 @@ public static function searchByQuery($query = null, $aggregations = null, $sourc * * Using this method, a custom query can be sent to Elasticsearch. * - * @param $params parameters to be passed directly to Elasticsearch + * @param $params * @return ElasticquentResultCollection */ public static function complexSearch($params) @@ -277,7 +273,7 @@ public static function complexSearch($params) $result = $instance->getElasticSearchClient()->search($params); - return new \Elasticquent\ElasticquentResultCollection($result, $instance = new static); + return new ResultCollection($result, $instance = new static); } /** @@ -287,9 +283,9 @@ public static function complexSearch($params) * * @param string $term * - * @return ElasticquentResultCollection + * @return ResultCollection */ - public static function search($term = '') + public static function search($term = null) { $instance = new static; @@ -299,7 +295,7 @@ public static function search($term = '') $result = $instance->getElasticSearchClient()->search($params); - return new \Elasticquent\ElasticquentResultCollection($result, $instance = new static); + return new ResultCollection($result, $instance = new static); } /** @@ -376,16 +372,21 @@ public function getIndexedDocument() * @param bool $getIdIfPossible * @param bool $getSourceIfPossible * @param bool $getTimestampIfPossible - * @param int $limit - * @param int $offset + * @param int $limit + * @param int $offset * * @return array */ - public function getBasicEsParams($getIdIfPossible = true, $getSourceIfPossible = false, $getTimestampIfPossible = false, $limit = null, $offset = null) - { + public function getBasicEsParams( + $getIdIfPossible = true, + $getSourceIfPossible = false, + $getTimestampIfPossible = false, + $limit = null, + $offset = null + ) { $params = array( 'index' => $this->getIndexName(), - 'type' => $this->getTypeName(), + 'type' => $this->getTypeName(), ); if ($getIdIfPossible && $this->getKey()) { @@ -411,8 +412,8 @@ public function getBasicEsParams($getIdIfPossible = true, $getSourceIfPossible = /** * Build the 'fields' parameter depending on given options. * - * @param bool $getSourceIfPossible - * @param bool $getTimestampIfPossible + * @param bool $getSourceIfPossible + * @param bool $getTimestampIfPossible * @return array */ private function buildFieldsParameter($getSourceIfPossible, $getTimestampIfPossible) @@ -472,7 +473,7 @@ public static function putMapping($ignoreConflicts = false) $mapping = $instance->getBasicEsParams(); $params = array( - '_source' => array('enabled' => true), + '_source' => array('enabled' => true), 'properties' => $instance->getMappingProperties(), ); @@ -581,6 +582,32 @@ public static function typeExists() return $instance->getElasticSearchClient()->indices()->existsType($params); } + /** + * Delete Documents in A type by query. + * Delete By Query Plugin required + * https://www.elastic.co/guide/en/elasticsearch/plugins/2.0/plugins-delete-by-query.html#plugins-delete-by-query + * @param array $query + */ + public static function deleteDocuments($query = array()) + { + $instance = new static; + $params = $instance->getBasicEsParams(); + $params['body']['query'] = $query; + + return $instance->getElasticSearchClient()->deleteByQuery($params); + } + + /** + * Delete all Documents in A type. + * Delete By Query Plugin required + * https://www.elastic.co/guide/en/elasticsearch/plugins/2.0/plugins-delete-by-query.html#plugins-delete-by-query + * @return array + */ + public static function deleteAllDocuments() + { + return self::deleteDocuments(array('match_all'=>[])); + } + /** * New From Hit Builder * From dadd4eb7fbb8fadfc0871d2e7ef55339ad539c5b Mon Sep 17 00:00:00 2001 From: Amr Soliman Date: Wed, 2 Mar 2016 15:16:12 +0200 Subject: [PATCH 2/6] check if INDEX exisits function --- src/ElasticquentTrait.php | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/ElasticquentTrait.php b/src/ElasticquentTrait.php index 75c8575..4f98b81 100644 --- a/src/ElasticquentTrait.php +++ b/src/ElasticquentTrait.php @@ -519,6 +519,26 @@ public static function rebuildMapping() return $instance->putMapping(); } + /** + * Index Exists ? + * + * @return bool + */ + public static function indexExists() + { + $instance = new static; + + $client = $instance->getElasticSearchClient(); + + $index = array( + 'index' => $instance->getIndexName(), + ); + + return $client->indices()->exists($index); + + + } + /** * Create Index * @@ -605,7 +625,7 @@ public static function deleteDocuments($query = array()) */ public static function deleteAllDocuments() { - return self::deleteDocuments(array('match_all'=>[])); + return self::deleteDocuments(array('match_all' => [])); } /** From 92d4d02642a28beccc717b5044d234bbdde8944e Mon Sep 17 00:00:00 2001 From: Amr Soliman Date: Wed, 9 Mar 2016 11:28:58 +0200 Subject: [PATCH 3/6] psr-4 code format --- composer.json | 11 +- src/Builder.php | 390 ++++++++++++++++++++++ src/Contracts/Builder.php | 151 +++++++++ src/Contracts/Query.php | 40 +++ src/Contracts/Wrapper.php | 87 +++++ src/ElasticquentCollectionTrait.php | 5 +- src/ElasticquentConfigTrait.php | 2 +- src/ElasticquentPaginator.php | 3 +- src/ElasticquentResultCollection.php | 6 +- src/ElasticquentTrait.php | 14 + src/Laralastica.php | 475 +++++++++++++++++++++++++++ src/Query.php | 90 +++++ 12 files changed, 1264 insertions(+), 10 deletions(-) create mode 100644 src/Builder.php create mode 100644 src/Contracts/Builder.php create mode 100644 src/Contracts/Query.php create mode 100644 src/Contracts/Wrapper.php create mode 100644 src/Laralastica.php create mode 100644 src/Query.php diff --git a/composer.json b/composer.json index e8bde8f..c5baf0b 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "elasticquent/elasticquent", + "name": "Amrsoliman/elasticquent", "type": "library", "description": "Maps Laravel Eloquent models to Elasticsearch types.", "keywords": [ @@ -7,9 +7,14 @@ "eloquent", "laravel" ], - "homepage": "https://github.com/elasticquent/Elasticquent", + "homepage": "https://github.com/AmrSoliman/Elasticquent", "license": "MIT", "authors": [ + { + "name": "Amr Soliman", + "email": "asoliman@engineer.com", + "role": "Developer" + }, { "name": "Adam Fairholm", "email": "adam.fairholm@gmail.com", @@ -21,7 +26,7 @@ "illuminate/database": "~4.2|^5", "illuminate/config": "~4.2|^5", "nesbot/carbon": "~1.0", - "elasticsearch/elasticsearch": ">1.0 <2.1" + "elasticsearch/elasticsearch": "2.1.*" }, "require-dev": { "phpunit/phpunit": "~5.0", diff --git a/src/Builder.php b/src/Builder.php new file mode 100644 index 0000000..084e8fa --- /dev/null +++ b/src/Builder.php @@ -0,0 +1,390 @@ +setFieldQuery($field, $query); + $match->setFieldType($field, $type); + + if ($fuzzy) { + $match->setFieldFuzziness($field, 'AUTO'); + } + + $query = $this->newQuery($match); + $this->query[] = $query; + + return $query; + } + + /** + * Find all documents where the value is matched in the fields. The type option + * allows you to specify the type of match, can be best_fields, most_fields, + * cross_fields, phrase, phrase_prefix. + * + * best_fields finds documents which match any field, but uses the _score + * from the best field. + * + * most_fields finds documents which match any field and combines the _score + * from each field. + * + * cross_fields treats fields with the same analyzer as though they were + * one big field. Looks for each word in any field. + * + * phrase runs a match_phrase query on each field and combines the _score + * from each field. + * + * phrase_prefix runs a match_phrase_prefix query on each field and combines + * the _score from each field. + * + * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html + * + * @param array $fields The fields to search in + * @param string $query The string to search for + * @param string $type The match type + * @param bool $fuzzy Set whether the match should be fuzzy + * @param float $tieBreaker Can be between 0.0 and 1.0 + * @param string $operator Can be 'and' or 'or' + * @return Query + */ + public function multiMatch( + array $fields, + $query, + $type = 'phrase', + $fuzzy = false, + $tieBreaker = 0.0, + $operator = 'and' + ) { + $match = new MultiMatch(); + + $match->setFields($fields); + $match->setQuery($query); + $match->setType($type); + + if ($fuzzy) { + $match->setFuzziness('AUTO'); + } + + if ($type == 'best_fields') { + $match->setTieBreaker($tieBreaker); + } + + if ($type == 'cross_fields') { + $match->setOperator($operator); + } + + $query = $this->newQuery($match); + $this->query[] = $query; + + return $query; + } + + /** + * Finds all documents matching the query but groups common words, + * i.e. the, and runs them after the initial query for more efficiency. + * + * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-common-terms-query.html + * + * @param string $field + * @param string $query + * @param float $cutOff + * @param int|bool $minimumMatch + * @return Query + */ + public function common($field, $query, $cutOff = 0.001, $minimumMatch = false) + { + $common = new Common($field, $query, $cutOff); + + if ($minimumMatch) { + $common->setMinimumShouldMatch($minimumMatch); + } + + $query = $this->newQuery($common); + $this->query[] = $query; + + return $query; + } + + /** + * A query which matches all documents. + * + * @return Query + */ + public function matchAll() + { + $match = new MatchAll(); + + $query = $this->newQuery($match); + $this->query[] = $query; + + return $query; + } + + /** + * Find all documents in a given range. The range is provided as an array with + * at least either a 'lt' or 'lte' key and a 'gt' or 'gte' key. + * + * 'lt' stands for less than + * 'lte' for less than or equal to + * 'gt' for greater than + * 'gte' for greater than or equal to + * + * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-range-query.html + * + * @param string $field + * @param array $range + * @param bool $timeZone + * @param bool $format + * @return Query + */ + public function range($field, array $range, $timeZone = false, $format = false) + { + $range = new Range($field, $range); + + if ($timeZone) { + $range->setParam('time_zone', $timeZone); + } + + if ($format) { + $range->setParam('format', $format); + } + + $query = $this->newQuery($range); + $this->query[] = $query; + + return $query; + } + + /** + * Find all documents matching the provided regular expression. + * + * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html + * + * @param string $field + * @param string $regex + * @return Query + */ + public function regexp($field, $regex) + { + $regexp = new Regexp($field, $regex); + + $query = $this->newQuery($regexp); + $this->query[] = $query; + + return $query; + } + + /** + * Find a document matching an exact term. + * + * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-term-query.html + * + * @param string $key + * @param string $value + * @param float $boost + * @return Query + */ + public function term($key, $value, $boost = 1.0) + { + $term = new Term(); + $term->setTerm($key, $value, $boost); + + $query = $this->newQuery($term); + $this->query[] = $query; + + return $query; + } + + /** + * Find any documents matching the provided terms, optionally you can set a + * minimum amount of terms to match. + * + * @param string $key + * @param array $terms + * @param bool|int $minimumShouldMatch + * @return Query + */ + public function terms($key, array $terms, $minimumShouldMatch = false) + { + $query = new Terms($key, $terms); + + if ($minimumShouldMatch) { + $query->setMinimumMatch($minimumShouldMatch); + } + + $query = $this->newQuery($query); + $this->query[] = $query; + + return $query; + } + + /** + * Find a document matching a value containing a wildcard. Please note wildcard + * queries can be very slow, to avoid this don't start a string with a wildcard. + * + * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-wildcard-query.html + * + * @param string $key + * @param string $value + * @param float $boost + * @return Query + */ + public function wildcard($key, $value, $boost = 1.0) + { + $query = new Wildcard($key, $value, $boost); + + $query = $this->newQuery($query); + $this->query[] = $query; + + return $query; + } + + /** + * The fuzzy query generates all possible matching terms that are within + * the maximum edit distance specified in fuzziness and then checks the + * term dictionary to find out which of those generated terms actually + * exist in the index. + * + * @link https://www.elastic.co/guide/en/elasticsearch/reference/1.4/query-dsl-fuzzy-query.html + * + * @param string $key + * @param string $value + * @param array $options + * @return Fuzzy + */ + public function fuzzy($key, $value, $options = []) + { + $query = new Fuzzy($key, $value); + + foreach ($options as $option => $value) { + $query->setFieldOption($option, $value); + } + + $this->query[] = $this->newQuery($query); + + return $query; + } + + /** + * Find documents that are "like" provided text by running it against one or more + * fields. + * + * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-flt-query.html + * + * @param string $value + * @param array $fields + * @param float $fuzziness + * @return FuzzyLikeThis + */ + public function fuzzyLikeThis($value, array $fields = [], $fuzziness = 0.5) + { + $query = new FuzzyLikeThis(); + + $query->setLikeText($value); + + if (!empty($fields)) { + $query->addFields($fields); + } + + $query->setMinSimilarity($fuzziness); + + $this->query[] = $this->newQuery($query); + + return $query; + } + + /** + * Add an abstract elastica query to array + * + * @param AbstractQuery $query + * @return Query + */ + public function addQuery(AbstractQuery $query) + { + $query = $this->newQuery($query); + $this->query[] = $query; + + return $query; + } + + /** + * Get the queries to be run. + * + * @return array + */ + public function getQuery() + { + return $this->query; + } + + /** + * Create a new query wrapper. + * + * @param mixed $query + * @return Query + */ + protected function newQuery($query) + { + return new Query($query); + } + + + public function sortBy($sort) + { + $query = new \Elastica\Query('agg_name'); + $query->setSort($sort); + $this->query[] = $query; + + return $query; + } + +} \ No newline at end of file diff --git a/src/Contracts/Builder.php b/src/Contracts/Builder.php new file mode 100644 index 0000000..6739987 --- /dev/null +++ b/src/Contracts/Builder.php @@ -0,0 +1,151 @@ +perPage = $perPage; $this->lastPage = (int) ceil($total / $perPage); $this->currentPage = $this->setCurrentPage($currentPage, $this->lastPage); - $this->path = $this->path != '/' ? rtrim($this->path, '/') . '/' : $this->path; + $this->path = $this->path != '/' ? rtrim($this->path, '/').'/' : $this->path; $this->items = $items instanceof Collection ? $items : Collection::make($items); $this->hits = $hits; } - /** * Get the instance as an array. * diff --git a/src/ElasticquentResultCollection.php b/src/ElasticquentResultCollection.php index 21010c2..32d266a 100644 --- a/src/ElasticquentResultCollection.php +++ b/src/ElasticquentResultCollection.php @@ -12,10 +12,12 @@ class ElasticquentResultCollection extends \Illuminate\Database\Eloquent\Collect protected $instance; /** - * Create a new instance containing Elasticsearch results + * _construct * - * @param $results elasticsearch results + * @param $results elasticsearch results * @param $instance + * + * @return \Elasticquent\ElasticquentResultCollection */ public function __construct($results, $instance) { diff --git a/src/ElasticquentTrait.php b/src/ElasticquentTrait.php index 4f98b81..94856bb 100644 --- a/src/ElasticquentTrait.php +++ b/src/ElasticquentTrait.php @@ -602,6 +602,20 @@ public static function typeExists() return $instance->getElasticSearchClient()->indices()->existsType($params); } + /** + * Check f document already exists? + * + * Retrieve an ElasticSearch document + * for this enty. + * + * @return array + */ + public function isDocumentExists() + { + return $this->getElasticSearchClient()->exists($this->getBasicEsParams()); + } + + /** * Delete Documents in A type by query. * Delete By Query Plugin required diff --git a/src/Laralastica.php b/src/Laralastica.php new file mode 100644 index 0000000..9119051 --- /dev/null +++ b/src/Laralastica.php @@ -0,0 +1,475 @@ +config = $config; + $this->request = $request; + + $this->client = $this->newClient(); + $this->index = $this->newIndex(); + } + + /** + * Run the elasticsearch query and then get the corresponding models for + * the results. + * + * @param string|array $types + * @param callable $query + * @param null|int $limit + * @param null|int $offset + * @return mixed + */ + public function search($types, Closure $query, $limit = null, $offset = null) + { + $results = $this->query($types, $query, $limit, $offset); + + return $this->resultsToModels($results); + } + + /** + * Run a search and then paginate the results using the laravel length + * aware paginator. + * + * @param string|array $types + * @param callable $query + * @param string|int $perPage + * @return LengthAwarePaginator + */ + public function paginate($types, Closure $query, $perPage) + { + $page = $this->request->has('page') ? $this->request->get('page') : 1; + $offset = $perPage * ($page - 1); + + // Get the total results + $this->query($types, $query); + + $total = $this->results->getTotalHits(); + $results = $this->search($types, $query, $perPage, $offset); + + return new LengthAwarePaginator($results, $total, $perPage, $page, [ + 'path' => $this->request->url(), + ]); + } + + /** + * Run a Elastica query and then return the results. + * + * @param string|array $types + * @param callable $query + * @param null $limit + * @param null $offset + * @return ResultSet + */ + public function query($types, Closure $query, $limit = null, $offset = null) + { + $builder = $this->newQueryBuilder(); + $query($builder); + + $search = $this->newSearch($this->client, $this->index, $types); + $query = $this->newQuery($builder->getQuery()); + + if (is_int($limit)) { + $query->setSize($limit); + } else { + $query->setSize($this->config['size']); + } + + if (is_array($this->sortFields) && count($this->sortFields)) { + $query->setSort($this->sortFields); + } + if (is_int($offset)) { + $query->setFrom($offset); + } + + $search->setQuery($query); + + + return $this->results = $search->search(); + } + + /** + * Add a new document to the provided type. + * + * @param string $type + * @param string|int $id + * @param array $data + * @return $this + */ + public function add($type, $id, array $data) + { + $type = $this->getType($type); + + $document = new Document($id, $data); + $type->addDocument($document); + + $this->refreshIndex(); + + return $this; + } + + /** + * Add multiple documents to the elasticsearch type. The data array must be a + * multidimensional array with the key as the desired id and the value as + * the data to be added to the document. + * + * @param string $type + * @param array $data + * @return $this + */ + public function addMultiple($type, array $data) + { + $type = $this->getType($type); + $documents = []; + + foreach ($data as $id => $values) { + $documents[] = new Document($id, $values); + } + + $type->addDocuments($documents); + + $this->refreshIndex(); + + return $this; + } + + /** + * Delete a document from the provided type. + * + * @param string $type + * @param string|int $id + * @return $this + */ + public function delete($type, $id) + { + $type = $this->getType($type); + $type->deleteById($id); + + $this->refreshIndex(); + + return $this; + } + + /** + * Return the total results from the last search. + * + * @return int + */ + public function getTotalHits() + { + if (isset($this->results)) { + return $this->results->getTotalHits(); + } + } + + /** + * Return the total amount of time for the last search. + * + * @return int + */ + public function getTotalTime() + { + if (isset($this->results)) { + return $this->results->getTotalTime(); + } + } + + /** + * Get an elasticsearch type from its index. + * + * @param string $type + * @return \Elastica\Type + */ + protected function getType($type) + { + if (!isset($this->index)) { + $this->index = $this->newIndex(); + } + + return $this->index->getType($type); + } + + /** + * Turn the elasticsearch results into a collection of models. + * + * @param ResultSet $resultSet + * @return Collection + */ + protected function resultsToModels(ResultSet $resultSet) + { + $results = $resultSet->getResults(); + + if (!empty($results)) { + $groupedResults = $this->groupResultsByType($results); + $modelResults = $this->getModelsFromGroupedResults($groupedResults); + $collection = $this->newCollection($modelResults); + + return $collection->sortByDesc(function ($model) { + return $model->score; + }); + } + + return $this->newCollection([]); + } + + /** + * Get th models from the grouped search results. + * + * @param $groupedResults + * @return array + */ + protected function getModelsFromGroupedResults($groupedResults) + { + $modelResults = []; + + foreach ($groupedResults as $key => $results) { + $modelName = $this->config['types'][$key]; + $model = new $modelName; + $query = $model->whereIn('id', array_keys($results)) + ->with($model::getEagerLoaded()) + ->orderBy(\DB::raw('FIELD(id, ' . implode(',', array_keys($results)) . ')'), 'ASC') + ->get(); + + $query->map(function ($model) use ($results) { + $model->score = $results[$model->getKey()]->getScore(); + }); + + $modelResults = array_merge_recursive($modelResults, $query->all()); + } + + return $modelResults; + } + + /** + * Group the + * + * @param array $results + * @return array + */ + protected function groupResultsByType(array $results) + { + $groupedResults = []; + + foreach ($results as $result) { + if (!isset($groupedResults[$result->getType()])) { + $groupedResults[$result->getType()] = []; + } + + $groupedResults[$result->getType()][$result->getId()] = $result; + } + + return $groupedResults; + } + + /** + * Create a new elastica client. + * + * @return Client + */ + protected function newClient() + { + $config = config('elasticquent')['config']; + if (class_exists('\Elasticsearch\ClientBuilder')) { + return \Elasticsearch\ClientBuilder::fromConfig($config); + } + return new Client($this->connection()); + } + + /** + * Get the elastica connection config. + * + * @return array + */ + protected function connection() + { + return [ + 'host' => !empty($this->config['host']) ? $this->config['host'] : null, + 'port' => !empty($this->config['port']) ? $this->config['port'] : null, + 'url' => !empty($this->config['url']) ? $this->config['url'] : null, + ]; + } + + /** + * Get the elasticsearch index being used. + * + * @return Index + */ + protected function newIndex() + { + if (!isset($this->client)) { + $this->client = $this->newClient(); + } + + return $this->client->getIndex($this->config['index']); + } + + /** + * Create a new laralastica query builder. + * + * @return Builder + */ + public function newQueryBuilder() + { + return new Builder($this->config); + } + + /** + * Create a new elastica search. + * + * @param Client $client + * @param Index $index + * @param string|array $types + * @return Search + */ + protected function newSearch(Client $client, Index $index, $types) + { + if (is_string($types)) { + $types = [$types]; + } + + $search = new Search($client); + + $search->addIndex($index); + $search->addTypes($types); + + return $search; + } + + /** + * Create a new elastica query from an array of queries. + * + * @param array $queries + * @return Query + */ + public function newQuery(array $queries) + { + if (!empty($queries)) { + $container = new BoolQuery(); + + foreach ($queries as $query) { + $container = $this->addQueryToContainer($query, $container); + } + + $query = new ElasticaQuery($container); + $query->addSort('_score'); + } else { + $query = new ElasticaQuery(); + } + + return $query; + } + + /** + * Set the type of match for the query and then add it to the bool container. + * + * @param QueryContract $query + * @param Bool $container + * @return Bool + */ + protected function addQueryToContainer(QueryContract $query, BoolQuery $container) + { + switch ($query->getType()) { + case "must": + $container->addMust($query->getQuery()); + break; + + case "should": + $container->addShould($query->getQuery()); + break; + + case "must_not": + $container->addMustNot($query->getQuery()); + break; + } + + return $container; + } + + /** + * Create a new collection. + * + * @param array $data + * @return Collection + */ + protected function newCollection(array $data) + { + return new Collection($data); + } + + /** + * Refreshes the elasticsearch index, should be run after adding + * or deleting documents. + * + * @return \Elastica\Response + */ + protected function refreshIndex() + { + return $this->index->refresh(); + } + + /** + * Sort on multiple fields. + * + * @param array $fields Associative array where the keys are field names to sort on, and the + * values are the sort order: "asc" or "desc" + * + * @return array + */ + + public function setSortFields(array $fields) + { + return $this->sortFields = $fields; + } + +} \ No newline at end of file diff --git a/src/Query.php b/src/Query.php new file mode 100644 index 0000000..73a3a20 --- /dev/null +++ b/src/Query.php @@ -0,0 +1,90 @@ +query = $query; + } + + /** + * Set that this query must be matched. + * + * @return Query + */ + public function must() + { + return $this->type('must'); + } + + /** + * Set that this query should be matched. + * + * @return Query + */ + public function should() + { + return $this->type('should'); + } + + /** + * Set that this query must not be matched. + * + * @return Query + */ + public function mustNot() + { + return $this->type('must_not'); + } + + /** + * Set the type of query. + * + * @param string $type + * @return $this + */ + protected function type($type) + { + $this->type = $type; + + return $this; + } + + /** + * Return the query. + * + * @return mixed + */ + public function getQuery() + { + return $this->query; + } + + /** + * Return the type of match. + * + * @return string + */ + public function getType() + { + return $this->type; + } + +} \ No newline at end of file From c5f7473a2a7e9db748fc8eec3d1027ce89e0c511 Mon Sep 17 00:00:00 2001 From: Amr Soliman Date: Wed, 9 Mar 2016 11:28:58 +0200 Subject: [PATCH 4/6] psr-4 code format --- composer.json | 11 +- src/Builder.php | 390 ++++++++++++++++++++++ src/Contracts/Builder.php | 151 +++++++++ src/Contracts/Query.php | 40 +++ src/Contracts/Wrapper.php | 87 +++++ src/ElasticquentCollectionTrait.php | 5 +- src/ElasticquentConfigTrait.php | 2 +- src/ElasticquentPaginator.php | 3 +- src/ElasticquentResultCollection.php | 6 +- src/ElasticquentTrait.php | 14 + src/Laralastica.php | 475 +++++++++++++++++++++++++++ src/Query.php | 90 +++++ 12 files changed, 1264 insertions(+), 10 deletions(-) create mode 100644 src/Builder.php create mode 100644 src/Contracts/Builder.php create mode 100644 src/Contracts/Query.php create mode 100644 src/Contracts/Wrapper.php create mode 100644 src/Laralastica.php create mode 100644 src/Query.php diff --git a/composer.json b/composer.json index e8bde8f..3542919 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "elasticquent/elasticquent", + "name": "amrsoliman/elasticquent", "type": "library", "description": "Maps Laravel Eloquent models to Elasticsearch types.", "keywords": [ @@ -7,9 +7,14 @@ "eloquent", "laravel" ], - "homepage": "https://github.com/elasticquent/Elasticquent", + "homepage": "https://github.com/AmrSoliman/Elasticquent", "license": "MIT", "authors": [ + { + "name": "Amr Soliman", + "email": "asoliman@engineer.com", + "role": "Developer" + }, { "name": "Adam Fairholm", "email": "adam.fairholm@gmail.com", @@ -21,7 +26,7 @@ "illuminate/database": "~4.2|^5", "illuminate/config": "~4.2|^5", "nesbot/carbon": "~1.0", - "elasticsearch/elasticsearch": ">1.0 <2.1" + "elasticsearch/elasticsearch": "2.1.*" }, "require-dev": { "phpunit/phpunit": "~5.0", diff --git a/src/Builder.php b/src/Builder.php new file mode 100644 index 0000000..084e8fa --- /dev/null +++ b/src/Builder.php @@ -0,0 +1,390 @@ +setFieldQuery($field, $query); + $match->setFieldType($field, $type); + + if ($fuzzy) { + $match->setFieldFuzziness($field, 'AUTO'); + } + + $query = $this->newQuery($match); + $this->query[] = $query; + + return $query; + } + + /** + * Find all documents where the value is matched in the fields. The type option + * allows you to specify the type of match, can be best_fields, most_fields, + * cross_fields, phrase, phrase_prefix. + * + * best_fields finds documents which match any field, but uses the _score + * from the best field. + * + * most_fields finds documents which match any field and combines the _score + * from each field. + * + * cross_fields treats fields with the same analyzer as though they were + * one big field. Looks for each word in any field. + * + * phrase runs a match_phrase query on each field and combines the _score + * from each field. + * + * phrase_prefix runs a match_phrase_prefix query on each field and combines + * the _score from each field. + * + * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html + * + * @param array $fields The fields to search in + * @param string $query The string to search for + * @param string $type The match type + * @param bool $fuzzy Set whether the match should be fuzzy + * @param float $tieBreaker Can be between 0.0 and 1.0 + * @param string $operator Can be 'and' or 'or' + * @return Query + */ + public function multiMatch( + array $fields, + $query, + $type = 'phrase', + $fuzzy = false, + $tieBreaker = 0.0, + $operator = 'and' + ) { + $match = new MultiMatch(); + + $match->setFields($fields); + $match->setQuery($query); + $match->setType($type); + + if ($fuzzy) { + $match->setFuzziness('AUTO'); + } + + if ($type == 'best_fields') { + $match->setTieBreaker($tieBreaker); + } + + if ($type == 'cross_fields') { + $match->setOperator($operator); + } + + $query = $this->newQuery($match); + $this->query[] = $query; + + return $query; + } + + /** + * Finds all documents matching the query but groups common words, + * i.e. the, and runs them after the initial query for more efficiency. + * + * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-common-terms-query.html + * + * @param string $field + * @param string $query + * @param float $cutOff + * @param int|bool $minimumMatch + * @return Query + */ + public function common($field, $query, $cutOff = 0.001, $minimumMatch = false) + { + $common = new Common($field, $query, $cutOff); + + if ($minimumMatch) { + $common->setMinimumShouldMatch($minimumMatch); + } + + $query = $this->newQuery($common); + $this->query[] = $query; + + return $query; + } + + /** + * A query which matches all documents. + * + * @return Query + */ + public function matchAll() + { + $match = new MatchAll(); + + $query = $this->newQuery($match); + $this->query[] = $query; + + return $query; + } + + /** + * Find all documents in a given range. The range is provided as an array with + * at least either a 'lt' or 'lte' key and a 'gt' or 'gte' key. + * + * 'lt' stands for less than + * 'lte' for less than or equal to + * 'gt' for greater than + * 'gte' for greater than or equal to + * + * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-range-query.html + * + * @param string $field + * @param array $range + * @param bool $timeZone + * @param bool $format + * @return Query + */ + public function range($field, array $range, $timeZone = false, $format = false) + { + $range = new Range($field, $range); + + if ($timeZone) { + $range->setParam('time_zone', $timeZone); + } + + if ($format) { + $range->setParam('format', $format); + } + + $query = $this->newQuery($range); + $this->query[] = $query; + + return $query; + } + + /** + * Find all documents matching the provided regular expression. + * + * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html + * + * @param string $field + * @param string $regex + * @return Query + */ + public function regexp($field, $regex) + { + $regexp = new Regexp($field, $regex); + + $query = $this->newQuery($regexp); + $this->query[] = $query; + + return $query; + } + + /** + * Find a document matching an exact term. + * + * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-term-query.html + * + * @param string $key + * @param string $value + * @param float $boost + * @return Query + */ + public function term($key, $value, $boost = 1.0) + { + $term = new Term(); + $term->setTerm($key, $value, $boost); + + $query = $this->newQuery($term); + $this->query[] = $query; + + return $query; + } + + /** + * Find any documents matching the provided terms, optionally you can set a + * minimum amount of terms to match. + * + * @param string $key + * @param array $terms + * @param bool|int $minimumShouldMatch + * @return Query + */ + public function terms($key, array $terms, $minimumShouldMatch = false) + { + $query = new Terms($key, $terms); + + if ($minimumShouldMatch) { + $query->setMinimumMatch($minimumShouldMatch); + } + + $query = $this->newQuery($query); + $this->query[] = $query; + + return $query; + } + + /** + * Find a document matching a value containing a wildcard. Please note wildcard + * queries can be very slow, to avoid this don't start a string with a wildcard. + * + * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-wildcard-query.html + * + * @param string $key + * @param string $value + * @param float $boost + * @return Query + */ + public function wildcard($key, $value, $boost = 1.0) + { + $query = new Wildcard($key, $value, $boost); + + $query = $this->newQuery($query); + $this->query[] = $query; + + return $query; + } + + /** + * The fuzzy query generates all possible matching terms that are within + * the maximum edit distance specified in fuzziness and then checks the + * term dictionary to find out which of those generated terms actually + * exist in the index. + * + * @link https://www.elastic.co/guide/en/elasticsearch/reference/1.4/query-dsl-fuzzy-query.html + * + * @param string $key + * @param string $value + * @param array $options + * @return Fuzzy + */ + public function fuzzy($key, $value, $options = []) + { + $query = new Fuzzy($key, $value); + + foreach ($options as $option => $value) { + $query->setFieldOption($option, $value); + } + + $this->query[] = $this->newQuery($query); + + return $query; + } + + /** + * Find documents that are "like" provided text by running it against one or more + * fields. + * + * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-flt-query.html + * + * @param string $value + * @param array $fields + * @param float $fuzziness + * @return FuzzyLikeThis + */ + public function fuzzyLikeThis($value, array $fields = [], $fuzziness = 0.5) + { + $query = new FuzzyLikeThis(); + + $query->setLikeText($value); + + if (!empty($fields)) { + $query->addFields($fields); + } + + $query->setMinSimilarity($fuzziness); + + $this->query[] = $this->newQuery($query); + + return $query; + } + + /** + * Add an abstract elastica query to array + * + * @param AbstractQuery $query + * @return Query + */ + public function addQuery(AbstractQuery $query) + { + $query = $this->newQuery($query); + $this->query[] = $query; + + return $query; + } + + /** + * Get the queries to be run. + * + * @return array + */ + public function getQuery() + { + return $this->query; + } + + /** + * Create a new query wrapper. + * + * @param mixed $query + * @return Query + */ + protected function newQuery($query) + { + return new Query($query); + } + + + public function sortBy($sort) + { + $query = new \Elastica\Query('agg_name'); + $query->setSort($sort); + $this->query[] = $query; + + return $query; + } + +} \ No newline at end of file diff --git a/src/Contracts/Builder.php b/src/Contracts/Builder.php new file mode 100644 index 0000000..6739987 --- /dev/null +++ b/src/Contracts/Builder.php @@ -0,0 +1,151 @@ +perPage = $perPage; $this->lastPage = (int) ceil($total / $perPage); $this->currentPage = $this->setCurrentPage($currentPage, $this->lastPage); - $this->path = $this->path != '/' ? rtrim($this->path, '/') . '/' : $this->path; + $this->path = $this->path != '/' ? rtrim($this->path, '/').'/' : $this->path; $this->items = $items instanceof Collection ? $items : Collection::make($items); $this->hits = $hits; } - /** * Get the instance as an array. * diff --git a/src/ElasticquentResultCollection.php b/src/ElasticquentResultCollection.php index 21010c2..32d266a 100644 --- a/src/ElasticquentResultCollection.php +++ b/src/ElasticquentResultCollection.php @@ -12,10 +12,12 @@ class ElasticquentResultCollection extends \Illuminate\Database\Eloquent\Collect protected $instance; /** - * Create a new instance containing Elasticsearch results + * _construct * - * @param $results elasticsearch results + * @param $results elasticsearch results * @param $instance + * + * @return \Elasticquent\ElasticquentResultCollection */ public function __construct($results, $instance) { diff --git a/src/ElasticquentTrait.php b/src/ElasticquentTrait.php index 4f98b81..94856bb 100644 --- a/src/ElasticquentTrait.php +++ b/src/ElasticquentTrait.php @@ -602,6 +602,20 @@ public static function typeExists() return $instance->getElasticSearchClient()->indices()->existsType($params); } + /** + * Check f document already exists? + * + * Retrieve an ElasticSearch document + * for this enty. + * + * @return array + */ + public function isDocumentExists() + { + return $this->getElasticSearchClient()->exists($this->getBasicEsParams()); + } + + /** * Delete Documents in A type by query. * Delete By Query Plugin required diff --git a/src/Laralastica.php b/src/Laralastica.php new file mode 100644 index 0000000..9119051 --- /dev/null +++ b/src/Laralastica.php @@ -0,0 +1,475 @@ +config = $config; + $this->request = $request; + + $this->client = $this->newClient(); + $this->index = $this->newIndex(); + } + + /** + * Run the elasticsearch query and then get the corresponding models for + * the results. + * + * @param string|array $types + * @param callable $query + * @param null|int $limit + * @param null|int $offset + * @return mixed + */ + public function search($types, Closure $query, $limit = null, $offset = null) + { + $results = $this->query($types, $query, $limit, $offset); + + return $this->resultsToModels($results); + } + + /** + * Run a search and then paginate the results using the laravel length + * aware paginator. + * + * @param string|array $types + * @param callable $query + * @param string|int $perPage + * @return LengthAwarePaginator + */ + public function paginate($types, Closure $query, $perPage) + { + $page = $this->request->has('page') ? $this->request->get('page') : 1; + $offset = $perPage * ($page - 1); + + // Get the total results + $this->query($types, $query); + + $total = $this->results->getTotalHits(); + $results = $this->search($types, $query, $perPage, $offset); + + return new LengthAwarePaginator($results, $total, $perPage, $page, [ + 'path' => $this->request->url(), + ]); + } + + /** + * Run a Elastica query and then return the results. + * + * @param string|array $types + * @param callable $query + * @param null $limit + * @param null $offset + * @return ResultSet + */ + public function query($types, Closure $query, $limit = null, $offset = null) + { + $builder = $this->newQueryBuilder(); + $query($builder); + + $search = $this->newSearch($this->client, $this->index, $types); + $query = $this->newQuery($builder->getQuery()); + + if (is_int($limit)) { + $query->setSize($limit); + } else { + $query->setSize($this->config['size']); + } + + if (is_array($this->sortFields) && count($this->sortFields)) { + $query->setSort($this->sortFields); + } + if (is_int($offset)) { + $query->setFrom($offset); + } + + $search->setQuery($query); + + + return $this->results = $search->search(); + } + + /** + * Add a new document to the provided type. + * + * @param string $type + * @param string|int $id + * @param array $data + * @return $this + */ + public function add($type, $id, array $data) + { + $type = $this->getType($type); + + $document = new Document($id, $data); + $type->addDocument($document); + + $this->refreshIndex(); + + return $this; + } + + /** + * Add multiple documents to the elasticsearch type. The data array must be a + * multidimensional array with the key as the desired id and the value as + * the data to be added to the document. + * + * @param string $type + * @param array $data + * @return $this + */ + public function addMultiple($type, array $data) + { + $type = $this->getType($type); + $documents = []; + + foreach ($data as $id => $values) { + $documents[] = new Document($id, $values); + } + + $type->addDocuments($documents); + + $this->refreshIndex(); + + return $this; + } + + /** + * Delete a document from the provided type. + * + * @param string $type + * @param string|int $id + * @return $this + */ + public function delete($type, $id) + { + $type = $this->getType($type); + $type->deleteById($id); + + $this->refreshIndex(); + + return $this; + } + + /** + * Return the total results from the last search. + * + * @return int + */ + public function getTotalHits() + { + if (isset($this->results)) { + return $this->results->getTotalHits(); + } + } + + /** + * Return the total amount of time for the last search. + * + * @return int + */ + public function getTotalTime() + { + if (isset($this->results)) { + return $this->results->getTotalTime(); + } + } + + /** + * Get an elasticsearch type from its index. + * + * @param string $type + * @return \Elastica\Type + */ + protected function getType($type) + { + if (!isset($this->index)) { + $this->index = $this->newIndex(); + } + + return $this->index->getType($type); + } + + /** + * Turn the elasticsearch results into a collection of models. + * + * @param ResultSet $resultSet + * @return Collection + */ + protected function resultsToModels(ResultSet $resultSet) + { + $results = $resultSet->getResults(); + + if (!empty($results)) { + $groupedResults = $this->groupResultsByType($results); + $modelResults = $this->getModelsFromGroupedResults($groupedResults); + $collection = $this->newCollection($modelResults); + + return $collection->sortByDesc(function ($model) { + return $model->score; + }); + } + + return $this->newCollection([]); + } + + /** + * Get th models from the grouped search results. + * + * @param $groupedResults + * @return array + */ + protected function getModelsFromGroupedResults($groupedResults) + { + $modelResults = []; + + foreach ($groupedResults as $key => $results) { + $modelName = $this->config['types'][$key]; + $model = new $modelName; + $query = $model->whereIn('id', array_keys($results)) + ->with($model::getEagerLoaded()) + ->orderBy(\DB::raw('FIELD(id, ' . implode(',', array_keys($results)) . ')'), 'ASC') + ->get(); + + $query->map(function ($model) use ($results) { + $model->score = $results[$model->getKey()]->getScore(); + }); + + $modelResults = array_merge_recursive($modelResults, $query->all()); + } + + return $modelResults; + } + + /** + * Group the + * + * @param array $results + * @return array + */ + protected function groupResultsByType(array $results) + { + $groupedResults = []; + + foreach ($results as $result) { + if (!isset($groupedResults[$result->getType()])) { + $groupedResults[$result->getType()] = []; + } + + $groupedResults[$result->getType()][$result->getId()] = $result; + } + + return $groupedResults; + } + + /** + * Create a new elastica client. + * + * @return Client + */ + protected function newClient() + { + $config = config('elasticquent')['config']; + if (class_exists('\Elasticsearch\ClientBuilder')) { + return \Elasticsearch\ClientBuilder::fromConfig($config); + } + return new Client($this->connection()); + } + + /** + * Get the elastica connection config. + * + * @return array + */ + protected function connection() + { + return [ + 'host' => !empty($this->config['host']) ? $this->config['host'] : null, + 'port' => !empty($this->config['port']) ? $this->config['port'] : null, + 'url' => !empty($this->config['url']) ? $this->config['url'] : null, + ]; + } + + /** + * Get the elasticsearch index being used. + * + * @return Index + */ + protected function newIndex() + { + if (!isset($this->client)) { + $this->client = $this->newClient(); + } + + return $this->client->getIndex($this->config['index']); + } + + /** + * Create a new laralastica query builder. + * + * @return Builder + */ + public function newQueryBuilder() + { + return new Builder($this->config); + } + + /** + * Create a new elastica search. + * + * @param Client $client + * @param Index $index + * @param string|array $types + * @return Search + */ + protected function newSearch(Client $client, Index $index, $types) + { + if (is_string($types)) { + $types = [$types]; + } + + $search = new Search($client); + + $search->addIndex($index); + $search->addTypes($types); + + return $search; + } + + /** + * Create a new elastica query from an array of queries. + * + * @param array $queries + * @return Query + */ + public function newQuery(array $queries) + { + if (!empty($queries)) { + $container = new BoolQuery(); + + foreach ($queries as $query) { + $container = $this->addQueryToContainer($query, $container); + } + + $query = new ElasticaQuery($container); + $query->addSort('_score'); + } else { + $query = new ElasticaQuery(); + } + + return $query; + } + + /** + * Set the type of match for the query and then add it to the bool container. + * + * @param QueryContract $query + * @param Bool $container + * @return Bool + */ + protected function addQueryToContainer(QueryContract $query, BoolQuery $container) + { + switch ($query->getType()) { + case "must": + $container->addMust($query->getQuery()); + break; + + case "should": + $container->addShould($query->getQuery()); + break; + + case "must_not": + $container->addMustNot($query->getQuery()); + break; + } + + return $container; + } + + /** + * Create a new collection. + * + * @param array $data + * @return Collection + */ + protected function newCollection(array $data) + { + return new Collection($data); + } + + /** + * Refreshes the elasticsearch index, should be run after adding + * or deleting documents. + * + * @return \Elastica\Response + */ + protected function refreshIndex() + { + return $this->index->refresh(); + } + + /** + * Sort on multiple fields. + * + * @param array $fields Associative array where the keys are field names to sort on, and the + * values are the sort order: "asc" or "desc" + * + * @return array + */ + + public function setSortFields(array $fields) + { + return $this->sortFields = $fields; + } + +} \ No newline at end of file diff --git a/src/Query.php b/src/Query.php new file mode 100644 index 0000000..73a3a20 --- /dev/null +++ b/src/Query.php @@ -0,0 +1,90 @@ +query = $query; + } + + /** + * Set that this query must be matched. + * + * @return Query + */ + public function must() + { + return $this->type('must'); + } + + /** + * Set that this query should be matched. + * + * @return Query + */ + public function should() + { + return $this->type('should'); + } + + /** + * Set that this query must not be matched. + * + * @return Query + */ + public function mustNot() + { + return $this->type('must_not'); + } + + /** + * Set the type of query. + * + * @param string $type + * @return $this + */ + protected function type($type) + { + $this->type = $type; + + return $this; + } + + /** + * Return the query. + * + * @return mixed + */ + public function getQuery() + { + return $this->query; + } + + /** + * Return the type of match. + * + * @return string + */ + public function getType() + { + return $this->type; + } + +} \ No newline at end of file From 201e3ea402a65cf7b3b14786d04163768aa0b44d Mon Sep 17 00:00:00 2001 From: Amr Soliman Date: Thu, 10 Mar 2016 01:59:15 +0200 Subject: [PATCH 5/6] check if index Exists --- src/ElasticquentTrait.php | 63 +++++++-------------------------------- 1 file changed, 11 insertions(+), 52 deletions(-) diff --git a/src/ElasticquentTrait.php b/src/ElasticquentTrait.php index 94856bb..8942839 100644 --- a/src/ElasticquentTrait.php +++ b/src/ElasticquentTrait.php @@ -519,35 +519,15 @@ public static function rebuildMapping() return $instance->putMapping(); } - /** - * Index Exists ? - * - * @return bool - */ - public static function indexExists() - { - $instance = new static; - - $client = $instance->getElasticSearchClient(); - - $index = array( - 'index' => $instance->getIndexName(), - ); - - return $client->indices()->exists($index); - - - } - /** * Create Index * * @param int $shards * @param int $replicas - * + * @param array $extraSettings * @return array */ - public static function createIndex($shards = null, $replicas = null) + public static function createIndex($shards = null, $replicas = null, $extraSettings = array()) { $instance = new static; @@ -564,6 +544,9 @@ public static function createIndex($shards = null, $replicas = null) if (!is_null($replicas)) { $index['body']['settings']['number_of_replicas'] = $replicas; } + if (count($extraSettings)) { + $index['body']['settings'] = $extraSettings; + } return $client->indices()->create($index); } @@ -603,44 +586,20 @@ public static function typeExists() } /** - * Check f document already exists? + * Index Exists. * - * Retrieve an ElasticSearch document - * for this enty. + * Does this type exist? * - * @return array - */ - public function isDocumentExists() - { - return $this->getElasticSearchClient()->exists($this->getBasicEsParams()); - } - - - /** - * Delete Documents in A type by query. - * Delete By Query Plugin required - * https://www.elastic.co/guide/en/elasticsearch/plugins/2.0/plugins-delete-by-query.html#plugins-delete-by-query - * @param array $query + * @return bool */ - public static function deleteDocuments($query = array()) + public static function indexExists() { $instance = new static; - $params = $instance->getBasicEsParams(); - $params['body']['query'] = $query; + $params = ['index' => $instance->getIndexName()]; - return $instance->getElasticSearchClient()->deleteByQuery($params); + return $instance->getElasticSearchClient()->indices()->exists($params); } - /** - * Delete all Documents in A type. - * Delete By Query Plugin required - * https://www.elastic.co/guide/en/elasticsearch/plugins/2.0/plugins-delete-by-query.html#plugins-delete-by-query - * @return array - */ - public static function deleteAllDocuments() - { - return self::deleteDocuments(array('match_all' => [])); - } /** * New From Hit Builder From 15e7b842558dc77b03d2afe929e277be8fbd642e Mon Sep 17 00:00:00 2001 From: Amr Soliman Date: Mon, 27 Feb 2017 21:20:03 +0200 Subject: [PATCH 6/6] upgrade to ES5,allow AWS 4sign --- src/ElasticquentClientTrait.php | 17 +++++++++++++++++ src/config/elasticquent.php | 8 ++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/ElasticquentClientTrait.php b/src/ElasticquentClientTrait.php index 63045a5..3b7749e 100644 --- a/src/ElasticquentClientTrait.php +++ b/src/ElasticquentClientTrait.php @@ -2,6 +2,11 @@ namespace Elasticquent; +use Aws\Credentials\CredentialProvider; +use Aws\Credentials\Credentials; +use Aws\ElasticsearchService\ElasticsearchPhpHandler; +use Elasticsearch\ClientBuilder; + trait ElasticquentClientTrait { use ElasticquentConfigTrait; @@ -14,7 +19,19 @@ trait ElasticquentClientTrait public function getElasticSearchClient() { $config = $this->getElasticConfig(); +// dd($config); + $isAWS = $this->getElasticConfig('ELASTIC_AWS'); + if ($isAWS) { + $provider = CredentialProvider::fromCredentials( + new Credentials($config['aws_key'], $config['aws_secret']) + ); + $handler = new ElasticsearchPhpHandler($config['aws_region'], $provider); + return ClientBuilder::create() + ->setHandler($handler) + ->setHosts($config['hosts']) + ->build(); + } // elasticsearch v2.0 using builder if (class_exists('\Elasticsearch\ClientBuilder')) { return \Elasticsearch\ClientBuilder::fromConfig($config); diff --git a/src/config/elasticquent.php b/src/config/elasticquent.php index 7562a61..ea881c3 100644 --- a/src/config/elasticquent.php +++ b/src/config/elasticquent.php @@ -12,10 +12,10 @@ | | http://www.elasticsearch.org/guide/en/elasticsearch/client/php-api/current/_configuration.html */ - - 'config' => [ - 'hosts' => ['localhost:9200'], - 'retries' => 1, + 'aws_signiutue' => false, + 'config' => [ + 'hosts' => ['localhost:9200'], + 'retries' => 1, ], /*