Skip to content
This repository was archived by the owner on Jan 9, 2024. It is now read-only.

Commit 0334c43

Browse files
committed
Commit for github.
1 parent bc9bbc5 commit 0334c43

17 files changed

+1109
-0
lines changed

README.md

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
Laravel Scout MySQL Driver
2+
==========================
3+
4+
Search Eloquent Models using MySQL `FULLTEXT` Indexes or `WHERE LIKE '%:search%`' statements.
5+
6+
1. [Installation](#installation)
7+
2. [Usage](#usage)
8+
3. [Modes](#modes)
9+
4. [Console Command](#console-command)
10+
5. [Configuration](#configuration)
11+
12+
13+
Installation <div id="installation"></div>
14+
------------
15+
16+
**Note: Any Models you plan to search using this driver must use a MySQL MyISAM or InnoDB table.**
17+
18+
If you haven't already you should [install Laravel Scout](https://laravel.com/docs/5.3/scout#installation) to
19+
your project and apply the `Laravel\Scout\Searchable` trait to any Eloquent models you would like to make searchable.
20+
21+
Install this package via **Composer**
22+
23+
`composer require damiantw/laravel-scout-mysql-driver`
24+
25+
26+
Next add the ServiceProvider to the Package Service Providers in `config/app.php`
27+
28+
```php
29+
/*
30+
* Package Service Providers...
31+
*/
32+
DamianTW\MySQLScout\Providers\MySQLScoutServiceProvider::class,
33+
```
34+
35+
Append the default configuration to `config/scout.php`
36+
37+
```php
38+
39+
'mysql' => [
40+
'mode' => 'NATURAL_LANGUAGE',
41+
'model_directories' => [app_path()],
42+
'min_search_length' => 0,
43+
'min_fulltext_search_length' => 4,
44+
'min_fulltext_search_fallback' => 'LIKE',
45+
'query_expansion' => false
46+
]
47+
48+
```
49+
50+
Set `SCOUT_DRIVER=mysql` in your `.env` file
51+
52+
Please note this Laravel Scout driver does not need to update any indexes when a Model is changed as this is handled
53+
natively by MySQL. Therefore you can safely disable queuing in `config/scout.php`.
54+
55+
```php
56+
/*
57+
|--------------------------------------------------------------------------
58+
| Queue Data Syncing
59+
|--------------------------------------------------------------------------
60+
|
61+
| This option allows you to control if the operations that sync your data
62+
| with your search engines are queued. When this is set to "true" then
63+
| all automatic data syncing will get queued for better performance.
64+
|
65+
*/
66+
67+
'queue' => false,
68+
```
69+
70+
In addition there is no need to use the `php artisan scout:import` command. However, if you plan to use this driver in
71+
either `NATURAL_LANGUAGE` or `BOOLEAN` mode you should first run the provided [console command](#console-command) to
72+
create the needed `FULLTEXT` indexes.
73+
74+
Usage <div id="usage"></div>
75+
-----
76+
77+
Simply call the `search()` method on your `Searchable` models:
78+
79+
`$beers = App\Drink::search('beer')->get();`
80+
81+
For more usage information see the [Laravel Scout Documentation](https://laravel.com/docs/5.3/scout).
82+
83+
Modes <div id="modes"></div>
84+
-----
85+
86+
This driver can perform different types of search queries depending on the mode set in the `scout.mysql.mode`
87+
Laravel configuration value. Currently 4 different modes are supported `NATURAL_LANGUAGE`,`BOOLEAN`,`LIKE` and `LIKE_EXPANDED`.
88+
89+
90+
### NATURAL_LANGUAGE and BOOLEAN Modes
91+
92+
In `NATURAL_LANGUAGE` and `BOOLEAN` mode the driver will run MySQL `WHERE MATCH() AGAINST()` queries in the
93+
respective modes.
94+
95+
Both modes search queries will include all of Model's `FULLTEXT` compatible fields (`CHAR`,`VARCHAR`,`TEXT`)
96+
returned from the Model's `toSearchableArray()` method. It is required to have a `FULLTEXT` index for these fields.
97+
You can create this index using the provided [console command](#console-command).
98+
99+
For example running a search on a `POST` model with the following database structure:
100+
101+
| column name | type |
102+
|-------------|------------------|
103+
| id | int(10) UN AI PK |
104+
| content | VARCHAR(255) |
105+
| meta | TEXT |
106+
107+
108+
would produce the following query in `NATURAL_LANGUAGE` mode:
109+
110+
111+
```sql
112+
select * from `posts` where MATCH(content,meta) AGAINST(:_search IN NATURAL LANGUAGE MODE)
113+
```
114+
115+
and the following query in `BOOLEAN` mode:
116+
117+
```sql
118+
select * from `posts` where MATCH(content,meta) AGAINST(:_search IN BOOLEAN MODE)
119+
```
120+
121+
Operators for `BOOLEAN` mode should be passed as part of the search string.
122+
123+
124+
For more information see the
125+
126+
[MySQL's Full-Text Search Functions documentation](http://dev.mysql.com/doc/refman/5.7/en/fulltext-search.html).
127+
128+
### LIKE and LIKE_EXPANDED Modes
129+
130+
`LIKE` and `LIKE_EXPANDED` modes will run `WHERE LIKE %:_search%` queries that will include all of the Model's fields
131+
returned from `toSearchableArray()`. `LIKE_EXPANDED` mode will query each field using each individual word in the search string.
132+
133+
For example running a search on a `Customer` model with the following database structure:
134+
135+
| column name | type |
136+
|-------------|------------------|
137+
| id | int(10) UN AI PK |
138+
| first_name | VARCHAR(255) |
139+
| last_name | VARCHAR(255) |
140+
141+
would produce the following query in `LIKE` mode given the search string "John":
142+
143+
```sql
144+
SELECT * FROM `customers` WHERE (`id` LIKE '%John%' OR `first_name` LIKE '%John%' OR `last_name` LIKE '%JOHN%')
145+
```
146+
147+
and the following query in `LIKE_EXPANDED` mode given the search string "John Smith":
148+
149+
```sql
150+
SELECT * FROM `customers` WHERE (`id` LIKE '%John%' OR `id` LIKE '%Smith%' OR `first_name` LIKE '%John%' OR `first_name` LIKE '%Smith%' OR `last_name` LIKE '%John%' OR `last_name` LIKE '%Smith%')
151+
```
152+
153+
Console Command <div id="console-command"></div>
154+
---------------
155+
156+
The command `php artisan scout:mysql-index {model?}` is included to manage the `FULLTEXT` indexes needed for
157+
`NATURAL_LANGUAGE`and `BOOLEAN` modes.
158+
159+
If the model parameter is omitted the command will run with all Model's with the `Laravel\Scout\Searchable` trait
160+
and a MySQL connection within the directories defined in the `scout.mysql.model_directories` Laravel configuration value.
161+
162+
### Creating Indexes
163+
164+
Pass the command a Model to create a `FULLTEXT` index for all of the Model's `FULLTEXT` compatible fields
165+
(`CHAR`,`VARCHAR`,`TEXT`) returned from the Model's `toSearchableArray()` method. The index name will be the result of
166+
the Mode's `searchableAs()` method.
167+
168+
If an index already exists for the Model and the Model contains new searchable fields not in the existing index the
169+
index will be dropped and recreated.
170+
171+
`php artisan scout:mysql-index App\\Post`
172+
173+
### Dropping index
174+
175+
Pass the `-D` or `--drop` options to drop an existing index for a Model.
176+
177+
`php artisan scout:mysql-index App\\Post --drop`
178+
179+
Configuration <div id="configuration"></div>
180+
-------------
181+
182+
Behavior can be changed by modifying the `scout.mysql` Laravel configuration values.
183+
184+
* `scout.mysql.mode` - The [mode](#mode) used to determine how the driver runs search queries. Acceptable values are
185+
`NATURAL_LANGUAGE`,`BOOLEAN`,`LIKE` and `LIKE_EXPANDED`.
186+
187+
* `scout.mysql.model_directories` - If no model parameter is provided to the included `php artisian scout:mysql-index`
188+
command the directories defined here will be searched for Model's with the `Laravel\Scout\Searchable` trait
189+
and a MySQL connection.
190+
191+
* `scout.mysql.min_search_length` - If the length of a search string is smaller then this value no search queries will
192+
run and an empty Collection will be returned.
193+
194+
* `scout.mysql.min_fulltext_search_length` - If using `NATURAL_LANGUAGE` or `BOOLEAN` modes and a search string's length
195+
is less than this value the driver will revert to a fallback mode. By default MySQL requires a search string length of at
196+
least 4 to to run `FULL TEXT` queries. For information on changing this see the
197+
[MySQL's Fine-Tuning MySQL Full-Text Search documentation](http://dev.mysql.com/doc/refman/5.7/en/fulltext-fine-tuning.html).
198+
199+
* `scout.mysql.min_fulltext_search_fallback` - The mode that will be used as a fallback when the search string's length
200+
is less than `scout.mysql.min_fulltext_search_length` in `NATURAL_LANGUAGE` or `BOOLEAN` modes. Acceptable values are
201+
`LIKE` and `LIKE_EXPANDED`.
202+
203+
* `scout.mysql.query_expansion` - If set to true MySQL query expansion will be used in search queries. Only applies if
204+
using `NATURAL_LANGUAGE` mode. For more information see
205+
[MySQL's Full-Text Searches with Query Expansion documentation](http://dev.mysql.com/doc/refman/5.7/en/fulltext-query-expansion.html).

composer.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "damiantw/laravel-scout-mysql-driver",
3+
"description": "MySQL driver for Laravel Scout.",
4+
"license": "MIT",
5+
"authors": [
6+
{
7+
"name": "Damian Crisafulli",
8+
"email": "damian.crisafulli@troyweb.com"
9+
}
10+
],
11+
"minimum-stability": "dev",
12+
"require": {
13+
"php": ">=5.6.4",
14+
"laravel/framework": "5.3.*",
15+
"laravel/scout": "^1.1"
16+
},
17+
"autoload": {
18+
"psr-4": {
19+
"DamianTW\\MySQLScout\\": "src/"
20+
}
21+
}
22+
}

config/config.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
'mysql' => [
2+
'mode' => 'NATURAL_LANGUAGE',
3+
'model_directories' => [app_path()],
4+
'min_search_length' => 0,
5+
'min_fulltext_search_length' => 4,
6+
'min_fulltext_search_fallback' => 'LIKE',
7+
'query_expansion' => false
8+
]

src/Commands/ManageIndexes.php

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
namespace DamianTW\MySQLScout\Commands;
4+
5+
use Illuminate\Console\Command;
6+
use DamianTW\MySQLScout\Services\IndexService;
7+
use Illuminate\Contracts\Events\Dispatcher;
8+
use DamianTW\MySQLScout\Events;
9+
10+
class ManageIndexes extends Command
11+
{
12+
/**
13+
* The name and signature of the console command.
14+
*
15+
* @var string
16+
*/
17+
protected $signature = 'scout:mysql-index {model?} {--D|drop}';
18+
19+
/**
20+
* The console command description.
21+
*
22+
* @var string
23+
*/
24+
protected $description = 'Create MySQL FULLTEXT indexes for searchable models';
25+
26+
protected $indexService;
27+
28+
/**
29+
* Create a new command instance.
30+
*
31+
* @param IndexService $indexService
32+
*/
33+
public function __construct(IndexService $indexService)
34+
{
35+
parent::__construct();
36+
$this->indexService = $indexService;
37+
}
38+
39+
/**
40+
* Execute the console command.
41+
*
42+
* @param Dispatcher $events
43+
* @return mixed
44+
*/
45+
public function handle(Dispatcher $events)
46+
{
47+
48+
$events->listen(Events\ModelIndexCreated::class, function ($event) {
49+
$this->comment("Index '$event->indexName' created with fields: $event->indexFields");
50+
});
51+
52+
$events->listen(Events\ModelIndexUpdated::class, function ($event) {
53+
$this->comment("Index '$event->indexName' updated");
54+
});
55+
56+
$events->listen(Events\ModelIndexDropped::class, function ($event) {
57+
$this->comment("Index '$event->indexName' dropped");
58+
});
59+
60+
$events->listen(Events\ModelIndexIgnored::class, function ($event) {
61+
$this->comment("Existing Index '$event->indexName' ignored");
62+
});
63+
64+
$model = $this->argument('model');
65+
$drop = $this->option('drop');
66+
67+
if(! $model) {
68+
$modelDirectories = config('scout.mysql.model_directories');
69+
$searchableModels = $this->indexService->getAllSearchableModels($modelDirectories);
70+
71+
foreach ($searchableModels as $searchableModel) {
72+
$drop ? $this->dropModelIndex($searchableModel) : $this->createOrUpdateModelIndex($searchableModel);
73+
}
74+
75+
}
76+
else {
77+
$drop ? $this->dropModelIndex($model) : $this->createOrUpdateModelIndex($model);
78+
}
79+
80+
}
81+
82+
private function createOrUpdateModelIndex($searchableModel)
83+
{
84+
$this->info("Creating index for $searchableModel...");
85+
$this->indexService->setModel($searchableModel);
86+
$this->indexService->createOrUpdateIndex();
87+
}
88+
89+
private function dropModelIndex($searchableModel)
90+
{
91+
$this->info("Dropping index for $searchableModel...");
92+
$this->indexService->setModel($searchableModel);
93+
$this->indexService->dropIndex();
94+
}
95+
96+
}

src/Engines/Modes/Boolean.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace DamianTW\MySQLScout\Engines\Modes;
4+
5+
use Laravel\Scout\Builder;
6+
use DamianTW\MySQLScout\Services\ModelService;
7+
8+
class Boolean extends Mode
9+
{
10+
protected $modelService;
11+
12+
function __construct(Builder $builder)
13+
{
14+
parent::__construct($builder);
15+
16+
$this->modelService = resolve(ModelService::class);
17+
$this->modelService->setModel($this->builder->model);
18+
}
19+
20+
public function buildWhereRawString()
21+
{
22+
$queryString = '';
23+
24+
foreach ($this->builder->wheres as $field => $value) {
25+
$queryString .= "$field = :$field AND ";
26+
}
27+
28+
$indexFields = implode(',', $this->modelService->getFullTextIndexFields());
29+
30+
$queryString .= "MATCH($indexFields) AGAINST(:_search IN BOOLEAN MODE)";
31+
32+
return $queryString;
33+
}
34+
35+
public function buildParams()
36+
{
37+
$params = [];
38+
39+
foreach ($this->builder->wheres as $field => $value) {
40+
$params[$field] = $value;
41+
}
42+
43+
$params['_search'] = $this->builder->query;
44+
return $params;
45+
}
46+
47+
public function isFullText()
48+
{
49+
return true;
50+
}
51+
}

0 commit comments

Comments
 (0)