Browse Source

Switch to PSR-4, fix CS and update service provider

master
James Brooks 8 years ago
parent
commit
c2ec458b2c
  1. 8
      .gitattributes
  2. 4
      composer.json
  3. 25
      config/searchy.php
  4. 21
      src/Facades/Searchy.php
  5. 16
      src/Interfaces/MatcherInterface.php
  6. 12
      src/Interfaces/SearchDriverInterface.php
  7. 30
      src/Matchers/AcronymMatcher.php
  8. 36
      src/Matchers/BaseMatcher.php
  9. 44
      src/Matchers/ConsecutiveCharactersMatcher.php
  10. 38
      src/Matchers/ExactMatcher.php
  11. 28
      src/Matchers/InStringMatcher.php
  12. 24
      src/Matchers/LevenshteinMatcher.php
  13. 28
      src/Matchers/StartOfStringMatcher.php
  14. 28
      src/Matchers/StartOfWordsMatcher.php
  15. 35
      src/Matchers/StudlyCaseMatcher.php
  16. 58
      src/Matchers/TimesInStringMatcher.php
  17. 111
      src/SearchBuilder.php
  18. 154
      src/SearchDrivers/BaseSearchDriver.php
  19. 20
      src/SearchDrivers/FuzzySearchDriver.php
  20. 13
      src/SearchDrivers/LevenshteinSearchDriver.php
  21. 15
      src/SearchDrivers/SimpleSearchDriver.php
  22. 56
      src/SearchyServiceProvider.php
  23. 19
      src/TomLingham/Searchy/Facades/Searchy.php
  24. 14
      src/TomLingham/Searchy/Interfaces/MatcherInterface.php
  25. 12
      src/TomLingham/Searchy/Interfaces/SearchDriverInterface.php
  26. 28
      src/TomLingham/Searchy/Matchers/AcronymMatcher.php
  27. 34
      src/TomLingham/Searchy/Matchers/BaseMatcher.php
  28. 40
      src/TomLingham/Searchy/Matchers/ConsecutiveCharactersMatcher.php
  29. 28
      src/TomLingham/Searchy/Matchers/InStringMatcher.php
  30. 25
      src/TomLingham/Searchy/Matchers/LevenshteinMatcher.php
  31. 28
      src/TomLingham/Searchy/Matchers/StartOfStringMatcher.php
  32. 28
      src/TomLingham/Searchy/Matchers/StartOfWordsMatcher.php
  33. 34
      src/TomLingham/Searchy/Matchers/StudlyCaseMatcher.php
  34. 110
      src/TomLingham/Searchy/SearchBuilder.php
  35. 147
      src/TomLingham/Searchy/SearchDrivers/BaseSearchDriver.php
  36. 19
      src/TomLingham/Searchy/SearchDrivers/FuzzySearchDriver.php
  37. 12
      src/TomLingham/Searchy/SearchDrivers/LevenshteinSearchDriver.php
  38. 14
      src/TomLingham/Searchy/SearchDrivers/SimpleSearchDriver.php
  39. 52
      src/TomLingham/Searchy/SearchyServiceProvider.php
  40. 28
      src/config/config.php

8
.gitattributes

@ -0,0 +1,8 @@
* text=auto
/tests export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.travis.yml export-ignore
/phpunit.xml export-ignore
/README.md export-ignore

4
composer.json

@ -17,8 +17,8 @@
"phpspec/phpspec": "~2.0"
},
"autoload": {
"psr-0": {
"TomLingham": "src/"
"psr-4": {
"TomLingham\\Searchy\\": "src/"
}
},
"minimum-stability": "stable"

25
config/searchy.php

@ -0,0 +1,25 @@
<?php
return [
'default' => 'fuzzy',
'fieldName' => 'relevance',
'drivers' => [
'fuzzy' => [
'class' => 'TomLingham\Searchy\SearchDrivers\FuzzySearchDriver',
],
'simple' => [
'class' => 'TomLingham\Searchy\SearchDrivers\SimpleSearchDriver',
],
'levenshtein' => [
'class' => 'TomLingham\Searchy\SearchDrivers\LevenshteinSearchDriver',
],
],
];

21
src/Facades/Searchy.php

@ -0,0 +1,21 @@
<?php
namespace TomLingham\Searchy\Facades;
use Illuminate\Support\Facades\Facade;
/**
* Searchy facade for the Laravel framework.
*/
class Searchy extends Facade
{
/**
* Get the registered component.
*
* @return object
*/
protected static function getFacadeAccessor()
{
return 'searchy';
}
}

16
src/Interfaces/MatcherInterface.php

@ -0,0 +1,16 @@
<?php
namespace TomLingham\Searchy\Interfaces;
interface MatcherInterface
{
/**
* Builds the string to add to the SELECT statement for the Matcher.
*
* @param $column
* @param $searchString
*
* @return mixed
*/
public function buildQueryString($column, $searchString);
}

12
src/Interfaces/SearchDriverInterface.php

@ -0,0 +1,12 @@
<?php
namespace TomLingham\Searchy\Interfaces;
interface SearchDriverInterface
{
public function query($searchString);
public function select(/* $columns */);
public function get();
}

30
src/Matchers/AcronymMatcher.php

@ -0,0 +1,30 @@
<?php
namespace TomLingham\Searchy\Matchers;
/**
* Matches strings for Acronym 'like' matches but does NOT return Studly Case Matches.
*
* for example, a search for 'fb' would match; 'foo bar' or 'Fred Brown' but not 'FreeBeer'.
*
* Class AcronymMatcher
*/
class AcronymMatcher extends BaseMatcher
{
/**
* @var string
*/
protected $operator = 'LIKE';
/**
* @param $searchString
*
* @return mixed|string
*/
public function formatSearchString($searchString)
{
$searchString = preg_replace('/[^0-9a-zA-Z]/', '', $searchString);
return implode('% ', str_split(strtoupper($searchString))).'%';
}
}

36
src/Matchers/BaseMatcher.php

@ -0,0 +1,36 @@
<?php
namespace TomLingham\Searchy\Matchers;
use TomLingham\Searchy\Interfaces\MatcherInterface;
/**
* @property mixed multiplier
* @property mixed operator
*/
abstract class BaseMatcher implements MatcherInterface
{
protected $multiplier;
public function __construct($multiplier)
{
$this->multiplier = $multiplier;
}
/**
* The default process for building the Query string.
*
* @param $column
* @param $searchString
*
* @return mixed|string
*/
public function buildQueryString($column, $searchString)
{
if (method_exists($this, 'formatSearchString')) {
$searchString = $this->formatSearchString($searchString);
}
return "IF($column {$this->operator} '$searchString', {$this->multiplier}, 0)";
}
}

44
src/Matchers/ConsecutiveCharactersMatcher.php

@ -0,0 +1,44 @@
<?php
namespace TomLingham\Searchy\Matchers;
/**
* Matches strings that include all the characters in the search relatively position within the string.
* It also calculates the percentage of characters in the string that are matched and applies the multiplier accordingly.
*
* For Example, a search for 'fba' would match; 'Foo Bar' or 'Afraid of bats'
*
* Class ConsecutiveCharactersMatcher
*/
class ConsecutiveCharactersMatcher extends BaseMatcher
{
/**
* @var string
*/
protected $operator = 'LIKE';
/**
* @param $searchString
*
* @return string
*/
public function formatSearchString($searchString)
{
$searchString = preg_replace('/[^0-9a-zA-Z]/', '', $searchString);
return '%'.implode('%', str_split($searchString)).'%';
}
/**
* @param $column
* @param $rawString
*
* @return mixed|string
*/
public function buildQueryString($column, $rawString)
{
$searchString = $this->formatSearchString($rawString);
return "IF(REPLACE($column, '\.', '') {$this->operator} '$searchString', ROUND({$this->multiplier} * (CHAR_LENGTH( '$rawString' ) / CHAR_LENGTH( REPLACE($column, ' ', '') ))), 0)";
}
}

38
src/TomLingham/Searchy/Matchers/ExactMatcher.php → src/Matchers/ExactMatcher.php

@ -1,20 +1,18 @@
<?php namespace TomLingham\Searchy\Matchers;
/**
* 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.
*
* Class ExactMatcher
* @package TomLingham\Searchy\Matchers
*/
class ExactMatcher extends BaseMatcher
{
/**
* @var string
*/
protected $operator = '=';
}
<?php
namespace TomLingham\Searchy\Matchers;
/**
* 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.
*
* Class ExactMatcher
*/
class ExactMatcher extends BaseMatcher
{
/**
* @var string
*/
protected $operator = '=';
}

28
src/Matchers/InStringMatcher.php

@ -0,0 +1,28 @@
<?php
namespace TomLingham\Searchy\Matchers;
/**
* Matches against any occurrences of a string within a string and is case-insensitive.
*
* For example, a search for 'smi' would match; 'John Smith' or 'Smiley Face'
*
* Class InStringMatcher
*/
class InStringMatcher extends BaseMatcher
{
/**
* @var string
*/
protected $operator = 'LIKE';
/**
* @param $searchString
*
* @return string
*/
public function formatSearchString($searchString)
{
return "%$searchString%";
}
}

24
src/Matchers/LevenshteinMatcher.php

@ -0,0 +1,24 @@
<?php
namespace TomLingham\Searchy\Matchers;
/**
* Matches strings for Acronym 'like' matches but does NOT return Studly Case Matches.
*
* for example, a search for 'fb' would match; 'foo bar' or 'Fred Brown' but not 'FreeBeer'.
*
* Class AcronymMatcher
*/
class LevenshteinMatcher extends BaseMatcher
{
/**
* @param $column
* @param $searchString
*
* @return mixed|string
*/
public function buildQueryString($column, $searchString)
{
return "levenshtein($column, '$searchString')";
}
}

28
src/Matchers/StartOfStringMatcher.php

@ -0,0 +1,28 @@
<?php
namespace TomLingham\Searchy\Matchers;
/**
* Matches Strings that begin with the search string.
*
* For example, a search for 'hel' would match; 'Hello World' or 'helping hand'
*
* Class StartOfStringMatcher
*/
class StartOfStringMatcher extends BaseMatcher
{
/**
* @var string
*/
protected $operator = 'LIKE';
/**
* @param $searchString
*
* @return string
*/
public function formatSearchString($searchString)
{
return "$searchString%";
}
}

28
src/Matchers/StartOfWordsMatcher.php

@ -0,0 +1,28 @@
<?php
namespace TomLingham\Searchy\Matchers;
/**
* Matches the start of each word against each word in a search.
*
* For example, a search for 'jo ta' would match; 'John Taylor' or 'Joshua B. Takashi'
*
* Class StartOfWordsMatcher
*/
class StartOfWordsMatcher extends BaseMatcher
{
/**
* @var string
*/
protected $operator = 'LIKE';
/**
* @param $searchString
*
* @return string
*/
public function formatSearchString($searchString)
{
return implode('% ', explode(' ', $searchString)).'%';
}
}

35
src/Matchers/StudlyCaseMatcher.php

@ -0,0 +1,35 @@
<?php
namespace TomLingham\Searchy\Matchers;
/**
* Matches Studly Case strings using the first letters of the words only.
*
* For example a search for 'hp' would match; 'HtmlServiceProvider' or 'HashParser' but not 'hasProvider'
*
* Class StudlyCaseMatcher
*/
class StudlyCaseMatcher extends BaseMatcher
{
/**
* @var string
*/
protected $operator = 'LIKE BINARY';
/**
* @param $searchString
*
* @return string
*/
public function formatSearchString($searchString)
{
$searchString = preg_replace('/[^0-9a-zA-Z]/', '', $searchString);
return implode('%', str_split(strtoupper($searchString))).'%';
}
public function buildQueryString($column, $searchString)
{
return "IF( CHAR_LENGTH( TRIM($column)) = CHAR_LENGTH( REPLACE( TRIM($column), ' ', '')) AND $column {$this->operator} '{$this->formatSearchString($searchString)}', {$this->multiplier}, 0)";
}
}

58
src/TomLingham/Searchy/Matchers/TimesInStringMatcher.php → src/Matchers/TimesInStringMatcher.php

@ -1,29 +1,29 @@
<?php namespace TomLingham\Searchy\Matchers;
/**
* Matches a string based on how many times the search string appears inside the string
* it then applies the multiplier for each occurrence.
*
* For example, a search for 'tha' would match; 'I hope that that cat has caught that mouse' (3 x multiplier) or 'Thanks, it was great!' (1 x multiplier)
*
* Class TimesInStringMatcher
* @package TomLingham\Searchy\Matchers
*/
class TimesInStringMatcher extends BaseMatcher
{
/**
* @param $column
* @param $searchString
* @return mixed|string
*/
public function buildQueryString( $column, $searchString )
{
$query = "{$this->multiplier} * ROUND ((
CHAR_LENGTH($column) - CHAR_LENGTH( REPLACE ( LOWER($column), lower('$searchString'), ''))
) / LENGTH('$searchString'))";
return $query;
}
}
<?php
namespace TomLingham\Searchy\Matchers;
/**
* Matches a string based on how many times the search string appears inside the string
* it then applies the multiplier for each occurrence.
*
* For example, a search for 'tha' would match; 'I hope that that cat has caught that mouse' (3 x multiplier) or 'Thanks, it was great!' (1 x multiplier)
*
* Class TimesInStringMatcher
*/
class TimesInStringMatcher extends BaseMatcher
{
/**
* @param $column
* @param $searchString
*
* @return mixed|string
*/
public function buildQueryString($column, $searchString)
{
$query = "{$this->multiplier} * ROUND ((
CHAR_LENGTH($column) - CHAR_LENGTH( REPLACE ( LOWER($column), lower('$searchString'), ''))
) / LENGTH('$searchString'))";
return $query;
}
}

111
src/SearchBuilder.php

@ -0,0 +1,111 @@
<?php
namespace TomLingham\Searchy;
use Illuminate\Config\Repository;
use TomLingham\Searchy\SearchDrivers\FuzzySearchDriver;
/**
* @property mixed driverName
*/
class SearchBuilder
{
/**
* @var
*/
private $table;
/**
* @var
*/
private $searchFields;
/**
* @var
*/
private $driverName;
/**
* @var
*/
private $config;
public function __construct(Repository $config)
{
$this->config = $config;
}
/**
* @param $searchable
*
* @return $this
*/
public function search($searchable)
{
if (is_object($searchable) && method_exists($searchable, 'getTable')) {
$this->table = $searchable->getTable();
} else {
$this->table = $searchable;
}
return $this;
}
/**
* @return FuzzySearchDriver
*/
public function fields(/* $fields */)
{
$this->searchFields = func_get_args();
return $this->makeDriver();
}
/**
* @param $driverName
*
* @return $this
*/
public function driver($driverName)
{
$this->driverName = $driverName;
return $this;
}
/**
* @param $table
* @param $searchFields
*
* @return mixed
*/
public function __call($table, $searchFields)
{
return call_user_func_array([$this->search($table), 'fields'], $searchFields);
}
/**
* @return mixed
*/
private function makeDriver()
{
$relevanceFieldName = $this->config->get('searchy.fieldName');
// Check if default driver is being overridden, otherwise
// load the default
if ($this->driverName) {
$driverName = $this->driverName;
} else {
$driverName = $this->config->get('searchy.default');
}
// Gets the details for the selected driver from the configuration file
$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
$driverInstance = new $driver($this->table, $this->searchFields, $relevanceFieldName);
return $driverInstance;
}
}

154
src/SearchDrivers/BaseSearchDriver.php

@ -0,0 +1,154 @@
<?php
namespace TomLingham\Searchy\SearchDrivers;
use TomLingham\Searchy\Interfaces\SearchDriverInterface;
abstract class BaseSearchDriver implements SearchDriverInterface
{
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 = [], $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;
}
/**
* Get the results of the search as an Array.
*
* @return array
*/
public function get()
{
return $this->run()->get();
}
/**
* Returns an instance of the Laravel Fluent Database Query Object with the search
* queries applied.
*
* @return array
*/
public function getQuery()
{
return $this->run();
}
/**
* 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 $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;
}
/**
* @param array $searchFields
*
* @return array|\Illuminate\Database\Query\Expression
*/
protected function buildSelectQuery(array $searchFields)
{
$query = [];
foreach ($searchFields as $searchField) {
if (strpos($searchField, '::')) {
$concatString = str_replace('::', ", ' ', ", $searchField);
$query[] = $this->buildSelectCriteria("CONCAT({$concatString})");
} else {
$query[] = $this->buildSelectCriteria($searchField);
}
}
return \DB::raw(implode(' + ', $query).' AS '.$this->relevanceFieldName);
}
/**
* @param null $searchField
*
* @return string
*/
protected function buildSelectCriteria($searchField = null)
{
$criteria = [];
foreach ($this->matchers as $matcher => $multiplier) {
$criteria[] = $this->makeMatcher($searchField, $matcher, $multiplier);
}
return implode(' + ', $criteria);
}
/**
* @param $searchField
* @param $matcherClass
* @param $multiplier
*
* @return mixed
*/
protected function makeMatcher($searchField, $matcherClass, $multiplier)
{
$matcher = new $matcherClass($multiplier);
return $matcher->buildQueryString($searchField, $this->searchString);
}
}

20
src/SearchDrivers/FuzzySearchDriver.php

@ -0,0 +1,20 @@
<?php
namespace TomLingham\Searchy\SearchDrivers;
class FuzzySearchDriver extends BaseSearchDriver
{
/**
* @var array
*/
protected $matchers = [
'TomLingham\Searchy\Matchers\ExactMatcher' => 100,
'TomLingham\Searchy\Matchers\StartOfStringMatcher' => 50,
'TomLingham\Searchy\Matchers\AcronymMatcher' => 42,
'TomLingham\Searchy\Matchers\ConsecutiveCharactersMatcher' => 40,
'TomLingham\Searchy\Matchers\StartOfWordsMatcher' => 35,
'TomLingham\Searchy\Matchers\StudlyCaseMatcher' => 32,
'TomLingham\Searchy\Matchers\InStringMatcher' => 30,
'TomLingham\Searchy\Matchers\TimesInStringMatcher' => 8,
];
}

13
src/SearchDrivers/LevenshteinSearchDriver.php

@ -0,0 +1,13 @@
<?php
namespace TomLingham\Searchy\SearchDrivers;
class LevenshteinSearchDriver extends BaseSearchDriver
{
/**
* @var array
*/
protected $matchers = [
'TomLingham\Searchy\Matchers\LevenshteinMatcher' => 100,
];
}

15
src/SearchDrivers/SimpleSearchDriver.php

@ -0,0 +1,15 @@
<?php
namespace TomLingham\Searchy\SearchDrivers;
class SimpleSearchDriver extends BaseSearchDriver
{
/**
* @var array
*/
protected $matchers = [
'TomLingham\Searchy\Matchers\ExactMatcher' => 100,
'TomLingham\Searchy\Matchers\StartOfStringMatcher' => 50,
'TomLingham\Searchy\Matchers\InStringMatcher' => 30,
];
}

56
src/SearchyServiceProvider.php

@ -0,0 +1,56 @@
<?php
namespace TomLingham\Searchy;
use Illuminate\Support\ServiceProvider;
class SearchyServiceProvider extends ServiceProvider
{
/**
* Indicates if loading of the provider is deferred.
*
* @var bool
*/
protected $defer = false;
/**
* Register the service provider.
*/
public function register()
{
//
}
/**
* Registers searchy.
*/
public function registerSearchy()
{
$this->app->bindShared('searchy', function ($app) {
return new SearchBuilder($app['config']);
});
}
/**
* Loads the configuration file.
*/
public function setupConfig()
{
$source = realpath(__DIR__.'/../config/searchy.php');
if (class_exists('Illuminate\Foundation\Application', false)) {
$this->publishes([$source => config_path('searchy.php')]);
}
$this->mergeConfigFrom($source, 'searchy');
}
/**
* Boot the service provider.
*/
public function boot()
{
$this->setupConfig();
$this->registerSearchy();
}
}

19
src/TomLingham/Searchy/Facades/Searchy.php

@ -1,19 +0,0 @@
<?php namespace TomLingham\Searchy\Facades;
use Illuminate\Support\Facades\Facade;
/**
* Searchy facade for the Laravel framework
*/
class Searchy extends Facade
{
/**
* Get the registered component.
*
* @return object
*/
protected static function getFacadeAccessor()
{
return 'searchy';
}
}

14
src/TomLingham/Searchy/Interfaces/MatcherInterface.php

@ -1,14 +0,0 @@
<?php namespace TomLingham\Searchy\Interfaces;
interface MatcherInterface
{
/**
* Builds the string to add to the SELECT statement for the Matcher
*
* @param $column
* @param $searchString
* @return mixed
*/
public function buildQueryString( $column, $searchString );
}

12
src/TomLingham/Searchy/Interfaces/SearchDriverInterface.php

@ -1,12 +0,0 @@
<?php namespace TomLingham\Searchy\Interfaces;
interface SearchDriverInterface
{
public function query( $searchString );
public function select( /* $columns */ );
public function get();
}

28
src/TomLingham/Searchy/Matchers/AcronymMatcher.php

@ -1,28 +0,0 @@
<?php namespace TomLingham\Searchy\Matchers;
/**
* Matches strings for Acronym 'like' matches but does NOT return Studly Case Matches
*
* for example, a search for 'fb' would match; 'foo bar' or 'Fred Brown' but not 'FreeBeer'.
*
* Class AcronymMatcher
* @package TomLingham\Searchy\Matchers
*/
class AcronymMatcher extends BaseMatcher
{
/**
* @var string
*/
protected $operator = 'LIKE';
/**
* @param $searchString
* @return mixed|string
*/
public function formatSearchString( $searchString )
{
$searchString = preg_replace('/[^0-9a-zA-Z]/', '', $searchString);
return implode( '% ', str_split(strtoupper( $searchString ))) . '%';
}
}

34
src/TomLingham/Searchy/Matchers/BaseMatcher.php

@ -1,34 +0,0 @@
<?php namespace TomLingham\Searchy\Matchers;
use TomLingham\Searchy\Interfaces\MatcherInterface;
/**
* @property mixed multiplier
* @property mixed operator
*/
abstract class BaseMatcher implements MatcherInterface
{
protected $multiplier;
public function __construct( $multiplier )
{
$this->multiplier = $multiplier;
}
/**
* The default process for building the Query string
*
* @param $column
* @param $searchString
* @return mixed|string
*/
public function buildQueryString( $column, $searchString )
{
if ( method_exists($this, 'formatSearchString') )
$searchString = $this->formatSearchString( $searchString );
return "IF($column {$this->operator} '$searchString', {$this->multiplier}, 0)";
}
}

40
src/TomLingham/Searchy/Matchers/ConsecutiveCharactersMatcher.php

@ -1,40 +0,0 @@
<?php namespace TomLingham\Searchy\Matchers;
/**
* Matches strings that include all the characters in the search relatively position within the string.
* It also calculates the percentage of characters in the string that are matched and applies the multiplier accordingly.
*
* For Example, a search for 'fba' would match; 'Foo Bar' or 'Afraid of bats'
*
* Class ConsecutiveCharactersMatcher
* @package TomLingham\Searchy\Matchers
*/
class ConsecutiveCharactersMatcher extends BaseMatcher
{
/**
* @var string
*/
protected $operator = 'LIKE';
/**
* @param $searchString
* @return string
*/
public function formatSearchString( $searchString )
{
$searchString = preg_replace('/[^0-9a-zA-Z]/', '', $searchString);
return '%'.implode('%', str_split( $searchString )).'%';
}
/**
* @param $column
* @param $rawString
* @return mixed|string
*/
public function buildQueryString( $column, $rawString )
{
$searchString = $this->formatSearchString( $rawString );
return "IF(REPLACE($column, '\.', '') {$this->operator} '$searchString', ROUND({$this->multiplier} * (CHAR_LENGTH( '$rawString' ) / CHAR_LENGTH( REPLACE($column, ' ', '') ))), 0)";
}
}

28
src/TomLingham/Searchy/Matchers/InStringMatcher.php

@ -1,28 +0,0 @@
<?php namespace TomLingham\Searchy\Matchers;
/**
* Matches against any occurrences of a string within a string and is case-insensitive.
*
* For example, a search for 'smi' would match; 'John Smith' or 'Smiley Face'
*
* Class InStringMatcher
* @package TomLingham\Searchy\Matchers
*/
class InStringMatcher extends BaseMatcher
{
/**
* @var string
*/
protected $operator = 'LIKE';
/**
* @param $searchString
* @return string
*/
public function formatSearchString( $searchString )
{
return "%$searchString%";
}
}

25
src/TomLingham/Searchy/Matchers/LevenshteinMatcher.php

@ -1,25 +0,0 @@
<?php namespace TomLingham\Searchy\Matchers;
/**
* Matches strings for Acronym 'like' matches but does NOT return Studly Case Matches
*
* for example, a search for 'fb' would match; 'foo bar' or 'Fred Brown' but not 'FreeBeer'.
*
* Class AcronymMatcher
* @package TomLingham\Searchy\Matchers
*/
class LevenshteinMatcher extends BaseMatcher
{
/**
* @param $column
* @param $searchString
* @return mixed|string
*/
public function buildQueryString( $column, $searchString )
{
return "levenshtein($column, '$searchString')";
}
}

28
src/TomLingham/Searchy/Matchers/StartOfStringMatcher.php

@ -1,28 +0,0 @@
<?php namespace TomLingham\Searchy\Matchers;
/**
* Matches Strings that begin with the search string.
*
* For example, a search for 'hel' would match; 'Hello World' or 'helping hand'
*
* Class StartOfStringMatcher
* @package TomLingham\Searchy\Matchers
*/
class StartOfStringMatcher extends BaseMatcher
{
/**
* @var string
*/
protected $operator = 'LIKE';
/**
* @param $searchString
* @return string
*/
public function formatSearchString( $searchString )
{
return "$searchString%";
}
}

28
src/TomLingham/Searchy/Matchers/StartOfWordsMatcher.php

@ -1,28 +0,0 @@
<?php namespace TomLingham\Searchy\Matchers;
/**
* Matches the start of each word against each word in a search
*
* For example, a search for 'jo ta' would match; 'John Taylor' or 'Joshua B. Takashi'
*
* Class StartOfWordsMatcher
* @package TomLingham\Searchy\Matchers
*/
class StartOfWordsMatcher extends BaseMatcher
{
/**
* @var string
*/
protected $operator = 'LIKE';
/**
* @param $searchString
* @return string
*/
public function formatSearchString( $searchString )
{
return implode('% ', explode(' ', $searchString)) . '%';
}
}

34
src/TomLingham/Searchy/Matchers/StudlyCaseMatcher.php

@ -1,34 +0,0 @@
<?php namespace TomLingham\Searchy\Matchers;
/**
* Matches Studly Case strings using the first letters of the words only
*
* For example a search for 'hp' would match; 'HtmlServiceProvider' or 'HashParser' but not 'hasProvider'
*
* Class StudlyCaseMatcher
* @package TomLingham\Searchy\Matchers
*/
class StudlyCaseMatcher extends BaseMatcher
{
/**
* @var string
*/
protected $operator = 'LIKE BINARY';
/**
* @param $searchString
* @return string
*/
public function formatSearchString( $searchString )
{
$searchString = preg_replace('/[^0-9a-zA-Z]/', '', $searchString);
return implode( '%', str_split(strtoupper( $searchString ))) . '%';
}
public function buildQueryString( $column, $searchString )
{
return "IF( CHAR_LENGTH( TRIM($column)) = CHAR_LENGTH( REPLACE( TRIM($column), ' ', '')) AND $column {$this->operator} '{$this->formatSearchString($searchString)}', {$this->multiplier}, 0)";
}
}

110
src/TomLingham/Searchy/SearchBuilder.php

@ -1,110 +0,0 @@
<?php namespace TomLingham\Searchy;
use Illuminate\Config\Repository;
use TomLingham\Searchy\SearchDrivers\FuzzySearchDriver;
/**
* @property mixed driverName
*/
class SearchBuilder {
/**
* @var
*/
private $table;
/**
* @var
*/
private $searchFields;
/**
* @var
*/
private $driverName;
/**
* @var
*/
private $config;
public function __construct( Repository $config )
{
$this->config = $config;
}
/**
* @param $searchable
* @return $this
*/
public function search( $searchable )
{
if (is_object( $searchable ) && method_exists($searchable, 'getTable')) {
$this->table = $searchable->getTable();
} else {
$this->table = $searchable;
}
return $this;
}
/**
* @return FuzzySearchDriver
*/
public function fields( /* $fields */ )
{
$this->searchFields = func_get_args();
return $this->makeDriver();
}
/**
* @param $driverName
* @return $this
*/
public function driver( $driverName )
{
$this->driverName = $driverName;
return $this;
}
/**
* @param $table
* @param $searchFields
* @return mixed
*/
public function __call( $table, $searchFields )
{
return call_user_func_array([$this->search( $table ), 'fields'], $searchFields);
}
/**
* @return mixed
*/
private function makeDriver()
{
$relevanceFieldName = $this->config->get('searchy.fieldName');
// Check if default driver is being overridden, otherwise
// load the default
if ( $this->driverName ){
$driverName = $this->driverName;
} else {
$driverName = $this->config->get('searchy.default');
}
// Gets the details for the selected driver from the configuration file
$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
$driverInstance = new $driver( $this->table, $this->searchFields, $relevanceFieldName );
return $driverInstance;
}
}

147
src/TomLingham/Searchy/SearchDrivers/BaseSearchDriver.php

@ -1,147 +0,0 @@
<?php namespace TomLingham\Searchy\SearchDrivers;
use TomLingham\Searchy\Interfaces\SearchDriverInterface;
abstract class BaseSearchDriver implements SearchDriverInterface {
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 = [], $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;
}
/**
* Get the results of the search as an Array
*
* @return array
*/
public function get()
{
return $this->run()->get();
}
/**
* Returns an instance of the Laravel Fluent Database Query Object with the search
* queries applied
*
* @return array
*/
public function getQuery()
{
return $this->run();
}
/**
* 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 $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;
}
/**
* @param array $searchFields
* @return array|\Illuminate\Database\Query\Expression
*/
protected function buildSelectQuery( array $searchFields )
{
$query = [];
foreach ($searchFields as $searchField) {
if (strpos($searchField, '::')){
$concatString = str_replace('::', ", ' ', ", $searchField);
$query[] = $this->buildSelectCriteria( "CONCAT({$concatString})");
} else {
$query[] = $this->buildSelectCriteria( $searchField );
}
}
return \DB::raw(implode(' + ', $query) . ' AS ' . $this->relevanceFieldName);
}
/**
* @param null $searchField
* @return string
*/
protected function buildSelectCriteria( $searchField = null )
{
$criteria = [];
foreach( $this->matchers as $matcher => $multiplier){
$criteria[] = $this->makeMatcher( $searchField, $matcher, $multiplier );
}
return implode(' + ', $criteria);
}
/**
* @param $searchField
* @param $matcherClass
* @param $multiplier
* @return mixed
*/
protected function makeMatcher( $searchField, $matcherClass, $multiplier )
{
$matcher = new $matcherClass( $multiplier );
return $matcher->buildQueryString( $searchField, $this->searchString );
}
}

19
src/TomLingham/Searchy/SearchDrivers/FuzzySearchDriver.php

@ -1,19 +0,0 @@
<?php namespace TomLingham\Searchy\SearchDrivers;
class FuzzySearchDriver extends BaseSearchDriver {
/**
* @var array
*/
protected $matchers = [
'TomLingham\Searchy\Matchers\ExactMatcher' => 100,
'TomLingham\Searchy\Matchers\StartOfStringMatcher' => 50,
'TomLingham\Searchy\Matchers\AcronymMatcher' => 42,
'TomLingham\Searchy\Matchers\ConsecutiveCharactersMatcher' => 40,
'TomLingham\Searchy\Matchers\StartOfWordsMatcher' => 35,
'TomLingham\Searchy\Matchers\StudlyCaseMatcher' => 32,
'TomLingham\Searchy\Matchers\InStringMatcher' => 30,
'TomLingham\Searchy\Matchers\TimesInStringMatcher' => 8,
];
}

12
src/TomLingham/Searchy/SearchDrivers/LevenshteinSearchDriver.php

@ -1,12 +0,0 @@
<?php namespace TomLingham\Searchy\SearchDrivers;
class LevenshteinSearchDriver extends BaseSearchDriver {
/**
* @var array
*/
protected $matchers = [
'TomLingham\Searchy\Matchers\LevenshteinMatcher' => 100
];
}

14
src/TomLingham/Searchy/SearchDrivers/SimpleSearchDriver.php

@ -1,14 +0,0 @@
<?php namespace TomLingham\Searchy\SearchDrivers;
class SimpleSearchDriver extends BaseSearchDriver {
/**
* @var array
*/
protected $matchers = [
'TomLingham\Searchy\Matchers\ExactMatcher' => 100,
'TomLingham\Searchy\Matchers\StartOfStringMatcher' => 50,
'TomLingham\Searchy\Matchers\InStringMatcher' => 30,
];
}

52
src/TomLingham/Searchy/SearchyServiceProvider.php

@ -1,52 +0,0 @@
<?php namespace TomLingham\Searchy;
use Illuminate\Config\Repository;
use Illuminate\Support\ServiceProvider;
class SearchyServiceProvider extends ServiceProvider {
/**
* Indicates if loading of the provider is deferred.
*
* @var bool
*/
protected $defer = false;
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->bindShared('searchy', function( $app )
{
return new SearchBuilder( $app['config'] );
});
$this->mergeConfigFrom(
__DIR__ . '/../../config/config.php', 'searchy'
);
}
/**
*
*/
public function boot()
{
$this->publishes([
__DIR__.'/../../config/config.php' => config_path('searchy.php'),
]);
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return array();
}
}

28
src/config/config.php

@ -1,28 +0,0 @@
<?php
return [
'default' => 'fuzzy',
'fieldName' => 'relevance',
'drivers' => [
'fuzzy' => [
'class' => 'TomLingham\Searchy\SearchDrivers\FuzzySearchDriver'
],
'simple' => [
'class' => 'TomLingham\Searchy\SearchDrivers\SimpleSearchDriver'
],
'levenshtein' => [
'class' => 'TomLingham\Searchy\SearchDrivers\LevenshteinSearchDriver'
],
],
];
Loading…
Cancel
Save