Skip to content

Commit

Permalink
Complete rewrite and ability to join
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolaslopezj committed Oct 8, 2014
1 parent 50eeac5 commit 50b00e5
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 86 deletions.
88 changes: 55 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Searchable, a search trait for Laravel

Searchable is a trait for Laravel 4.2+ that adds a simple search function to Eloquent Models.

Searchable allows you to perform searches in a table giving priorities to each field.
Searchable allows you to perform searches in a table giving priorities to each field for the table and it's relations.

This is not optimized for big searchs, but sometimes you just need to make it simple.

Expand All @@ -12,7 +12,7 @@ This is not optimized for big searchs, but sometimes you just need to make it si
Simply add the package to your `composer.json` file and run `composer update`.

```
"nicolaslopezj/searchable": "1.0.*"
"nicolaslopezj/searchable": "1.1.*"
```

## Usage
Expand All @@ -32,11 +32,23 @@ class User extends \Eloquent
* @var array
*/
protected $searchable = [
['column' => 'first_name', 'relevance' => 10],
['column' => 'last_name', 'relevance' => 10],
['column' => 'bio', 'relevance' => 2],
['column' => 'email', 'relevance' => 5],
'columns' => [
'first_name' => 10,
'last_name' => 10,
'bio' => 2,
'email' => 5,
'posts.title' => 2,
'posts.body' => 1,
],
'joins' => [
'posts' => ['users.id','posts.user_id'],
],
];

public function posts() {
return $this->hasMany('Post');
}

}
```

Expand All @@ -47,8 +59,9 @@ Now you can search your model.
$users = User::search($query)->get();

// Search and get relations
// It will not get the relations if you don't do this
$users = User::search($query)
->with('photos')
->with('posts')
->get();
```

Expand Down Expand Up @@ -98,12 +111,21 @@ class Post extends \Eloquent {
use SearchableTrait;

protected $fillable = ['title', 'resume', 'body', 'tags'];

//searchable attributes

/**
* Searchable rules.
*
* @var array
*/
protected $searchable = [
['column' => 'title', 'relevance' => 20],
['column' => 'resume', 'relevance' => 5],
['column' => 'body', 'relevance' => 2],
'columns' => [
'title' => 20,
'resume' => 5,
'body' => 2,
],
'joins' => [

],
];

}
Expand All @@ -117,33 +139,33 @@ $search = Post::search('Sed neque labore')->get();
####Result:
```sql
SELECT *,
-- For each column you specify makes 3 "ifs" containing
-- each word of the search input and adds relevace to
-- the row
-- For each column you specify makes 3 "ifs" containing
-- each word of the search input and adds relevace to
-- the row

-- The first checks if the column is equal to the word,
-- if then it adds relevace*15
if(title = 'Sed' || title = 'neque' || title = 'labore', 300, 0) +
-- The first checks if the column is equal to the word,
-- if then it adds relevace*15
if(title = 'Sed' || title = 'neque' || title = 'labore', 300, 0) +

-- The second checks if the column starts with the word,
-- if then it adds relevace*5
if(title LIKE 'Sed%' || title LIKE 'neque%' || title LIKE 'labore%', 100, 0) +
-- The second checks if the column starts with the word,
-- if then it adds relevace*5
if(title LIKE 'Sed%' || title LIKE 'neque%' || title LIKE 'labore%', 100, 0) +

-- The third checks if the column contains the word,
-- if then it adds relevace*1
if(title LIKE '%Sed%' || title LIKE '%neque%' || title LIKE '%labore%', 20, 0) +
-- The third checks if the column contains the word,
-- if then it adds relevace*1
if(title LIKE '%Sed%' || title LIKE '%neque%' || title LIKE '%labore%', 20, 0) +

-- Repeats with each column
if(resume = 'Sed' || resume = 'neque' || resume = 'labore', 75, 0) +
if(resume LIKE 'Sed%' || resume LIKE 'neque%' || resume LIKE 'labore%', 25, 0) +
if(resume LIKE '%Sed%' || resume LIKE '%neque%' || resume LIKE '%labore%', 5, 0) +
-- Repeats with each column
if(resume = 'Sed' || resume = 'neque' || resume = 'labore', 75, 0) +
if(resume LIKE 'Sed%' || resume LIKE 'neque%' || resume LIKE 'labore%', 25, 0) +
if(resume LIKE '%Sed%' || resume LIKE '%neque%' || resume LIKE '%labore%', 5, 0) +

if(body = 'Sed' || body = 'neque' || body = 'labore', 30, 0) +
if(body LIKE 'Sed%' || body LIKE 'neque%' || body LIKE 'labore%', 10, 0) +
if(body LIKE '%Sed%' || body LIKE '%neque%' || body LIKE '%labore%', 2, 0) +
if(body = 'Sed' || body = 'neque' || body = 'labore', 30, 0) +
if(body LIKE 'Sed%' || body LIKE 'neque%' || body LIKE 'labore%', 10, 0) +
if(body LIKE '%Sed%' || body LIKE '%neque%' || body LIKE '%labore%', 2, 0) +

AS relevance
FROM `posts`
AS relevance
FROM `posts`

-- Selects only the rows that have more than
-- the sum of all attributes relevances and divided by 4
Expand Down
17 changes: 14 additions & 3 deletions src/Nicolaslopezj/Searchable/DBHelper.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
<?php namespace Nicolaslopezj\Searchable;

/**
* Class DBHelper
* @package Nicolaslopezj\Searchable
*/

use DB;

class DBHelper {
//get the count of rows of a query
//only tested with the search

/**
* Returns the count of rows in for the query
* @param $db_query
* @return int
*/
public static function getQueryCount($db_query)
{
$query = $db_query['query'];
Expand All @@ -16,7 +27,7 @@ public static function getQueryCount($db_query)
$count_query = 'select count(*) as count from (' . $query . ') as results';

//execute the query and get the result
$count = \DB::select(\DB::raw($count_query), $bindings)[0]->count;
$count = DB::select(DB::raw($count_query), $bindings)[0]->count;

return intval($count);
}
Expand Down
153 changes: 103 additions & 50 deletions src/Nicolaslopezj/Searchable/SearchableTrait.php
Original file line number Diff line number Diff line change
@@ -1,72 +1,125 @@
<?php namespace Nicolaslopezj\Searchable;

/**
* Trait SearchableTrait
* @package Nicolaslopezj\Searchable
*/
trait SearchableTrait
{
public function scopeSearch($query, $search)
{
//if no search query just return the simple query
if (!$search) return $query;

$selects = [];
/**
* Makes the search process
* @param $query
* @param $search
* @return mixed
*/
public function scopeSearch($query, $search) {

//here we add the if's functions to the selects array
//we separate each word
$words = explode(' ', $search);
//we need to count the sum of relevance to take only the good results
$relevance_count = 0;
for ($i = 0; $i < count($this->searchable); $i++) {
$field = $this->searchable[$i];
$relevance_count += $field['relevance'];
//for each word and column we make a like and a =, the = has more relevance
//first with the =
$equal_fields = [];
foreach ($words as $word) {
$equal_fields[] = $field['column'] . " = '" . $word . "'";
}

//we join each =
$equal_fields = join(' || ', $equal_fields);
if (!$search) {
return $query;
}

//we put the ='s into the if function and set the relevance
//if it match it adds the relevance number to the relevance column
$selects[] = 'if(' . $equal_fields . ', ' . $field['relevance'] * 15 . ', 0)';
$relevance_count = 0;
$words = explode(' ', $search);

//then the like
$like_fields = [];
foreach ($words as $word) {
$like_fields[] = $field['column'] . " LIKE '" . $word . "%'";
$selects = [];
foreach ($this->getColumns() as $column => $relevance) {
$relevance_count += $relevance;
$queries = $this->getSearchQueriesForColumn($column, $relevance, $words);
foreach ($queries as $select) {
$selects[] = $select;
}
}

//we join each like
$like_fields = join(' || ', $like_fields);
$this->addSelectsToQuery($query, $selects);
$this->filterQueryWithRelevace($query, ($relevance_count / 4));

//we put the like's into the if function and set the relevance
//if it match it adds the relevance number to the relevance column
$selects[] = 'if(' . $like_fields . ', ' . $field['relevance'] * 5 . ', 0)';
$this->makeJoins($query);

//and then the other like
$like_other_fields = [];
foreach ($words as $word) {
$like_other_fields[] = $field['column'] . " LIKE '%" . $word . "%'";
}
return $query;
}

//we join each like
$like_other_fields = join(' || ', $like_other_fields);
/**
* Returns the search columns
* @return array
*/
protected function getColumns() {
return $this->searchable['columns'];
}

/**
* Returns the tables that has to join
* @return array
*/
protected function getJoins() {
return $this->searchable['joins'];
}

//we put the like's into the if function and set the relevance
//if it match it adds the relevance number to the relevance column
$selects[] = 'if(' . $like_other_fields . ', ' . $field['relevance'] . ', 0)';
/**
* Adds the join sql to the query
* @param $query
*/
protected function makeJoins(&$query) {
foreach ($this->getJoins() as $table => $keys) {
$query->join($table, $keys[0], '=', $keys[1]);
}
}

//we sum each match to see the relevance
/**
* Puts all the select clauses to the main query
* @param $query
* @param $selects
*/
protected function addSelectsToQuery(&$query, $selects) {
$selects = \DB::raw(join(' + ', $selects) . ' as relevance');
$query->select(['*', $selects]);
$query->select([$this->getTable() . '.*', $selects]);
}

//this make that we only select the matching rows
$query->havingRaw('relevance > ' . ($relevance_count / 4));
//order rows by relevance
/**
* Adds relevance filter to the query
* @param $query
* @param $relevance_count
*/
protected function filterQueryWithRelevace(&$query, $relevance_count) {
$query->havingRaw('relevance > ' . $relevance_count);
$query->orderBy('relevance', 'desc');
}

return $query;
/**
* Returns the search queries for the specified column
* @param $column
* @param $relevance
* @param $words
* @return array
*/
protected function getSearchQueriesForColumn($column, $relevance, $words) {
$queries = [];
$queries[] = $this->getSearchQuery($column, $relevance, $words, '=', 15);
$queries[] = $this->getSearchQuery($column, $relevance, $words, 'LIKE', 5, '', '%');
$queries[] = $this->getSearchQuery($column, $relevance, $words, 'LIKE', 1, '%', '%');
return $queries;
}

/**
* Returns the sql string for the parameters
* @param $column
* @param $relevance
* @param $words
* @param $compare
* @param $relevance_multiplier
* @param string $pre_word
* @param string $post_word
* @return string
*/
protected function getSearchQuery($column, $relevance, $words, $compare, $relevance_multiplier, $pre_word = '', $post_word = '') {
$fields = [];
foreach ($words as $word) {
$fields[] = $column . " " . $compare . " '" . $pre_word . $word . $post_word . "'";
}

$fields = join(' || ', $fields);

return 'if(' . $fields . ', ' . $relevance * $relevance_multiplier . ', 0)';
}

}

0 comments on commit 50b00e5

Please sign in to comment.