From 44b60cfe44d1c6cf8eab6275c5453bc47aea40ab Mon Sep 17 00:00:00 2001 From: Claudio Pinto Date: Tue, 6 Nov 2018 23:10:28 +0000 Subject: [PATCH] Added custom builder to allow search with closure and orWhere --- README.md | 16 +- src/Builder.php | 228 ++++++++++++++++++++++++++++ src/Engines/ElasticSearchEngine.php | 6 +- src/Traits/Searchable.php | 16 +- 4 files changed, 263 insertions(+), 3 deletions(-) create mode 100644 src/Builder.php diff --git a/README.md b/README.md index a7c3f30..fbc3a57 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,21 @@ Change the config/scout.php file to include elasticsearch settings ## Usage -The driver will work just like described in laravel scout [documentation](https://laravel.com/docs/5.5/scout) +The driver will work just like described in laravel scout [documentation](https://laravel.com/docs/5.5/scout). + +If you want the ability to use closures in where and orWhere methods your model must use the Searchable trait included in the package + +```php +model, $this->query)); + $this->nestedWhere($query); + + return $this; + } + $this->addWhere($field, $value); + + return $this; + } + + /** + * @param string|Closure $field + * @param null $value + * + * @return $this + * @throws \Exception + */ + public function orWhere($field, $value = null) + { + if (is_callable($field)) { + $field($query = new self($this->model, $this->query)); + $this->nestedWhere($query, self::TYPE_OR); + + return $this; + } + $this->addWhere($field, $value, self::TYPE_OR); + + return $this; + } + + /** + * @return string + */ + public function toSql() + { + return $this->parseGroup($this->wheres); + } + + /** + * @return array + */ + public function toESDL() + { + return $this->parseEsGroup($this->wheres); + } + + /** + * @param array $list + * + * @return array + */ + private function parseEsGroup($list = []) + { + $query = []; + if (count($list)) { + $ands = []; + $ors = []; + foreach ($list as $item) { + if ($item['type'] == self::TYPE_OR) { + $ors[] = $item; + } else { + $ands[] = $item; + } + } + + $tmp = [ + 'bool' => [] + ]; + + if (count($ors)) { + $tmp['bool'] = ['should' => []]; + $using = &$tmp['bool']['should']; + + foreach ($ors as $where) { + $using[] = $this->parseEsWhere($where); + } + + if (count($ands)) { + $using[] = [ + 'bool' => [ + 'must' => $this->parseEsGroup($ands) + ] + ]; + } + } else { + $tmp['bool'] = ['must' => []]; + $using = &$tmp['bool']['must']; + + foreach ($ands as $where) { + $using[] = $this->parseEsWhere($where); + } + } + + $query[] = $tmp; + } + + return $query; + } + + /** + * @param $where + * + * @return array|null + */ + private function parseEsWhere($where) + { + if (isset($where['field'])) { + return $this->parseEsEntry($where); + } + + $group = $this->parseEsGroup($where['group']); + + return $group[0] ?? null; + } + + /** + * @param $where + * + * @return array + */ + private function parseEsEntry($where) + { + if (is_array($where['value'])) { + return [ + 'terms' => [ + $where['field'] => $where['value'] + ] + ]; + } + + return [ + 'match_phrase' => [ + $where['field'] => $where['value'] + ] + ]; + } + + /** + * @param array $list + * + * @return string + */ + private function parseGroup($list = []) + { + $sql = ''; + foreach ($list as $i => $item) { + if ($i > 0) { + $sql .= ' ' . $item['type'] . ' '; + } + + if (isset($item['field'])) { + $sql .= sprintf('%s = %s', $item['field'], $item['value']); + } elseif (!empty($item['group'])) { + $sql .= sprintf('(%s)', $this->parseGroup($item['group'])); + } + } + + return $sql; + } + + /** + * @param $field + * @param $value + * @param string $type + * + * @throws \Exception + */ + private function addWhere($field, $value, $type = self::TYPE_AND) + { + if (!in_array($type, self::VALID_TYPES)) { + throw new \Exception(sprintf('Invalid type %s', $type)); + } + + $this->wheres[] = [ + 'field' => $field, + 'value' => $value, + 'type' => $type, + ]; + } + + /** + * @param Builder $query + * @param string $type + * + * @throws \Exception + */ + private function nestedWhere(Builder $query, $type = self::TYPE_AND) + { + if (!in_array($type, self::VALID_TYPES)) { + throw new \Exception(sprintf('Invalid type %s', $type)); + } + + if (count($query->wheres)) { + $this->wheres[] = [ + 'type' => $type, + 'group' => $query->wheres, + ]; + } + } +} \ No newline at end of file diff --git a/src/Engines/ElasticSearchEngine.php b/src/Engines/ElasticSearchEngine.php index ce5a443..36dcc8c 100644 --- a/src/Engines/ElasticSearchEngine.php +++ b/src/Engines/ElasticSearchEngine.php @@ -213,7 +213,7 @@ protected function performSearch(Builder $builder, array $options = []) ] ] ]; - + if (method_exists($builder->model, 'getFields') && is_array($builder->model->getFields())) { $params['body']['query']['bool']['must'][0]['query_string']['fields'] = $builder->model->getFields(); } @@ -254,6 +254,10 @@ protected function performSearch(Builder $builder, array $options = []) */ protected function filters(Builder $builder) { + if ($builder instanceof \ScoutEngines\Elasticsearch\Builder) { + return ($builder->toESDL()); + } + return collect($builder->wheres)->map( function ($value, $key) { if (is_array($value)) { diff --git a/src/Traits/Searchable.php b/src/Traits/Searchable.php index 6de1767..726ea7b 100644 --- a/src/Traits/Searchable.php +++ b/src/Traits/Searchable.php @@ -10,11 +10,25 @@ use Laravel\Scout\Searchable as SearchableTrait; +use ScoutEngines\Elasticsearch\Builder; trait Searchable { use SearchableTrait; + /** + * @param string $query + * @param Closure $callback + * + * @return Builder + */ + public static function search($query, $callback = null) + { + return new Builder( + new static, $query, $callback, config('scout.soft_delete', false) + ); + } + /** * @return array */ @@ -35,7 +49,7 @@ public function getFields() { return $this->searchable; } - + public function setFields(array $fields) { $this->searchable = $fields;