Browse Source

First release beta

master
Tom Lingham 11 years ago
parent
commit
44ace92f93
  1. 24
      src/TomLingham/Searchy/Facades/Searchy.php
  2. 14
      src/TomLingham/Searchy/Interfaces/MatcherInterface.php
  3. 13
      src/TomLingham/Searchy/Interfaces/SearchDriverInterface.php
  4. 32
      src/TomLingham/Searchy/Matchers/AcronymMatcher.php
  5. 33
      src/TomLingham/Searchy/Matchers/BaseMatcher.php
  6. 46
      src/TomLingham/Searchy/Matchers/ConsecutiveCharactersMatcher.php
  7. 24
      src/TomLingham/Searchy/Matchers/ExactMatcher.php
  8. 32
      src/TomLingham/Searchy/Matchers/InStringMatcher.php
  9. 33
      src/TomLingham/Searchy/Matchers/LevenshteinMatcher.php
  10. 32
      src/TomLingham/Searchy/Matchers/StartOfStringMatcher.php
  11. 32
      src/TomLingham/Searchy/Matchers/StartOfWordsMatcher.php
  12. 39
      src/TomLingham/Searchy/Matchers/StudlyCaseMatcher.php
  13. 34
      src/TomLingham/Searchy/Matchers/TimesInStringMatcher.php
  14. 80
      src/TomLingham/Searchy/SearchBuilder.php
  15. 110
      src/TomLingham/Searchy/SearchDrivers/BaseSearchDriver.php
  16. 19
      src/TomLingham/Searchy/SearchDrivers/FuzzySearchDriver.php
  17. 36
      src/TomLingham/Searchy/SearchDrivers/LevenshteinSearchDriver.php
  18. 14
      src/TomLingham/Searchy/SearchDrivers/SimpleSearchDriver.php
  19. 5
      src/TomLingham/Searchy/SearchyServiceProvider.php
  20. 32
      src/config/config.php

24
src/TomLingham/Searchy/Facades/Searchy.php

@ -0,0 +1,24 @@
<?php namespace TomLingham\Searchy\Facades;
use TomLingham\Searchy\SearchyServiceProvider;
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()
{
if (!static::$app) {
static::$app = SearchyServiceProvider::make();
}
return 'searchy';
}
}

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

@ -0,0 +1,14 @@
<?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 );
}

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

@ -0,0 +1,13 @@
<?php namespace TomLingham\Searchy\Interfaces;
interface SearchDriverInterface
{
/**
* Execute the query on the Driver
*
* @param $searchString
* @return mixed
*/
public function query( $searchString );
}

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

@ -0,0 +1,32 @@
<?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';
/**
* @var int
*/
protected $multiplier = 42;
/**
* @param $searchString
* @return mixed|string
*/
public function formatSearchString( $searchString ) {
return implode( '% ', str_split(strtoupper( $searchString ))) . '%';
}
}

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

@ -0,0 +1,33 @@
<?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)";
}
}

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

@ -0,0 +1,46 @@
<?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';
/**
* @var int
*/
protected $multiplier = 40;
/**
* @param $searchString
* @return string
*/
public function formatSearchString( $searchString ) {
return '%'.implode('%', str_split( $searchString )).'%';
}
/**
* @param $column
* @param $rawString
* @return mixed|string
*/
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)";
return $query;
}
}

24
src/TomLingham/Searchy/Matchers/ExactMatcher.php

@ -0,0 +1,24 @@
<?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 = '=';
/**
* @var int
*/
protected $multiplier = 100;
}

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

@ -0,0 +1,32 @@
<?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';
/**
* @var int
*/
protected $multiplier = 30;
/**
* @param $searchString
* @return string
*/
public function formatSearchString( $searchString ){
return "%$searchString%";
}
}

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

@ -0,0 +1,33 @@
<?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
{
private $sensitivity;
public function setSensitivity( $sensitivity )
{
$this->sensitivity = $sensitivity;
}
/**
* @param $column
* @param $searchString
* @return mixed|string
*/
public function buildQueryString( $column, $searchString ){
return "levenshtein($column, '$searchString', {$this->sensitivity})";
}
}

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

@ -0,0 +1,32 @@
<?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';
/**
* @var int
*/
protected $multiplier = 50;
/**
* @param $searchString
* @return string
*/
public function formatSearchString( $searchString ) {
return "$searchString%";
}
}

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

@ -0,0 +1,32 @@
<?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';
/**
* @var int
*/
protected $multiplier = 35;
/**
* @param $searchString
* @return string
*/
public function formatSearchString( $searchString ) {
return implode('% ', explode(' ', $searchString)) . '%';
}
}

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

@ -0,0 +1,39 @@
<?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';
/**
* @var int
*/
protected $multiplier = 32;
/**
* @param $searchString
* @return string
*/
public function formatSearchString( $searchString ) {
return implode( '%', str_split(strtoupper( $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;
}
}

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

@ -0,0 +1,34 @@
<?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
{
/**
* @var int
*/
protected $multiplier = 8;
/**
* @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;
}
}

80
src/TomLingham/Searchy/SearchBuilder.php

@ -0,0 +1,80 @@
<?php namespace TomLingham\Searchy;
use Illuminate\Support\Facades\Config;
use TomLingham\Searchy\SearchDrivers\FuzzySearchDriver;
/**
* @property mixed driverName
*/
class SearchBuilder {
private $table;
private $searchFields;
private $driverName;
/**
* @param $table
* @return $this
*/
public function search( $table )
{
$this->table = $table;
return $this;
}
/**
* @return FuzzySearchDriver
*/
public function fields( /* $fields */ )
{
$searchFields = func_get_args();
$this->searchFields = $searchFields;
return $this->makeDriver();
}
/**
* @param $driverName
* @return $this
*/
public function driver( $driverName )
{
$this->driverName = $driverName;
return $this;
}
/**
* @param $table
* @param $fields
* @return mixed
*/
public function __call( $table, $fields )
{
return call_user_func_array([$this->search( $table ), 'fields'], $fields);
}
/**
* @return mixed
*/
private function makeDriver()
{
if (! $this->driverName){
$driverName = \Config::get('searchy::default');
} else {
$driverName = $this->driverName;
}
$driverMap = \Config::get("searchy::drivers.$driverName");
return new $driverMap['class']( $this->table, $this->searchFields );
}
}

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

@ -0,0 +1,110 @@
<?php namespace TomLingham\Searchy\SearchDrivers;
use TomLingham\Searchy\Interfaces\SearchDriverInterface;
/**
* @property mixed methods
* @property mixed matchers
*/
abstract class BaseSearchDriver implements SearchDriverInterface {
/**
* @var array
*/
protected $fields;
/**
* @var
*/
protected $searchString;
/**
* @var null
*/
protected $table;
/**
* @param null $table
* @param array $fields
*/
public function __construct( $table = null, $fields = [] ){
$this->fields = $fields;
$this->table = $table;
}
/**
* @param $searchString
* @return \Illuminate\Database\Query\Builder|mixed|static
* @throws \Whoops\Example\Exception
*/
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 ))
->orderBy(\Config::get('searchy::fieldName'), 'desc')
->having(\Config::get('searchy::fieldName'),'>', 0);
dd($results->toSql());
return $results;
}
/**
* @param array $fields
* @return array|\Illuminate\Database\Query\Expression
*/
protected function buildSelectQuery( array $fields ){
$query = [];
foreach ($fields as $field) {
$query[] = $this->buildSelectCriteria( $field );
}
$query = \DB::raw(implode(' + ', $query) . ' AS ' . \Config::get('searchy::fieldName'));
return $query;
}
/**
* @param null $field
* @return string
*/
protected function buildSelectCriteria( $field = null ) {
$criteria = [];
foreach( $this->matchers as $matcher => $multiplier){
$criteria[] = $this->makeMatcher( $field, $matcher, $multiplier );
}
return implode(' + ', $criteria);
}
/**
* @param $field
* @param $matcherClass
* @param $multiplier
* @return mixed
*/
protected function makeMatcher( $field, $matcherClass, $multiplier )
{
$matcher = new $matcherClass( $multiplier );
return $matcher->buildQueryString( $field, $this->searchString );
}
/**
* @param $searchString
* @return mixed
*/
private function sanitize( $searchString ) {
return preg_replace(\Config::get('searchy::sanitizeRegEx'), '', $searchString);
}
}

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

@ -0,0 +1,19 @@
<?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,
];
}

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

@ -0,0 +1,36 @@
<?php namespace TomLingham\Searchy\SearchDrivers;
class LevenshteinSearchDriver extends BaseSearchDriver {
private $sensitivity = 10;
protected $matchers = [
'TomLingham\Searchy\Matchers\LevenshteinMatcher' => 100
];
public function setSensitivity( $sensitivity ){
$this->sensitivity = $sensitivity;
return $this;
}
/**
* @param $column
* @param $matcherClass
* @param $multiplier
* @return mixed
*/
protected function makeMatcher( $column, $matcherClass, $multiplier )
{
$matcher = new $matcherClass( $multiplier );
$matcher->setSensitivity( $this->sensitivity );
return $matcher->buildQueryString( $column, $this->searchString );
}
}

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

@ -0,0 +1,14 @@
<?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,
];
}

5
src/TomLingham/Searchy/SearchyServiceProvider.php

@ -18,7 +18,10 @@ class SearchyServiceProvider extends ServiceProvider {
*/ */
public function register() public function register()
{ {
//
$this->app->bindShared('searchy', function($app)
{
return new SearchBuilder();
});
} }
/** /**

32
src/config/config.php

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