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/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/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 @@ +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/ElasticquentCollectionTrait.php b/src/ElasticquentCollectionTrait.php index ae51d51..2152862 100644 --- a/src/ElasticquentCollectionTrait.php +++ b/src/ElasticquentCollectionTrait.php @@ -13,9 +13,10 @@ trait ElasticquentCollectionTrait /** * Add To Index * - * Add all documents in this collection to to the Elasticsearch document index. + * Add all documents in this collection to + * to the Elasticsearch document index. * - * @return null|array + * @return mixed */ public function addToIndex() { diff --git a/src/ElasticquentConfigTrait.php b/src/ElasticquentConfigTrait.php index 77912d2..a4f2500 100644 --- a/src/ElasticquentConfigTrait.php +++ b/src/ElasticquentConfigTrait.php @@ -54,6 +54,6 @@ protected function getConfigHelper() */ protected function getConfigFile() { - return __DIR__ . '/config/elasticquent.php'; + return __DIR__.'/config/elasticquent.php'; } } diff --git a/src/ElasticquentPaginator.php b/src/ElasticquentPaginator.php index 84de151..3fe7562 100644 --- a/src/ElasticquentPaginator.php +++ b/src/ElasticquentPaginator.php @@ -24,11 +24,10 @@ public function __construct($items, $hits, $total, $perPage, $currentPage = null $this->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 55400fd..8942839 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(), ); @@ -523,10 +524,10 @@ public static function rebuildMapping() * * @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; @@ -543,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); } @@ -581,6 +585,22 @@ public static function typeExists() return $instance->getElasticSearchClient()->indices()->existsType($params); } + /** + * Index Exists. + * + * Does this type exist? + * + * @return bool + */ + public static function indexExists() + { + $instance = new static; + $params = ['index' => $instance->getIndexName()]; + + return $instance->getElasticSearchClient()->indices()->exists($params); + } + + /** * New From Hit Builder * 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 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, ], /*