Laravel 5 awesomeness
This commit is contained in:
53
README.md
53
README.md
@ -2,29 +2,29 @@ Laravel Searchy 2
|
||||
========================================
|
||||
### Database Searching Made Easy
|
||||
|
||||
Searchy is an easy-to-use Laravel Optimized package that makes running user driven searches on data in your models simple and effective.
|
||||
Searchy is an; easy-to-use, light-weight, MySQL only, Laravel package that makes running user driven searches on data in your models simple and effective.
|
||||
It uses pseudo fuzzy searching and other weighted mechanics depending on the search driver that you have enabled.
|
||||
It requires no other software installed on your server (so can be a little slower than dedicated search programs) but can be set up and ready to go in minutes.
|
||||
|
||||
Installation
|
||||
----------------------------------------
|
||||
Add `"tom-lingham/searchy" : "2.0~"` to your composer.json file under `require`:
|
||||
Add `"tom-lingham/searchy" : "2.0"` to your composer.json file under `require`:
|
||||
```
|
||||
"require": {
|
||||
"laravel/framework": "5.*",
|
||||
"tom-lingham/searchy" : "dev-master"
|
||||
"tom-lingham/searchy" : "2.0"
|
||||
}
|
||||
```
|
||||
Run `composer update` in your terminal to pull down the package into your vendors folder.
|
||||
|
||||
Add the service provider to the `providers` array in Laravel's app/config/app.php file:
|
||||
```php
|
||||
'TomLingham\Searchy\SearchyServiceProvider'
|
||||
TomLingham\Searchy\SearchyServiceProvider::class
|
||||
```
|
||||
|
||||
Add the Alias to the `aliases` array in Laravel's app/config/app.php file if you want to have quick access to it in your application:
|
||||
```php
|
||||
'Searchy' => 'TomLingham\Searchy\Facades\Searchy'
|
||||
'Searchy' => TomLingham\Searchy\Facades\Searchy::class
|
||||
```
|
||||
|
||||
|
||||
@ -34,19 +34,21 @@ To use Searchy, you can take advantage of magic methods.
|
||||
|
||||
If you are searching the name and email column/field of users in a `users` table you would, for example run:
|
||||
```php
|
||||
$users = Searchy::users('name', 'email')->query('John Smith');
|
||||
$users = Searchy::users('name', 'email')->query('John Smith')->get();
|
||||
```
|
||||
you can also write this as:
|
||||
|
||||
```php
|
||||
$users = Searchy::search('users')->fields('name', 'email')->query('John Smith');
|
||||
$users = Searchy::search('users')->fields('name', 'email')->query('John Smith')->get();
|
||||
```
|
||||
In this case, pass the columns you want to search through to the `fields()` method.
|
||||
|
||||
These examples both return a Laravel DB Query Builder Object, so you will need to chain `get()` to actually return the results:
|
||||
These examples both return an array of Objects containing your search results. You can use `getQuery()` instead of
|
||||
`get()` to return an instance of the Database Query Object in case you want to do further manipulation to the results:
|
||||
|
||||
```php
|
||||
$users = Searchy::search('users')->fields('name', 'email')->query('John Smith')->get();
|
||||
$users = Searchy::search('users')->fields('name', 'email')->query('John Smith')
|
||||
->getQuery()->having('relevance', '>', 20)->get();
|
||||
```
|
||||
|
||||
#### Searching multiple Columns
|
||||
@ -54,15 +56,25 @@ You can also add multiple arguments to the list of fields/columns to search by.
|
||||
|
||||
For example, if you want to search the name, email address and username of a user, you might run:
|
||||
```php
|
||||
$users = Searchy::users('name', 'email', 'username')->query('John Smith');
|
||||
$users = Searchy::users('name', 'email', 'username')->query('John Smith')->get();
|
||||
```
|
||||
|
||||
#### Searching Joined/Concatenated Columns
|
||||
Sometimes you may want to leverage searches on concatenated column. For example, on a `first_name` and `last_name` field but you only want to run the one query. To do this can separate columns with a double colon:
|
||||
```php
|
||||
$users = Searchy::users('first_name::last_name')->query('John Smith');
|
||||
$users = Searchy::users('first_name::last_name')->query('John Smith')->get();
|
||||
```
|
||||
|
||||
#### Return only specific columns
|
||||
You can specify which columns to return in your search:
|
||||
```php
|
||||
$users = Searchy::users('first_name::last_name')->query('John Smith')->select('first_name')->get();
|
||||
|
||||
// Or you can swap those around...
|
||||
$users = Searchy::users('first_name::last_name')->select('first_name')->query('John Smith')->get();
|
||||
```
|
||||
This will, however, also return the `relevance` aliased column regardless of what is entered here.
|
||||
|
||||
Configuration
|
||||
----------------------------------------
|
||||
You can publish the configuration file to your `app` directory and override the settings by running `php artisan config:publish tom-lingham/searchy`
|
||||
@ -72,7 +84,7 @@ You can set the default driver to use for searches in the configuration file. Yo
|
||||
You can also override these methods using the following syntax when running a search:
|
||||
|
||||
```php
|
||||
Searchy::driver('fuzzy')->users('name')->query('Bat Man')->get();
|
||||
Searchy::driver('fuzzy')->users('name')->query('Batman')->get();
|
||||
```
|
||||
|
||||
|
||||
@ -88,13 +100,11 @@ Currently there are only three drivers: Simple, Fuzzy and Levenshtein (Experimen
|
||||
The Simple search driver only uses 3 matchers each with the relevant multipliers that best suited my testing environments.
|
||||
|
||||
```php
|
||||
|
||||
protected $matchers = [
|
||||
'TomLingham\Searchy\Matchers\ExactMatcher' => 100,
|
||||
'TomLingham\Searchy\Matchers\StartOfStringMatcher' => 50,
|
||||
'TomLingham\Searchy\Matchers\InStringMatcher' => 30,
|
||||
];
|
||||
|
||||
```
|
||||
|
||||
|
||||
@ -102,7 +112,6 @@ protected $matchers = [
|
||||
The Fuzzy Search Driver is simply another group of matchers setup as follows. The multipliers are what I have used, but feel free to change these or roll your own driver with the same matchers and change the multipliers to suit.
|
||||
|
||||
```php
|
||||
|
||||
protected $matchers = [
|
||||
'TomLingham\Searchy\Matchers\ExactMatcher' => 100,
|
||||
'TomLingham\Searchy\Matchers\StartOfStringMatcher' => 50,
|
||||
@ -113,18 +122,15 @@ protected $matchers = [
|
||||
'TomLingham\Searchy\Matchers\InStringMatcher' => 30,
|
||||
'TomLingham\Searchy\Matchers\TimesInStringMatcher' => 8,
|
||||
];
|
||||
|
||||
```
|
||||
|
||||
#### Levenshtein Search Driver (Experimental)
|
||||
The Levenshtein Search Driver uses the Levenshetein Distance to calculate the 'distance' between strings. It requires that you have a stored procedure in MySQL similar to the following `levenshtein( string1, string2 )`. There is an SQL file with a suitable function in the `res` folder - feel free to use this one.
|
||||
|
||||
```php
|
||||
|
||||
protected $matchers = [
|
||||
'TomLingham\Searchy\Matchers\LevenshteinMatcher' => 100
|
||||
];
|
||||
|
||||
```
|
||||
|
||||
Matchers
|
||||
@ -132,7 +138,6 @@ Matchers
|
||||
|
||||
#### ExactMatcher
|
||||
Matches an exact string and applies a high multiplier to bring any exact matches to the top.
|
||||
When sanitize is on, if the expression strips some of the characters from the search query then this may not be able to match against a string despite entering in an exact match.
|
||||
|
||||
|
||||
#### StartOfStringMatcher
|
||||
@ -187,13 +192,3 @@ Contributing & Reporting Bugs
|
||||
----------------------------------------
|
||||
If you would like to improve on the code that is here, feel free to submit a pull request.
|
||||
If you find any bugs, submit them here and I will respond as soon as possible. Please make sure to include as much information as possible.
|
||||
|
||||
|
||||
Road Map
|
||||
----------------------------------------
|
||||
To the future! The intention is to (eventually):
|
||||
|
||||
1. Remove Searchy's dependancy on Laravel
|
||||
2. Include more drivers for more advanced searching (Including file system searching, indexing and more)
|
||||
3. Implement an AJAX friendly interface for searching models and implementing auto-suggestion features on the front end
|
||||
4. Speed up search performance and improve result relevance
|
||||
|
@ -2,12 +2,11 @@
|
||||
|
||||
interface SearchDriverInterface
|
||||
{
|
||||
/**
|
||||
* Execute the query on the Driver
|
||||
*
|
||||
* @param $searchString
|
||||
* @return mixed
|
||||
*/
|
||||
|
||||
public function query( $searchString );
|
||||
|
||||
public function select( /* $columns */ );
|
||||
|
||||
public function get();
|
||||
|
||||
}
|
@ -22,6 +22,7 @@ class AcronymMatcher extends BaseMatcher
|
||||
*/
|
||||
public function formatSearchString( $searchString )
|
||||
{
|
||||
$searchString = preg_replace('/[^0-9a-zA-Z]/', '', $searchString);
|
||||
return implode( '% ', str_split(strtoupper( $searchString ))) . '%';
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ class ConsecutiveCharactersMatcher extends BaseMatcher
|
||||
*/
|
||||
public function formatSearchString( $searchString )
|
||||
{
|
||||
$searchString = preg_replace('/[^0-9a-zA-Z]/', '', $searchString);
|
||||
return '%'.implode('%', str_split( $searchString )).'%';
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ class StudlyCaseMatcher extends BaseMatcher
|
||||
*/
|
||||
public function formatSearchString( $searchString )
|
||||
{
|
||||
|
||||
$searchString = preg_replace('/[^0-9a-zA-Z]/', '', $searchString);
|
||||
return implode( '%', str_split(strtoupper( $searchString ))) . '%';
|
||||
}
|
||||
|
||||
|
@ -25,21 +25,28 @@ class SearchBuilder {
|
||||
*/
|
||||
private $driverName;
|
||||
|
||||
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
private $config;
|
||||
|
||||
|
||||
public function __construct( Repository $config )
|
||||
{
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $table
|
||||
* @param $searchable
|
||||
* @return $this
|
||||
*/
|
||||
public function search( $table )
|
||||
public function search( $searchable )
|
||||
{
|
||||
$this->table = $table;
|
||||
if (is_object( $searchable ) && method_exists($searchable, 'getTable')) {
|
||||
$this->table = $searchable->getTable();
|
||||
} else {
|
||||
$this->table = $searchable;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -49,13 +56,9 @@ class SearchBuilder {
|
||||
*/
|
||||
public function fields( /* $fields */ )
|
||||
{
|
||||
|
||||
$searchFields = func_get_args();
|
||||
|
||||
$this->searchFields = $searchFields;
|
||||
$this->searchFields = func_get_args();
|
||||
|
||||
return $this->makeDriver();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,9 +79,7 @@ class SearchBuilder {
|
||||
*/
|
||||
public function __call( $table, $searchFields )
|
||||
{
|
||||
|
||||
return call_user_func_array([$this->search( $table ), 'fields'], $searchFields);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -86,6 +87,7 @@ class SearchBuilder {
|
||||
*/
|
||||
private function makeDriver()
|
||||
{
|
||||
$relevanceFieldName = $this->config->get('searchy.fieldName');
|
||||
|
||||
// Check if default driver is being overridden, otherwise
|
||||
// load the default
|
||||
@ -96,11 +98,12 @@ class SearchBuilder {
|
||||
}
|
||||
|
||||
// Gets the details for the selected driver from the configuration file
|
||||
$driverMap = $this->config->get("searchy.drivers.$driverName");
|
||||
$driver = $this->config->get("searchy.drivers.$driverName")['class'];
|
||||
|
||||
// Create a new instance of the selected drivers 'class' and pass
|
||||
// through table and fields to search
|
||||
return new $driverMap['class']( $this->table, $this->searchFields );
|
||||
$driverInstance = new $driver( $this->table, $this->searchFields, $relevanceFieldName );
|
||||
return $driverInstance;
|
||||
|
||||
}
|
||||
|
||||
|
@ -2,60 +2,98 @@
|
||||
|
||||
use TomLingham\Searchy\Interfaces\SearchDriverInterface;
|
||||
|
||||
/**
|
||||
* @property mixed methods
|
||||
* @property mixed matchers
|
||||
*/
|
||||
|
||||
abstract class BaseSearchDriver implements SearchDriverInterface {
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $searchFields;
|
||||
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
protected $searchString;
|
||||
|
||||
/**
|
||||
* @var null
|
||||
*/
|
||||
protected $table;
|
||||
|
||||
protected $columns;
|
||||
protected $searchFields;
|
||||
protected $searchString;
|
||||
protected $relevanceFieldName;
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* @param null $table
|
||||
* @param array $searchFields
|
||||
* @param $relevanceFieldName
|
||||
* @param array $columns
|
||||
* @internal param $relevanceField
|
||||
*/
|
||||
public function __construct( $table = null, $searchFields = [], $columns = ['*'] )
|
||||
public function __construct( $table = null, $searchFields = [], $relevanceFieldName, $columns = ['*'] )
|
||||
{
|
||||
$this->searchFields = $searchFields;
|
||||
$this->table = $table;
|
||||
$this->columns = $columns;
|
||||
$this->relevanceFieldName = $relevanceFieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify which columns to return
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function select()
|
||||
{
|
||||
$this->columns = func_get_args();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the string that is is being searched for
|
||||
*
|
||||
* @param $searchString
|
||||
* @return \Illuminate\Database\Query\Builder|mixed|static
|
||||
*/
|
||||
public function query( $searchString )
|
||||
{
|
||||
$this->searchString = trim(\DB::connection()->getPdo()->quote( $searchString ), "'");
|
||||
return $this;
|
||||
}
|
||||
|
||||
if(\Config::get('searchy.sanitize'))
|
||||
$this->searchString = $this->sanitize($searchString);
|
||||
/**
|
||||
* Get the results of the search as an Array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
return $this->run()->get();
|
||||
}
|
||||
|
||||
$results = \DB::table($this->table)
|
||||
->select( implode($this->columns, ', ') )
|
||||
->addSelect($this->buildSelectQuery( $this->searchFields ))
|
||||
->orderBy(\Config::get('searchy.fieldName'), 'desc')
|
||||
->having(\Config::get('searchy.fieldName'),'>', 0);
|
||||
/**
|
||||
* Returns an instance of the Laravel Fluent Database Query Object with the search
|
||||
* queries applied
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->run();
|
||||
}
|
||||
|
||||
die($results->toSQL());
|
||||
/**
|
||||
* Runs the 'having' method directly on the Laravel Fluent Database Query Object
|
||||
* and returns the instance of the object
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function having()
|
||||
{
|
||||
return call_user_func_array([$this->run(), 'having'], func_get_args());
|
||||
}
|
||||
|
||||
return $results;
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
protected function run()
|
||||
{
|
||||
$this->query = \DB::table( $this->table )
|
||||
->select( $this->columns )
|
||||
->addSelect( $this->buildSelectQuery( $this->searchFields ) )
|
||||
->orderBy( $this->relevanceFieldName, 'desc' )
|
||||
->having( $this->relevanceFieldName, '>', 0 );
|
||||
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -64,20 +102,18 @@ abstract class BaseSearchDriver implements SearchDriverInterface {
|
||||
*/
|
||||
protected function buildSelectQuery( array $searchFields )
|
||||
{
|
||||
|
||||
$query = [];
|
||||
|
||||
foreach ($searchFields as $searchField) {
|
||||
if (strpos($searchField, '::')){
|
||||
$concatString = explode('::', $searchField);
|
||||
$query[] = $this->buildSelectCriteria( "CONCAT({$concatString[0]}, ' ', {$concatString[1]})");
|
||||
$concatString = str_replace('::', ", ' ', ", $searchField);
|
||||
$query[] = $this->buildSelectCriteria( "CONCAT({$concatString})");
|
||||
} else {
|
||||
$query[] = $this->buildSelectCriteria( $searchField );
|
||||
}
|
||||
}
|
||||
|
||||
return \DB::raw(implode(' + ', $query) . ' AS ' . \Config::get('searchy.fieldName'));
|
||||
|
||||
return \DB::raw(implode(' + ', $query) . ' AS ' . $this->relevanceFieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -86,7 +122,6 @@ abstract class BaseSearchDriver implements SearchDriverInterface {
|
||||
*/
|
||||
protected function buildSelectCriteria( $searchField = null )
|
||||
{
|
||||
|
||||
$criteria = [];
|
||||
|
||||
foreach( $this->matchers as $matcher => $multiplier){
|
||||
@ -105,20 +140,8 @@ abstract class BaseSearchDriver implements SearchDriverInterface {
|
||||
*/
|
||||
protected function makeMatcher( $searchField, $matcherClass, $multiplier )
|
||||
{
|
||||
|
||||
$matcher = new $matcherClass( $multiplier );
|
||||
|
||||
return $matcher->buildQueryString( $searchField, $this->searchString );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $searchString
|
||||
* @return mixed
|
||||
*/
|
||||
private function sanitize( $searchString )
|
||||
{
|
||||
return preg_replace(\Config::get('searchy.sanitizeRegEx'), '', $searchString );
|
||||
}
|
||||
|
||||
}
|
@ -23,6 +23,10 @@ class SearchyServiceProvider extends ServiceProvider {
|
||||
{
|
||||
return new SearchBuilder( $app['config'] );
|
||||
});
|
||||
|
||||
$this->mergeConfigFrom(
|
||||
__DIR__ . '/../../config/config.php', 'searchy'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -4,10 +4,6 @@ return [
|
||||
|
||||
'default' => 'fuzzy',
|
||||
|
||||
'sanitize' => true,
|
||||
|
||||
'sanitizeRegEx' => '/[%\']+/i',
|
||||
|
||||
'fieldName' => 'relevance',
|
||||
|
||||
'drivers' => [
|
||||
|
Reference in New Issue
Block a user