Skip to content

Commit 15b43ca

Browse files
committed
Add GeoSearch Index and search
1 parent 3e95445 commit 15b43ca

File tree

5 files changed

+138
-11
lines changed

5 files changed

+138
-11
lines changed

README.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,16 @@ In your `config/scout.php` add:
7171
],
7272
'asYouType' => false,
7373
'searchBoolean' => env('TNTSEARCH_BOOLEAN', false),
74+
'geoIndex' => env('TNTSEARCH_GEOINDEX', false),
7475
],
7576
```
7677
To prevent your search indexes being commited to your project repository,
7778
add the following line to your `.gitignore` file.
7879

79-
```/storage/*.index```
80+
```
81+
/storage/*.index
82+
/storage/*.geoindex
83+
```
8084

8185
The `asYouType` option can be set per model basis, see the example below.
8286

src/Console/ImportCommand.php

+24-6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Illuminate\Console\Command;
66
use Illuminate\Contracts\Events\Dispatcher;
7+
use TeamTNT\TNTSearch\TNTGeoSearch;
78
use TeamTNT\TNTSearch\TNTSearch;
89
use Illuminate\Support\Facades\Schema;
910

@@ -40,12 +41,6 @@ public function handle(Dispatcher $events)
4041
$config = config('scout.tntsearch') + config("database.connections.$driver");
4142
$db = app('db')->connection($driver);
4243

43-
$tnt->loadConfig($config);
44-
$tnt->setDatabaseHandle($db->getPdo());
45-
46-
$indexer = $tnt->createIndex($model->searchableAs().'.index');
47-
$indexer->setPrimaryKey($model->getKeyName());
48-
4944
$availableColumns = Schema::connection($driver)->getColumnListing($model->getTable());
5045
$desiredColumns = array_keys($model->toSearchableArray());
5146

@@ -58,9 +53,32 @@ public function handle(Dispatcher $events)
5853
->addSelect($fields);
5954
}
6055

56+
$tnt->loadConfig($config);
57+
$tnt->setDatabaseHandle($db->getPdo());
58+
59+
$indexer = $tnt->createIndex($model->searchableAs().'.index');
60+
$indexer->setPrimaryKey($model->getKeyName());
61+
6162
$indexer->query($query->toSql());
6263

6364
$indexer->run();
65+
66+
if (!empty($config['geoIndex'])) {
67+
$geotnt = new TNTGeoSearch();
68+
69+
$geotnt->loadConfig($config);
70+
$geotnt->setDatabaseHandle($db->getPdo());
71+
72+
$geoIndexer = $geotnt->getIndex();
73+
$geoIndexer->loadConfig($geotnt->config);
74+
$geoIndexer->createIndex($model->searchableAs().'.geoindex');
75+
$geoIndexer->setPrimaryKey($model->getKeyName());
76+
77+
$geoIndexer->query($query->toSql());
78+
79+
$geoIndexer->run();
80+
}
81+
6482
$this->info('All ['.$class.'] records have been imported.');
6583
}
6684
}

src/Engines/TNTSearchEngine.php

+69-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Laravel\Scout\Builder;
99
use Laravel\Scout\Engines\Engine;
1010
use TeamTNT\TNTSearch\Exceptions\IndexNotFoundException;
11+
use TeamTNT\TNTSearch\TNTGeoSearch;
1112
use TeamTNT\TNTSearch\TNTSearch;
1213

1314
class TNTSearchEngine extends Engine
@@ -17,6 +18,11 @@ class TNTSearchEngine extends Engine
1718
*/
1819
protected $tnt;
1920

21+
/**
22+
* @var TNTGeoSearch
23+
*/
24+
protected $geotnt;
25+
2026
/**
2127
* @var Builder
2228
*/
@@ -26,10 +32,12 @@ class TNTSearchEngine extends Engine
2632
* Create a new engine instance.
2733
*
2834
* @param TNTSearch $tnt
35+
* @param TNTGeoSearch|null $geotnt
2936
*/
30-
public function __construct(TNTSearch $tnt)
37+
public function __construct(TNTSearch $tnt, TNTGeoSearch $geotnt = null)
3138
{
3239
$this->tnt = $tnt;
40+
$this->geotnt = $geotnt;
3341
}
3442

3543
/**
@@ -46,8 +54,17 @@ public function update($models)
4654
$index = $this->tnt->getIndex();
4755
$index->setPrimaryKey($models->first()->getKeyName());
4856

57+
$geoindex = null;
58+
if ($this->geotnt) {
59+
$this->geotnt->selectIndex("{$models->first()->searchableAs()}.geoindex");
60+
$geoindex = $this->geotnt->getIndex();
61+
$geoindex->loadConfig($this->geotnt->config);
62+
$geoindex->setPrimaryKey($models->first()->getKeyName());
63+
$geoindex->indexBeginTransaction();
64+
}
65+
4966
$index->indexBeginTransaction();
50-
$models->each(function ($model) use ($index) {
67+
$models->each(function ($model) use ($index, $geoindex) {
5168
$array = $model->toSearchableArray();
5269

5370
if (empty($array)) {
@@ -56,11 +73,25 @@ public function update($models)
5673

5774
if ($model->getKey()) {
5875
$index->update($model->getKey(), $array);
76+
if ($geoindex) {
77+
$geoindex->prepareAndExecuteStatement(
78+
'DELETE FROM locations WHERE doc_id = :documentId;',
79+
[['key' => ':documentId', 'value' => $model->getKey()]]
80+
);
81+
}
5982
} else {
6083
$index->insert($array);
6184
}
85+
if ($geoindex && !empty($array['longitude']) && !empty($array['latitude'])) {
86+
$array['longitude'] = (float) $array['longitude'];
87+
$array['latitude'] = (float) $array['latitude'];
88+
$geoindex->insert($array);
89+
}
6290
});
6391
$index->indexEndTransaction();
92+
if ($this->geotnt) {
93+
$geoindex->indexEndTransaction();
94+
}
6495
}
6596

6697
/**
@@ -78,6 +109,17 @@ public function delete($models)
78109
$index = $this->tnt->getIndex();
79110
$index->setPrimaryKey($model->getKeyName());
80111
$index->delete($model->getKey());
112+
113+
if ($this->geotnt) {
114+
$this->geotnt->selectIndex("{$model->searchableAs()}.geoindex");
115+
$index = $this->geotnt->getIndex();
116+
$index->loadConfig($this->geotnt->config);
117+
$index->setPrimaryKey($model->getKeyName());
118+
$index->prepareAndExecuteStatement(
119+
'DELETE FROM locations WHERE doc_id = :documentId;',
120+
[['key' => ':documentId', 'value' => $model->getKey()]]
121+
);
122+
}
81123
});
82124
}
83125

@@ -145,6 +187,9 @@ protected function performSearch(Builder $builder, array $options = [])
145187
$index = $builder->index ?: $builder->model->searchableAs();
146188
$limit = $builder->limit ?: 10000;
147189
$this->tnt->selectIndex("{$index}.index");
190+
if ($this->geotnt) {
191+
$this->geotnt->selectIndex("{$index}.geoindex");
192+
}
148193

149194
$this->builder = $builder;
150195

@@ -160,6 +205,14 @@ protected function performSearch(Builder $builder, array $options = [])
160205
$options
161206
);
162207
}
208+
209+
if (is_array($builder->query)) {
210+
$location = $builder->query['location'];
211+
$distance = $builder->query['distance'];
212+
$limit = array_key_exists('limit', $builder->query) ? $builder->query['limit'] : 10;
213+
return $this->geotnt->findNearest($location, $distance, $limit);
214+
}
215+
163216
if (isset($this->tnt->config['searchBoolean']) ? $this->tnt->config['searchBoolean'] : false) {
164217
return $this->tnt->searchBoolean($builder->query, $limit);
165218
} else {
@@ -260,6 +313,13 @@ public function initIndex($model)
260313
$indexer->setDatabaseHandle($model->getConnection()->getPdo());
261314
$indexer->setPrimaryKey($model->getKeyName());
262315
}
316+
if ($this->geotnt && !file_exists($this->tnt->config['storage']."/{$indexName}.geoindex")) {
317+
$indexer = $this->geotnt->getIndex();
318+
$indexer->loadConfig($this->geotnt->config);
319+
$indexer->createIndex("$indexName.geoindex");
320+
$indexer->setDatabaseHandle($model->getConnection()->getPdo());
321+
$indexer->setPrimaryKey($model->getKeyName());
322+
}
263323
}
264324

265325
/**
@@ -401,5 +461,12 @@ public function flush($model)
401461
if (file_exists($pathToIndex)) {
402462
unlink($pathToIndex);
403463
}
464+
465+
if ($this->geotnt){
466+
$pathToGeoIndex = $this->geotnt->config['storage']."/{$indexName}.geoindex";
467+
if (file_exists($pathToGeoIndex)) {
468+
unlink($pathToGeoIndex);
469+
}
470+
}
404471
}
405472
}

src/TNTSearchScoutServiceProvider.php

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?php namespace TeamTNT\Scout;
22

3+
use TeamTNT\TNTSearch\TNTGeoSearch;
34
use TeamTNT\TNTSearch\TNTSearch;
45
use Laravel\Scout\EngineManager;
56
use Laravel\Scout\Builder;
@@ -28,7 +29,15 @@ public function boot()
2829
$this->setFuzziness($tnt);
2930
$this->setAsYouType($tnt);
3031

31-
return new TNTSearchEngine($tnt);
32+
$geotnt = null;
33+
if (!empty($config['geoIndex'])) {
34+
$geotnt = new TNTGeoSearch();
35+
36+
$geotnt->loadConfig($config);
37+
$geotnt->setDatabaseHandle(app('db')->connection()->getPdo());
38+
}
39+
40+
return new TNTSearchEngine($tnt, $geotnt);
3241
});
3342

3443
if ($this->app->runningInConsole()) {

tests/TNTSearchEngineTest.php

+30-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,36 @@ public function test_update_adds_objects_to_index()
2929
$index->shouldReceive('update');
3030
$index->shouldReceive('indexEndTransaction');
3131

32-
$engine = new TNTSearchEngine($client);
32+
$geoClient = Mockery::mock('TeamTNT\TNTSearch\TNTGeoSearch');
33+
34+
$config = [
35+
'storage' => '/',
36+
'fuzziness' => false,
37+
'fuzzy' => [
38+
'prefix_length' => 2,
39+
'max_expansions' => 50,
40+
'distance' => 2
41+
],
42+
'asYouType' => false,
43+
'searchBoolean' => false,
44+
];
45+
46+
$geoClient->shouldReceive('getIndex')
47+
->andReturn($geoIndex = Mockery::mock('TeamTNT\TNTSearch\Indexer\TNTGeoIndexer'))
48+
->andSet('config', $config);
49+
$geoIndex->shouldReceive('loadConfig');
50+
$geoIndex->shouldReceive('createIndex')
51+
->with('table.geoindex');
52+
$geoIndex->shouldReceive('setDatabaseHandle');
53+
$geoIndex->shouldReceive('setPrimaryKey');
54+
$geoClient->shouldReceive('selectIndex');
55+
$geoIndex->shouldReceive('indexBeginTransaction');
56+
$geoIndex->shouldReceive('prepareAndExecuteStatement');
57+
$geoIndex->shouldReceive('insert');
58+
$geoIndex->shouldReceive('indexEndTransaction');
59+
60+
61+
$engine = new TNTSearchEngine($client, $geoClient);
3362
$engine->update(Collection::make([new TNTSearchEngineTestModel()]));
3463
}
3564
}

0 commit comments

Comments
 (0)