diff --git a/README.md b/README.md new file mode 100644 index 0000000..f10144f --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +ElasticSearch Query Builder +=========================== + +This is a PHP library which helps you build query for an ElasticSearch client by using a fluent interface. + +Installation +------------ + +``` +$ composer require erichard/elasticsearch-query-builder +``` + +Usage +----- + +``` + +use Erichard\ElasticQueryBuilder\QueryBuilder; +use Erichard\ElasticQueryBuilder\Aggregation\Aggregation; +use Erichard\ElasticQueryBuilder\Filter\Filter; + +$qb = new QueryBuilder(); + +$qb + ->setType('my_type') + ->setIndex('app') + ->setSize(10) +; + +// Add an aggregation +$qb->addAggregation(Aggregation::terms('agg_name')->setField('my_field')); + +// Add a filter +$boolFilter = Filter::bool(); +$boolFilter->addFilter(Filter::terms()->setField('field')->setValue($value)); + + +$qb->addFilter($boolFilter); + +// I am using a client from elasticsearch/elasticsearch here +$results = $client->search($qb->getQuery()); + +``` diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..d820294 --- /dev/null +++ b/composer.json @@ -0,0 +1,18 @@ +{ + "name": "erichard/elasticsearch-query-builder", + "license": "MIT", + "type": "library", + "description": "Create elastic search query with a fluent interface", + "authors": [ + { + "name": "Erwan Richard", + "email": "erwan.richard@protonmail.com" + } + ], + "autoload": { + "psr-4": { "Erichard\\ElasticQueryBuilder\\": "src/" } + }, + "require": { + "php": ">=7.0" + } +} diff --git a/src/Aggregation/Aggregation.php b/src/Aggregation/Aggregation.php new file mode 100644 index 0000000..36af82a --- /dev/null +++ b/src/Aggregation/Aggregation.php @@ -0,0 +1,35 @@ +name = $name; + } + + public function getName() + { + return $this->name; + } + + abstract public function build(): array; + + public static function terms($name = null) + { + return new TermsAggregation($name); + } + + public static function nested($name = null) + { + return new NestedAggregation($name); + } + + public static function filter($name = null) + { + return new FilterAggregation($name); + } +} diff --git a/src/Aggregation/FilterAggregation.php b/src/Aggregation/FilterAggregation.php new file mode 100644 index 0000000..d1b5e52 --- /dev/null +++ b/src/Aggregation/FilterAggregation.php @@ -0,0 +1,35 @@ +filter = $filter; + + return $this; + } + + public function setAggregation(Aggregation $aggregation) + { + $this->aggregation = $aggregation; + + return $this; + } + + public function build(): array + { + return [ + 'filter' => $this->filter->build(), + 'aggs' => [ + $this->aggregation->getName() => $this->aggregation->build(), + ], + ]; + } +} diff --git a/src/Aggregation/NestedAggregation.php b/src/Aggregation/NestedAggregation.php new file mode 100644 index 0000000..a57d11b --- /dev/null +++ b/src/Aggregation/NestedAggregation.php @@ -0,0 +1,35 @@ +path = $path; + + return $this; + } + + public function setAggregation(Aggregation $aggregation) + { + $this->aggregation = $aggregation; + + return $this; + } + + public function build(): array + { + return [ + 'nested' => [ + 'path' => $this->path, + ], + 'aggs' => [ + $this->aggregation->getName() => $this->aggregation->build(), + ], + ]; + } +} diff --git a/src/Aggregation/TermsAggregation.php b/src/Aggregation/TermsAggregation.php new file mode 100644 index 0000000..0038474 --- /dev/null +++ b/src/Aggregation/TermsAggregation.php @@ -0,0 +1,52 @@ +field = $field; + + return $this; + } + + public function setSize(int $size) + { + $this->size = $size; + + return $this; + } + + public function setScript(string $script) + { + $this->script = $script; + + return $this; + } + + public function build(): array + { + if (null !== $this->script) { + $term = [ + 'script' => [ + 'inline' => $this->script, + 'lang' => 'painless', + ], + ]; + } else { + $term = [ + 'field' => $this->field, + 'size' => $this->size, + ]; + } + + return [ + 'terms' => $term, + ]; + } +} diff --git a/src/Filter/BoolFilter.php b/src/Filter/BoolFilter.php new file mode 100644 index 0000000..557fbb3 --- /dev/null +++ b/src/Filter/BoolFilter.php @@ -0,0 +1,91 @@ +must[] = $filter; + + return $this; + } + + public function addMustNot(Filter $filter) + { + $this->mustNot[] = $filter; + + return $this; + } + + public function addShould(Filter $filter) + { + $this->should[] = $filter; + + return $this; + } + + public function addFilter(Filter $filter) + { + $this->filter[] = $filter; + + return $this; + } + + public function isEmpty() + { + return empty($this->must) + && empty($this->mustNot) + && empty($this->should) + && empty($this->filter) + ; + } + + public function build(): array + { + $filter = []; + + if (!empty($this->must)) { + $filter['must'] = []; + foreach ($this->must as $f) { + $filter['must'][] = $f->build(); + } + } + + if (!empty($this->mustNot)) { + $filter['must_not'] = []; + foreach ($this->mustNot as $f) { + $filter['must_not'][] = $f->build(); + } + } + + if (!empty($this->filter)) { + $filter['filter'] = []; + foreach ($this->filter as $f) { + $filter['filter'][] = $f->build(); + } + } + + if (!empty($this->should)) { + $filter['should'] = []; + foreach ($this->should as $f) { + $filter['should'][] = $f->build(); + } + } + + if (empty($filter)) { + throw new QueryException('Empty filter'); + } + + return [ + 'bool' => $filter, + ]; + } +} diff --git a/src/Filter/Filter.php b/src/Filter/Filter.php new file mode 100644 index 0000000..08fd1c7 --- /dev/null +++ b/src/Filter/Filter.php @@ -0,0 +1,43 @@ +distance = $distance; + + return $this; + } + + public function setPinLocation($pinLocation) + { + $this->pinLocation = $pinLocation; + + return $this; + } + + public function build(): array + { + return [ + 'geo_distance' => [ + 'distance' => $this->distance, + 'geolocation' => $this->pinLocation, + ], + ]; + } +} diff --git a/src/Filter/MatchFilter.php b/src/Filter/MatchFilter.php new file mode 100644 index 0000000..69beec2 --- /dev/null +++ b/src/Filter/MatchFilter.php @@ -0,0 +1,54 @@ +field = $field; + + return $this; + } + + public function setQuery(string $query) + { + $this->query = $query; + + return $this; + } + + public function setAnalyzer($analyzer) + { + $this->analyzer = $analyzer; + + return $this; + } + + public function build(): array + { + if (null === $this->query) { + throw new QueryException('You need to call setQuery() on'.__CLASS__); + } + + $query = [ + 'match' => [ + $this->field => [ + 'query' => $this->query, + ], + ], + ]; + + if (null !== $this->analyzer) { + $query['match'][$this->field]['analyzer'] = $this->analyzer; + } + + return $query; + } +} diff --git a/src/Filter/NestedFilter.php b/src/Filter/NestedFilter.php new file mode 100644 index 0000000..1f73f8a --- /dev/null +++ b/src/Filter/NestedFilter.php @@ -0,0 +1,33 @@ +path = $path; + + return $this; + } + + public function setFilter(Filter $filter) + { + $this->filter = $filter; + + return $this; + } + + public function build(): array + { + return [ + 'nested' => [ + 'path' => $this->path, + 'query' => $this->filter->build(), + ], + ]; + } +} diff --git a/src/Filter/RangeFilter.php b/src/Filter/RangeFilter.php new file mode 100644 index 0000000..2197589 --- /dev/null +++ b/src/Filter/RangeFilter.php @@ -0,0 +1,77 @@ +field = $field; + + return $this; + } + + public function gt($value) + { + $this->gt = $value; + + return $this; + } + + public function lt($value) + { + $this->lt = $value; + + return $this; + } + + public function gte($value) + { + $this->gte = $value; + + return $this; + } + + public function lte($value) + { + $this->lte = $value; + + return $this; + } + + public function build(): array + { + $filter = []; + + if (null !== $this->gt) { + $filter['gt'] = $this->gt; + } + if (null !== $this->lt) { + $filter['lt'] = $this->lt; + } + if (null !== $this->gte) { + $filter['gte'] = $this->gte; + } + if (null !== $this->lte) { + $filter['lte'] = $this->lte; + } + + if (empty($filter)) { + throw new QueryException('Empty filter'); + } + + return [ + 'range' => [ + $this->field => $filter, + ], + ]; + } +} diff --git a/src/Filter/TermFilter.php b/src/Filter/TermFilter.php new file mode 100644 index 0000000..102e88f --- /dev/null +++ b/src/Filter/TermFilter.php @@ -0,0 +1,41 @@ +field = $field; + + return $this; + } + + public function setValue($value) + { + $this->value = $value; + + return $this; + } + + public function build(): array + { + if (null === $this->field) { + throw new QueryException('You need to call setField() on'.__CLASS__); + } + if (null === $this->value) { + throw new QueryException('You need to call setValue() on'.__CLASS__); + } + + return [ + 'term' => [ + $this->field => $this->value, + ], + ]; + } +} diff --git a/src/Filter/TermsFilter.php b/src/Filter/TermsFilter.php new file mode 100644 index 0000000..f9a3bcc --- /dev/null +++ b/src/Filter/TermsFilter.php @@ -0,0 +1,15 @@ + [ + $this->field => $this->value, + ], + ]; + } +} diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php new file mode 100644 index 0000000..5688abc --- /dev/null +++ b/src/QueryBuilder.php @@ -0,0 +1,106 @@ +query = $query; + } + + public function setType($type) + { + $this->query['type'] = $type; + + return $this; + } + + public function setIndex($index) + { + $this->query['index'] = $index; + + return $this; + } + + public function setFrom($from) + { + $this->query['from'] = $from; + + return $this; + } + + public function setSize($size) + { + $this->query['size'] = $size; + + return $this; + } + + public function addAggregation(Aggregation $aggregation) + { + $this->aggregations[] = $aggregation; + + return $this; + } + + public function addFilter(Filter $filter) + { + $this->filters[] = $filter; + + return $this; + } + + public function setPostFilter(Filter $filter) + { + $this->postFilter = $filter; + } + + public function getQuery() + { + $query = $this->query; + + if (!empty($this->aggregations)) { + $query['body']['aggs'] = []; + foreach ($this->aggregations as $aggregation) { + $query['body']['aggs'][$aggregation->getName()] = $aggregation->build(); + } + } + + if (!empty($this->filters)) { + $query['body']['query'] = []; + foreach ($this->filters as $filter) { + $query['body']['query'] = $filter->build(); + } + } + + if (null !== $this->postFilter) { + $query['body']['post_filter'] = $this->postFilter->build(); + } + + return $query; + } +} diff --git a/src/QueryException.php b/src/QueryException.php new file mode 100644 index 0000000..58f009e --- /dev/null +++ b/src/QueryException.php @@ -0,0 +1,7 @@ +