Updating some terminology and variable names. Also updated readme to include more information about extending the package and fixed some spelling errors
This commit is contained in:
29
README.md
29
README.md
@ -1,8 +1,9 @@
|
||||
Laravel 4+ Searchy - Searching Made Easy
|
||||
Laravel 4+ Searchy
|
||||
========================================
|
||||
### Database Searching Made Easy
|
||||
|
||||
Searchy is an easy-to-use Laravel 4+ package that makes running user driven searches on data in your models easy and effective.
|
||||
It requires no other software installed on your server (so a bit slower) but can be setup and ready to go in minutes.
|
||||
Searchy is an easy-to-use Laravel 4+ package that makes running user driven searches on data in your models simple and effective.
|
||||
It requires no other software installed on your server (so a bit slower than dedicated search programs) but can be setup and ready to go in minutes.
|
||||
|
||||
Installation
|
||||
----------------------------------------
|
||||
@ -41,7 +42,7 @@ Configuration
|
||||
----------------------------------------
|
||||
You can publish the configuration file to override the settings by running `php artisan config:publish tom-lingham/searchy`
|
||||
|
||||
You can set the default driver to use for ssearches in the configuration file. Your options (At this stage) are: `fuzzy`, `simple` and `levenshtein`.
|
||||
You can set the default driver to use for searches in the configuration file. Your options (At this stage) are: `fuzzy`, `simple` and `levenshtein`.
|
||||
|
||||
You can also override these methods using the following syntax when running a search:
|
||||
|
||||
@ -59,12 +60,26 @@ Drivers are simply a specified group of 'Matchers' which match strings based on
|
||||
Currently there are only three drivers: Simple, Fuzzy and Levenshtein (Experimental).
|
||||
|
||||
|
||||
Extending
|
||||
----------------------------------------
|
||||
#### Drivers
|
||||
It's really easy to roll your own search drivers. Simply create a class that extends TomLingham\Searchy\SearchDrivers\BaseSearchDriver and add a property called `$matchers` with an array of matcher classes as the key and the multiplier as the values. You can pick from the classes that are already included with Searchy or you can create your own.
|
||||
|
||||
### Road Map
|
||||
#### Matchers
|
||||
To create your own matchers, you can create your own class that extends TomLingham\Searchy\Matchers\BaseMatcher and (for simple Matchers) override the `formatQuery` method to return a string formatted with `%` wildcards in required locations. For more advanced extensions you may need to override the `buildQuery` method and others as well.
|
||||
|
||||
So, the intention is to (in the future):
|
||||
|
||||
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. Plesae make sure to include as much information as possible.
|
||||
|
||||
|
||||
Road Map
|
||||
----------------------------------------
|
||||
Too the future! The intention is to, eventually:
|
||||
|
||||
1. Remove Searchy's dependancy on Laravel
|
||||
2. Include more drivers for more advanced searching (Including file searching, indexing and more)
|
||||
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 autosuggestion features on the front end
|
||||
4. Speed up search performance
|
||||
|
@ -17,16 +17,11 @@ class AcronymMatcher extends BaseMatcher
|
||||
protected $operator = 'LIKE';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $multiplier = 42;
|
||||
|
||||
/**
|
||||
* @param $searchString
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function formatSearchString( $searchString ) {
|
||||
|
||||
public function formatSearchString( $searchString )
|
||||
{
|
||||
return implode( '% ', str_split(strtoupper( $searchString ))) . '%';
|
||||
}
|
||||
}
|
@ -11,7 +11,8 @@ abstract class BaseMatcher implements MatcherInterface
|
||||
|
||||
protected $multiplier;
|
||||
|
||||
public function __construct( $multiplier ){
|
||||
public function __construct( $multiplier )
|
||||
{
|
||||
$this->multiplier = $multiplier;
|
||||
}
|
||||
|
||||
@ -22,8 +23,8 @@ abstract class BaseMatcher implements MatcherInterface
|
||||
* @param $searchString
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function buildQueryString( $column, $searchString ){
|
||||
|
||||
public function buildQueryString( $column, $searchString )
|
||||
{
|
||||
if ( method_exists($this, 'formatSearchString') )
|
||||
$searchString = $this->formatSearchString( $searchString );
|
||||
|
||||
|
@ -17,15 +17,11 @@ class ConsecutiveCharactersMatcher extends BaseMatcher
|
||||
protected $operator = 'LIKE';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $multiplier = 40;
|
||||
|
||||
/**
|
||||
* @param $searchString
|
||||
* @return string
|
||||
*/
|
||||
public function formatSearchString( $searchString ) {
|
||||
public function formatSearchString( $searchString )
|
||||
{
|
||||
return '%'.implode('%', str_split( $searchString )).'%';
|
||||
}
|
||||
|
||||
@ -34,8 +30,8 @@ class ConsecutiveCharactersMatcher extends BaseMatcher
|
||||
* @param $rawString
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function buildQueryString( $column, $rawString ){
|
||||
|
||||
public function buildQueryString( $column, $rawString )
|
||||
{
|
||||
$searchString = $this->formatSearchString( $rawString );
|
||||
|
||||
$query = "IF($column {$this->operator} '$searchString', ROUND({$this->multiplier} * (CHAR_LENGTH( '$rawString' ) / CHAR_LENGTH( REPLACE($column, ' ', '') ))), 0)";
|
||||
|
@ -11,14 +11,10 @@
|
||||
|
||||
class ExactMatcher extends BaseMatcher
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $operator = '=';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $multiplier = 100;
|
||||
|
||||
}
|
@ -18,15 +18,11 @@ class InStringMatcher extends BaseMatcher
|
||||
protected $operator = 'LIKE';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $multiplier = 30;
|
||||
|
||||
/**
|
||||
* @param $searchString
|
||||
* @return string
|
||||
*/
|
||||
public function formatSearchString( $searchString ){
|
||||
public function formatSearchString( $searchString )
|
||||
{
|
||||
return "%$searchString%";
|
||||
}
|
||||
}
|
@ -24,10 +24,9 @@ class LevenshteinMatcher extends BaseMatcher
|
||||
* @param $searchString
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function buildQueryString( $column, $searchString ){
|
||||
|
||||
public function buildQueryString( $column, $searchString )
|
||||
{
|
||||
return "levenshtein($column, '$searchString', {$this->sensitivity})";
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -18,15 +18,11 @@ class StartOfStringMatcher extends BaseMatcher
|
||||
protected $operator = 'LIKE';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $multiplier = 50;
|
||||
|
||||
/**
|
||||
* @param $searchString
|
||||
* @return string
|
||||
*/
|
||||
public function formatSearchString( $searchString ) {
|
||||
public function formatSearchString( $searchString )
|
||||
{
|
||||
return "$searchString%";
|
||||
}
|
||||
}
|
@ -18,15 +18,11 @@ class StartOfWordsMatcher extends BaseMatcher
|
||||
protected $operator = 'LIKE';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $multiplier = 35;
|
||||
|
||||
/**
|
||||
* @param $searchString
|
||||
* @return string
|
||||
*/
|
||||
public function formatSearchString( $searchString ) {
|
||||
public function formatSearchString( $searchString )
|
||||
{
|
||||
return implode('% ', explode(' ', $searchString)) . '%';
|
||||
}
|
||||
}
|
@ -17,21 +17,17 @@ class StudlyCaseMatcher extends BaseMatcher
|
||||
protected $operator = 'LIKE BINARY';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $multiplier = 32;
|
||||
|
||||
/**
|
||||
* @param $searchString
|
||||
* @return string
|
||||
*/
|
||||
public function formatSearchString( $searchString ) {
|
||||
public function formatSearchString( $searchString )
|
||||
{
|
||||
|
||||
return implode( '%', str_split(strtoupper( $searchString ))) . '%';
|
||||
}
|
||||
|
||||
public function buildQueryString( $column, $searchString ){
|
||||
|
||||
public function buildQueryString( $column, $searchString )
|
||||
{
|
||||
$query = "IF( CHAR_LENGTH( TRIM($column)) = CHAR_LENGTH( REPLACE( TRIM($column), ' ', '')) AND $column {$this->operator} '{$this->formatSearchString($searchString)}', {$this->multiplier}, 0)";
|
||||
|
||||
return $query;
|
||||
|
@ -14,17 +14,12 @@ class TimesInStringMatcher extends BaseMatcher
|
||||
{
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $multiplier = 8;
|
||||
|
||||
/**
|
||||
* @param $column
|
||||
* @param $searchString
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function buildQueryString( $column, $searchString ){
|
||||
|
||||
public function buildQueryString( $column, $searchString )
|
||||
{
|
||||
$query = "{$this->multiplier} * ROUND ((
|
||||
CHAR_LENGTH($column) - CHAR_LENGTH( REPLACE ( LOWER($column), lower('$searchString'), ''))
|
||||
) / LENGTH('$searchString'))";
|
||||
|
@ -10,8 +10,19 @@ use TomLingham\Searchy\SearchDrivers\FuzzySearchDriver;
|
||||
class SearchBuilder {
|
||||
|
||||
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
private $table;
|
||||
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
private $searchFields;
|
||||
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
private $driverName;
|
||||
|
||||
/**
|
||||
@ -46,18 +57,19 @@ class SearchBuilder {
|
||||
public function driver( $driverName )
|
||||
{
|
||||
$this->driverName = $driverName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $table
|
||||
* @param $fields
|
||||
* @param $searchFields
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call( $table, $fields )
|
||||
public function __call( $table, $searchFields )
|
||||
{
|
||||
|
||||
return call_user_func_array([$this->search( $table ), 'fields'], $fields);
|
||||
return call_user_func_array([$this->search( $table ), 'fields'], $searchFields);
|
||||
|
||||
}
|
||||
|
||||
@ -66,13 +78,19 @@ class SearchBuilder {
|
||||
*/
|
||||
private function makeDriver()
|
||||
{
|
||||
if (! $this->driverName){
|
||||
$driverName = \Config::get('searchy::default');
|
||||
} else {
|
||||
// Check if default driver is being overridden, otherwise
|
||||
// load the default
|
||||
if ( $this->driverName ){
|
||||
$driverName = $this->driverName;
|
||||
} else {
|
||||
$driverName = \Config::get('searchy::default');
|
||||
}
|
||||
|
||||
// Gets the details for the selected driver from the configuration file
|
||||
$driverMap = \Config::get("searchy::drivers.$driverName");
|
||||
|
||||
// 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 );
|
||||
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ abstract class BaseSearchDriver implements SearchDriverInterface {
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $fields;
|
||||
protected $searchFields;
|
||||
|
||||
/**
|
||||
* @var
|
||||
@ -25,10 +25,11 @@ abstract class BaseSearchDriver implements SearchDriverInterface {
|
||||
|
||||
/**
|
||||
* @param null $table
|
||||
* @param array $fields
|
||||
* @param array $searchFields
|
||||
*/
|
||||
public function __construct( $table = null, $fields = [] ){
|
||||
$this->fields = $fields;
|
||||
public function __construct( $table = null, $searchFields = [] )
|
||||
{
|
||||
$this->searchFields = $searchFields;
|
||||
$this->table = $table;
|
||||
}
|
||||
|
||||
@ -37,14 +38,15 @@ abstract class BaseSearchDriver implements SearchDriverInterface {
|
||||
* @return \Illuminate\Database\Query\Builder|mixed|static
|
||||
* @throws \Whoops\Example\Exception
|
||||
*/
|
||||
public function query( $searchString ){
|
||||
public function query( $searchString )
|
||||
{
|
||||
|
||||
if(\Config::get('searchy::sanitize'))
|
||||
$this->searchString = $this->sanitize($searchString);
|
||||
|
||||
$results = \DB::table($this->table)
|
||||
->select( \DB::raw('*') )
|
||||
->addSelect($this->buildSelectQuery( $this->fields ))
|
||||
->select('*')
|
||||
->addSelect($this->buildSelectQuery( $this->searchFields ))
|
||||
->orderBy(\Config::get('searchy::fieldName'), 'desc')
|
||||
->having(\Config::get('searchy::fieldName'),'>', 0);
|
||||
|
||||
@ -52,14 +54,16 @@ abstract class BaseSearchDriver implements SearchDriverInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $fields
|
||||
* @param array $searchFields
|
||||
* @return array|\Illuminate\Database\Query\Expression
|
||||
*/
|
||||
protected function buildSelectQuery( array $fields ){
|
||||
protected function buildSelectQuery( array $searchFields )
|
||||
{
|
||||
|
||||
$query = [];
|
||||
|
||||
foreach ($fields as $field) {
|
||||
$query[] = $this->buildSelectCriteria( $field );
|
||||
foreach ($searchFields as $searchField) {
|
||||
$query[] = $this->buildSelectCriteria( $searchField );
|
||||
}
|
||||
|
||||
$query = \DB::raw(implode(' + ', $query) . ' AS ' . \Config::get('searchy::fieldName'));
|
||||
@ -68,14 +72,16 @@ abstract class BaseSearchDriver implements SearchDriverInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null $field
|
||||
* @param null $searchField
|
||||
* @return string
|
||||
*/
|
||||
protected function buildSelectCriteria( $field = null ) {
|
||||
protected function buildSelectCriteria( $searchField = null )
|
||||
{
|
||||
|
||||
$criteria = [];
|
||||
|
||||
foreach( $this->matchers as $matcher => $multiplier){
|
||||
$criteria[] = $this->makeMatcher( $field, $matcher, $multiplier );
|
||||
$criteria[] = $this->makeMatcher( $searchField, $matcher, $multiplier );
|
||||
}
|
||||
|
||||
return implode(' + ', $criteria);
|
||||
@ -83,17 +89,17 @@ abstract class BaseSearchDriver implements SearchDriverInterface {
|
||||
|
||||
|
||||
/**
|
||||
* @param $field
|
||||
* @param $searchField
|
||||
* @param $matcherClass
|
||||
* @param $multiplier
|
||||
* @return mixed
|
||||
*/
|
||||
protected function makeMatcher( $field, $matcherClass, $multiplier )
|
||||
protected function makeMatcher( $searchField, $matcherClass, $multiplier )
|
||||
{
|
||||
|
||||
$matcher = new $matcherClass( $multiplier );
|
||||
|
||||
return $matcher->buildQueryString( $field, $this->searchString );
|
||||
return $matcher->buildQueryString( $searchField, $this->searchString );
|
||||
|
||||
}
|
||||
|
||||
@ -101,7 +107,8 @@ abstract class BaseSearchDriver implements SearchDriverInterface {
|
||||
* @param $searchString
|
||||
* @return mixed
|
||||
*/
|
||||
private function sanitize( $searchString ) {
|
||||
private function sanitize( $searchString )
|
||||
{
|
||||
return preg_replace(\Config::get('searchy::sanitizeRegEx'), '', $searchString);
|
||||
}
|
||||
|
||||
|
42
src/res/levenshtein.sql
Normal file
42
src/res/levenshtein.sql
Normal file
@ -0,0 +1,42 @@
|
||||
DELIMITER $$
|
||||
DROP FUNCTION IF EXISTS levenshtein $$
|
||||
CREATE FUNCTION levenshtein( s1 VARCHAR(255), s2 VARCHAR(255), sensitivity INT )
|
||||
RETURNS INT
|
||||
DETERMINISTIC
|
||||
BEGIN
|
||||
DECLARE s1_len, s2_len, i, j, c, c_temp, cost INT;
|
||||
DECLARE s1_char CHAR;
|
||||
-- max strlen=255
|
||||
DECLARE cv0, cv1 VARBINARY(256);
|
||||
SET s1_len = CHAR_LENGTH(s1), s2_len = CHAR_LENGTH(s2), cv1 = 0x00, j = 1, i = 1, c = 0;
|
||||
IF s1 = s2 THEN
|
||||
RETURN 0;
|
||||
ELSEIF s1_len = 0 THEN
|
||||
RETURN s2_len;
|
||||
ELSEIF s2_len = 0 THEN
|
||||
RETURN s1_len;
|
||||
ELSE
|
||||
WHILE j <= s2_len DO
|
||||
SET cv1 = CONCAT(cv1, UNHEX(HEX(j))), j = j + 1;
|
||||
END WHILE;
|
||||
WHILE i <= s1_len DO
|
||||
SET s1_char = SUBSTRING(s1, i, 1), c = i, cv0 = UNHEX(HEX(i)), j = 1;
|
||||
WHILE j <= s2_len DO
|
||||
SET c = c + 1;
|
||||
IF s1_char = SUBSTRING(s2, j, 1) THEN
|
||||
SET cost = 0; ELSE SET cost = 1;
|
||||
END IF;
|
||||
SET c_temp = CONV(HEX(SUBSTRING(cv1, j, 1)), 16, 10) + cost;
|
||||
IF c > c_temp THEN SET c = c_temp; END IF;
|
||||
SET c_temp = CONV(HEX(SUBSTRING(cv1, j+1, 1)), 16, 10) + 1;
|
||||
IF c > c_temp THEN
|
||||
SET c = c_temp;
|
||||
END IF;
|
||||
SET cv0 = CONCAT(cv0, UNHEX(HEX(c))), j = j + 1;
|
||||
END WHILE;
|
||||
SET cv1 = cv0, i = i + 1;
|
||||
END WHILE;
|
||||
END IF;
|
||||
RETURN c;
|
||||
END$$
|
||||
DELIMITER ;
|
Reference in New Issue
Block a user