diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..880a088 --- /dev/null +++ b/.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 diff --git a/composer.json b/composer.json index fa89a09..9fe5763 100755 --- a/composer.json +++ b/composer.json @@ -17,8 +17,8 @@ "phpspec/phpspec": "~2.0" }, "autoload": { - "psr-0": { - "TomLingham": "src/" + "psr-4": { + "TomLingham\\Searchy\\": "src/" } }, "minimum-stability": "stable" diff --git a/config/searchy.php b/config/searchy.php new file mode 100755 index 0000000..ccbf005 --- /dev/null +++ b/config/searchy.php @@ -0,0 +1,25 @@ + 'fuzzy', + + 'fieldName' => 'relevance', + + 'drivers' => [ + + 'fuzzy' => [ + 'class' => 'TomLingham\Searchy\SearchDrivers\FuzzySearchDriver', + ], + + 'simple' => [ + 'class' => 'TomLingham\Searchy\SearchDrivers\SimpleSearchDriver', + ], + + 'levenshtein' => [ + 'class' => 'TomLingham\Searchy\SearchDrivers\LevenshteinSearchDriver', + ], + + ], + +]; diff --git a/src/Facades/Searchy.php b/src/Facades/Searchy.php new file mode 100755 index 0000000..7213dde --- /dev/null +++ b/src/Facades/Searchy.php @@ -0,0 +1,21 @@ +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)"; + } +} diff --git a/src/Matchers/ConsecutiveCharactersMatcher.php b/src/Matchers/ConsecutiveCharactersMatcher.php new file mode 100755 index 0000000..8aabf76 --- /dev/null +++ b/src/Matchers/ConsecutiveCharactersMatcher.php @@ -0,0 +1,44 @@ +formatSearchString($rawString); + + return "IF(REPLACE($column, '\.', '') {$this->operator} '$searchString', ROUND({$this->multiplier} * (CHAR_LENGTH( '$rawString' ) / CHAR_LENGTH( REPLACE($column, ' ', '') ))), 0)"; + } +} diff --git a/src/TomLingham/Searchy/Matchers/ExactMatcher.php b/src/Matchers/ExactMatcher.php similarity index 68% rename from src/TomLingham/Searchy/Matchers/ExactMatcher.php rename to src/Matchers/ExactMatcher.php index 19f6398..bd5f56e 100755 --- a/src/TomLingham/Searchy/Matchers/ExactMatcher.php +++ b/src/Matchers/ExactMatcher.php @@ -1,20 +1,18 @@ -operator} '{$this->formatSearchString($searchString)}', {$this->multiplier}, 0)"; + } +} diff --git a/src/TomLingham/Searchy/Matchers/TimesInStringMatcher.php b/src/Matchers/TimesInStringMatcher.php similarity index 61% rename from src/TomLingham/Searchy/Matchers/TimesInStringMatcher.php rename to src/Matchers/TimesInStringMatcher.php index a8575df..0530e9b 100755 --- a/src/TomLingham/Searchy/Matchers/TimesInStringMatcher.php +++ b/src/Matchers/TimesInStringMatcher.php @@ -1,29 +1,29 @@ -multiplier} * ROUND (( - CHAR_LENGTH($column) - CHAR_LENGTH( REPLACE ( LOWER($column), lower('$searchString'), '')) - ) / LENGTH('$searchString'))"; - - return $query; - } -} \ No newline at end of file +multiplier} * ROUND (( + CHAR_LENGTH($column) - CHAR_LENGTH( REPLACE ( LOWER($column), lower('$searchString'), '')) + ) / LENGTH('$searchString'))"; + + return $query; + } +} diff --git a/src/SearchBuilder.php b/src/SearchBuilder.php new file mode 100755 index 0000000..217c3b9 --- /dev/null +++ b/src/SearchBuilder.php @@ -0,0 +1,111 @@ +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; + } +} diff --git a/src/SearchDrivers/BaseSearchDriver.php b/src/SearchDrivers/BaseSearchDriver.php new file mode 100755 index 0000000..32a6d64 --- /dev/null +++ b/src/SearchDrivers/BaseSearchDriver.php @@ -0,0 +1,154 @@ +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); + } +} diff --git a/src/SearchDrivers/FuzzySearchDriver.php b/src/SearchDrivers/FuzzySearchDriver.php new file mode 100755 index 0000000..bc61c9e --- /dev/null +++ b/src/SearchDrivers/FuzzySearchDriver.php @@ -0,0 +1,20 @@ + 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, + ]; +} diff --git a/src/SearchDrivers/LevenshteinSearchDriver.php b/src/SearchDrivers/LevenshteinSearchDriver.php new file mode 100755 index 0000000..a90ff46 --- /dev/null +++ b/src/SearchDrivers/LevenshteinSearchDriver.php @@ -0,0 +1,13 @@ + 100, + ]; +} diff --git a/src/SearchDrivers/SimpleSearchDriver.php b/src/SearchDrivers/SimpleSearchDriver.php new file mode 100755 index 0000000..b0c762f --- /dev/null +++ b/src/SearchDrivers/SimpleSearchDriver.php @@ -0,0 +1,15 @@ + 100, + 'TomLingham\Searchy\Matchers\StartOfStringMatcher' => 50, + 'TomLingham\Searchy\Matchers\InStringMatcher' => 30, + ]; +} diff --git a/src/SearchyServiceProvider.php b/src/SearchyServiceProvider.php new file mode 100755 index 0000000..3c68713 --- /dev/null +++ b/src/SearchyServiceProvider.php @@ -0,0 +1,56 @@ +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(); + } +} diff --git a/src/TomLingham/Searchy/Facades/Searchy.php b/src/TomLingham/Searchy/Facades/Searchy.php deleted file mode 100755 index a6f9c4d..0000000 --- a/src/TomLingham/Searchy/Facades/Searchy.php +++ /dev/null @@ -1,19 +0,0 @@ -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)"; - } - -} \ No newline at end of file diff --git a/src/TomLingham/Searchy/Matchers/ConsecutiveCharactersMatcher.php b/src/TomLingham/Searchy/Matchers/ConsecutiveCharactersMatcher.php deleted file mode 100755 index 82937f9..0000000 --- a/src/TomLingham/Searchy/Matchers/ConsecutiveCharactersMatcher.php +++ /dev/null @@ -1,40 +0,0 @@ -formatSearchString( $rawString ); - - return "IF(REPLACE($column, '\.', '') {$this->operator} '$searchString', ROUND({$this->multiplier} * (CHAR_LENGTH( '$rawString' ) / CHAR_LENGTH( REPLACE($column, ' ', '') ))), 0)"; - } -} \ No newline at end of file diff --git a/src/TomLingham/Searchy/Matchers/InStringMatcher.php b/src/TomLingham/Searchy/Matchers/InStringMatcher.php deleted file mode 100755 index 0ea479e..0000000 --- a/src/TomLingham/Searchy/Matchers/InStringMatcher.php +++ /dev/null @@ -1,28 +0,0 @@ -operator} '{$this->formatSearchString($searchString)}', {$this->multiplier}, 0)"; - } -} \ No newline at end of file diff --git a/src/TomLingham/Searchy/SearchBuilder.php b/src/TomLingham/Searchy/SearchBuilder.php deleted file mode 100755 index 95db821..0000000 --- a/src/TomLingham/Searchy/SearchBuilder.php +++ /dev/null @@ -1,110 +0,0 @@ -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; - - } - -} \ No newline at end of file diff --git a/src/TomLingham/Searchy/SearchDrivers/BaseSearchDriver.php b/src/TomLingham/Searchy/SearchDrivers/BaseSearchDriver.php deleted file mode 100755 index 3cb177f..0000000 --- a/src/TomLingham/Searchy/SearchDrivers/BaseSearchDriver.php +++ /dev/null @@ -1,147 +0,0 @@ -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 ); - } -} \ No newline at end of file diff --git a/src/TomLingham/Searchy/SearchDrivers/FuzzySearchDriver.php b/src/TomLingham/Searchy/SearchDrivers/FuzzySearchDriver.php deleted file mode 100755 index 86219af..0000000 --- a/src/TomLingham/Searchy/SearchDrivers/FuzzySearchDriver.php +++ /dev/null @@ -1,19 +0,0 @@ - 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, - ]; - -} \ No newline at end of file diff --git a/src/TomLingham/Searchy/SearchDrivers/LevenshteinSearchDriver.php b/src/TomLingham/Searchy/SearchDrivers/LevenshteinSearchDriver.php deleted file mode 100755 index 439a2fa..0000000 --- a/src/TomLingham/Searchy/SearchDrivers/LevenshteinSearchDriver.php +++ /dev/null @@ -1,12 +0,0 @@ - 100 - ]; - -} \ No newline at end of file diff --git a/src/TomLingham/Searchy/SearchDrivers/SimpleSearchDriver.php b/src/TomLingham/Searchy/SearchDrivers/SimpleSearchDriver.php deleted file mode 100755 index b5ad99f..0000000 --- a/src/TomLingham/Searchy/SearchDrivers/SimpleSearchDriver.php +++ /dev/null @@ -1,14 +0,0 @@ - 100, - 'TomLingham\Searchy\Matchers\StartOfStringMatcher' => 50, - 'TomLingham\Searchy\Matchers\InStringMatcher' => 30, - ]; - -} \ No newline at end of file diff --git a/src/TomLingham/Searchy/SearchyServiceProvider.php b/src/TomLingham/Searchy/SearchyServiceProvider.php deleted file mode 100755 index 1ee20d4..0000000 --- a/src/TomLingham/Searchy/SearchyServiceProvider.php +++ /dev/null @@ -1,52 +0,0 @@ -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(); - } - -} diff --git a/src/config/config.php b/src/config/config.php deleted file mode 100755 index 0745005..0000000 --- a/src/config/config.php +++ /dev/null @@ -1,28 +0,0 @@ - 'fuzzy', - - 'fieldName' => 'relevance', - - 'drivers' => [ - - 'fuzzy' => [ - 'class' => 'TomLingham\Searchy\SearchDrivers\FuzzySearchDriver' - ], - - 'simple' => [ - 'class' => 'TomLingham\Searchy\SearchDrivers\SimpleSearchDriver' - ], - - 'levenshtein' => [ - 'class' => 'TomLingham\Searchy\SearchDrivers\LevenshteinSearchDriver' - ], - - - ], - - - -]; \ No newline at end of file