Update v1.0.6
This commit is contained in:
21
vendor/nicolaslopezj/searchable/LICENSE.txt
vendored
21
vendor/nicolaslopezj/searchable/LICENSE.txt
vendored
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Nicolas Lopez
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
196
vendor/nicolaslopezj/searchable/README.md
vendored
196
vendor/nicolaslopezj/searchable/README.md
vendored
@@ -1,196 +0,0 @@
|
||||
Searchable, a search trait for Laravel
|
||||
==========================================
|
||||
|
||||
Searchable is a trait for Laravel 4.2+ and Laravel 5.0 that adds a simple search function to Eloquent Models.
|
||||
|
||||
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 searches, but sometimes you just need to make it simple (Although it is not slow).
|
||||
|
||||
# Installation
|
||||
|
||||
Simply add the package to your `composer.json` file and run `composer update`.
|
||||
|
||||
```
|
||||
"nicolaslopezj/searchable": "1.*"
|
||||
```
|
||||
|
||||
# Usage
|
||||
|
||||
Add the trait to your model and your search rules.
|
||||
|
||||
```php
|
||||
use Nicolaslopezj\Searchable\SearchableTrait;
|
||||
|
||||
class User extends \Eloquent
|
||||
{
|
||||
use SearchableTrait;
|
||||
|
||||
/**
|
||||
* Searchable rules.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchable = [
|
||||
'columns' => [
|
||||
'users.first_name' => 10,
|
||||
'users.last_name' => 10,
|
||||
'users.bio' => 2,
|
||||
'users.email' => 5,
|
||||
'posts.title' => 2,
|
||||
'posts.body' => 1,
|
||||
],
|
||||
'joins' => [
|
||||
'posts' => ['users.id','posts.user_id'],
|
||||
],
|
||||
];
|
||||
|
||||
public function posts()
|
||||
{
|
||||
return $this->hasMany('Post');
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Now you can search your model.
|
||||
|
||||
```php
|
||||
// Simple search
|
||||
$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('posts')
|
||||
->get();
|
||||
```
|
||||
|
||||
|
||||
## Search Paginated
|
||||
|
||||
As easy as laravel default queries
|
||||
|
||||
```php
|
||||
// Search with relations and paginate
|
||||
$users = User::search($query)
|
||||
->with('posts')
|
||||
->paginate(20);
|
||||
```
|
||||
|
||||
## Mix queries
|
||||
|
||||
Search method is compatible with any eloquent method. You can do things like this:
|
||||
|
||||
```php
|
||||
// Search only active users
|
||||
$users = User::where('status', 'active')
|
||||
->search($query)
|
||||
->paginate(20);
|
||||
```
|
||||
|
||||
## Custom Threshold
|
||||
|
||||
The default threshold for accepted relevance is the sum of all attribute relevance divided by 4.
|
||||
To change this value you can pass in a second parameter to search() like so:
|
||||
|
||||
```php
|
||||
// Search with lower relevance threshold
|
||||
$users = User::where('status', 'active')
|
||||
->search($query, 0)
|
||||
->paginate(20);
|
||||
```
|
||||
|
||||
The above, will return all users in order of relevance.
|
||||
|
||||
# How does it work?
|
||||
|
||||
Searchable builds a query that search through your model using Laravel's Eloquent.
|
||||
Here is an example query
|
||||
|
||||
####Eloquent Model:
|
||||
```php
|
||||
use Nicolaslopezj\Searchable\SearchableTrait;
|
||||
|
||||
class User extends \Eloquent
|
||||
{
|
||||
use SearchableTrait;
|
||||
|
||||
/**
|
||||
* Searchable rules.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchable = [
|
||||
'columns' => [
|
||||
'first_name' => 10,
|
||||
'last_name' => 10,
|
||||
'bio' => 2,
|
||||
'email' => 5,
|
||||
],
|
||||
];
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
####Search:
|
||||
```php
|
||||
$search = User::search('Sed neque labore', null, true)->get();
|
||||
```
|
||||
|
||||
####Result:
|
||||
```sql
|
||||
select `users`.*,
|
||||
|
||||
-- If third parameter is set as true, it will check if the column starts with the search
|
||||
-- if then it adds relevance * 30
|
||||
-- this ensures that relevant results will be at top
|
||||
(case when first_name LIKE 'Sed neque labore%' then 300 else 0 end) +
|
||||
|
||||
-- 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 relevance * 15
|
||||
(case when first_name LIKE 'Sed' || first_name LIKE 'neque' || first_name LIKE 'labore' then 150 else 0 end) +
|
||||
|
||||
-- The second checks if the column starts with the word,
|
||||
-- if then it adds relevance * 5
|
||||
(case when first_name LIKE 'Sed%' || first_name LIKE 'neque%' || first_name LIKE 'labore%' then 50 else 0 end) +
|
||||
|
||||
-- The third checks if the column contains the word,
|
||||
-- if then it adds relevance * 1
|
||||
(case when first_name LIKE '%Sed%' || first_name LIKE '%neque%' || first_name LIKE '%labore%' then 10 else 0 end) +
|
||||
|
||||
-- Repeats with each column
|
||||
(case when last_name LIKE 'Sed' || last_name LIKE 'neque' || last_name LIKE 'labore' then 150 else 0 end) +
|
||||
(case when last_name LIKE 'Sed%' || last_name LIKE 'neque%' || last_name LIKE 'labore%' then 50 else 0 end) +
|
||||
(case when last_name LIKE '%Sed%' || last_name LIKE '%neque%' || last_name LIKE '%labore%' then 10 else 0 end) +
|
||||
|
||||
(case when bio LIKE 'Sed' || bio LIKE 'neque' || bio LIKE 'labore' then 30 else 0 end) +
|
||||
(case when bio LIKE 'Sed%' || bio LIKE 'neque%' || bio LIKE 'labore%' then 10 else 0 end) +
|
||||
(case when bio LIKE '%Sed%' || bio LIKE '%neque%' || bio LIKE '%labore%' then 2 else 0 end) +
|
||||
|
||||
(case when email LIKE 'Sed' || email LIKE 'neque' || email LIKE 'labore' then 75 else 0 end) +
|
||||
(case when email LIKE 'Sed%' || email LIKE 'neque%' || email LIKE 'labore%' then 25 else 0 end) +
|
||||
(case when email LIKE '%Sed%' || email LIKE '%neque%' || email LIKE '%labore%' then 5 else 0 end)
|
||||
|
||||
as relevance
|
||||
from `users`
|
||||
group by `id`
|
||||
|
||||
-- Selects only the rows that have more than
|
||||
-- the sum of all attributes relevances and divided by 4
|
||||
-- Ej: (20 + 5 + 2) / 4 = 6.75
|
||||
having relevance > 6.75
|
||||
|
||||
-- Orders the results by relevance
|
||||
order by `relevance` desc
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Anyone is welcome to contribute. Fork, make your changes, and then submit a pull request.
|
||||
|
||||
[](https://gratipay.com/nicolaslopezj/)
|
23
vendor/nicolaslopezj/searchable/composer.json
vendored
23
vendor/nicolaslopezj/searchable/composer.json
vendored
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"name": "nicolaslopezj/searchable",
|
||||
"description": "Eloquent model search trait.",
|
||||
"keywords": ["laravel", "eloquent", "search", "searchable", "database", "model"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Lopez",
|
||||
"email": "nicolaslopezj@me.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.4.0",
|
||||
"illuminate/database": "4.2.x|~5.0",
|
||||
"ext-mbstring": "*"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Nicolaslopezj\\Searchable\\": "src/"
|
||||
}
|
||||
},
|
||||
"minimum-stability": "stable"
|
||||
}
|
@@ -1,329 +0,0 @@
|
||||
<?php namespace Nicolaslopezj\Searchable;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Query\Expression;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Trait SearchableTrait
|
||||
* @package Nicolaslopezj\Searchable
|
||||
* @property array $searchable
|
||||
* @property string $table
|
||||
* @property string $primaryKey
|
||||
* @method string getTable()
|
||||
*/
|
||||
trait SearchableTrait
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $search_bindings = [];
|
||||
|
||||
/**
|
||||
* Creates the search scope.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $q
|
||||
* @param string $search
|
||||
* @param float|null $threshold
|
||||
* @param boolean $entireText
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeSearch(Builder $q, $search, $threshold = null, $entireText = false)
|
||||
{
|
||||
return $this->scopeSearchRestricted($q, $search, null, $threshold, $entireText);
|
||||
}
|
||||
|
||||
public function scopeSearchRestricted(Builder $q, $search, $restriction, $threshold = null, $entireText = false)
|
||||
{
|
||||
$query = clone $q;
|
||||
$query->select($this->getTable() . '.*');
|
||||
$this->makeJoins($query);
|
||||
|
||||
if ( ! $search)
|
||||
{
|
||||
return $q;
|
||||
}
|
||||
|
||||
$search = mb_strtolower(trim($search));
|
||||
$words = explode(' ', $search);
|
||||
|
||||
$selects = [];
|
||||
$this->search_bindings = [];
|
||||
$relevance_count = 0;
|
||||
|
||||
foreach ($this->getColumns() as $column => $relevance)
|
||||
{
|
||||
$relevance_count += $relevance;
|
||||
$queries = $this->getSearchQueriesForColumn($query, $column, $relevance, $words);
|
||||
|
||||
if ( $entireText === true )
|
||||
{
|
||||
$queries[] = $this->getSearchQuery($query, $column, $relevance, [$search], 30, '', '%');
|
||||
}
|
||||
|
||||
foreach ($queries as $select)
|
||||
{
|
||||
$selects[] = $select;
|
||||
}
|
||||
}
|
||||
|
||||
$this->addSelectsToQuery($query, $selects);
|
||||
|
||||
// Default the threshold if no value was passed.
|
||||
if (is_null($threshold)) {
|
||||
$threshold = $relevance_count / 4;
|
||||
}
|
||||
|
||||
$this->filterQueryWithRelevance($query, $selects, $threshold);
|
||||
|
||||
$this->makeGroupBy($query);
|
||||
|
||||
$this->addBindingsToQuery($query, $this->search_bindings);
|
||||
|
||||
if(is_callable($restriction)) {
|
||||
$query = $restriction($query);
|
||||
}
|
||||
|
||||
$this->mergeQueries($query, $q);
|
||||
|
||||
return $q;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns database driver Ex: mysql, pgsql, sqlite.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getDatabaseDriver() {
|
||||
$key = $this->connection ?: Config::get('database.default');
|
||||
return Config::get('database.connections.' . $key . '.driver');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the search columns.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getColumns()
|
||||
{
|
||||
if (array_key_exists('columns', $this->searchable)) {
|
||||
return $this->searchable['columns'];
|
||||
} else {
|
||||
return DB::connection()->getSchemaBuilder()->getColumnListing($this->table);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not to keep duplicates.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getGroupBy()
|
||||
{
|
||||
if (array_key_exists('groupBy', $this->searchable)) {
|
||||
return $this->searchable['groupBy'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the table columns.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTableColumns()
|
||||
{
|
||||
return $this->searchable['table_columns'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tables that are to be joined.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getJoins()
|
||||
{
|
||||
return array_get($this->searchable, 'joins', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the sql joins to the query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
*/
|
||||
protected function makeJoins(Builder $query)
|
||||
{
|
||||
foreach ($this->getJoins() as $table => $keys) {
|
||||
$query->leftJoin($table, function ($join) use ($keys) {
|
||||
$join->on($keys[0], '=', $keys[1]);
|
||||
if (array_key_exists(2, $keys) && array_key_exists(3, $keys)) {
|
||||
$join->where($keys[2], '=', $keys[3]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the query not repeat the results.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
*/
|
||||
protected function makeGroupBy(Builder $query)
|
||||
{
|
||||
if ($groupBy = $this->getGroupBy()) {
|
||||
$query->groupBy($groupBy);
|
||||
} else {
|
||||
$driver = $this->getDatabaseDriver();
|
||||
|
||||
if ($driver == 'sqlsrv') {
|
||||
$columns = $this->getTableColumns();
|
||||
} else {
|
||||
$columns = $this->getTable() . '.' .$this->primaryKey;
|
||||
}
|
||||
|
||||
$query->groupBy($columns);
|
||||
|
||||
$joins = array_keys(($this->getJoins()));
|
||||
|
||||
foreach ($this->getColumns() as $column => $relevance) {
|
||||
array_map(function ($join) use ($column, $query) {
|
||||
if (Str::contains($column, $join)) {
|
||||
$query->groupBy($column);
|
||||
}
|
||||
}, $joins);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts all the select clauses to the main query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param array $selects
|
||||
*/
|
||||
protected function addSelectsToQuery(Builder $query, array $selects)
|
||||
{
|
||||
$selects = new Expression('max(' . implode(' + ', $selects) . ') as relevance');
|
||||
$query->addSelect($selects);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the relevance filter to the query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param array $selects
|
||||
* @param float $relevance_count
|
||||
*/
|
||||
protected function filterQueryWithRelevance(Builder $query, array $selects, $relevance_count)
|
||||
{
|
||||
$comparator = $this->getDatabaseDriver() != 'mysql' ? implode(' + ', $selects) : 'relevance';
|
||||
|
||||
$relevance_count=number_format($relevance_count,2,'.','');
|
||||
|
||||
$query->havingRaw("$comparator > $relevance_count");
|
||||
$query->orderBy('relevance', 'desc');
|
||||
|
||||
// add bindings to postgres
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the search queries for the specified column.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param string $column
|
||||
* @param float $relevance
|
||||
* @param array $words
|
||||
* @return array
|
||||
*/
|
||||
protected function getSearchQueriesForColumn(Builder $query, $column, $relevance, array $words)
|
||||
{
|
||||
$queries = [];
|
||||
|
||||
$queries[] = $this->getSearchQuery($query, $column, $relevance, $words, 15);
|
||||
$queries[] = $this->getSearchQuery($query, $column, $relevance, $words, 5, '', '%');
|
||||
$queries[] = $this->getSearchQuery($query, $column, $relevance, $words, 1, '%', '%');
|
||||
|
||||
return $queries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sql string for the given parameters.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param string $column
|
||||
* @param string $relevance
|
||||
* @param array $words
|
||||
* @param string $compare
|
||||
* @param float $relevance_multiplier
|
||||
* @param string $pre_word
|
||||
* @param string $post_word
|
||||
* @return string
|
||||
*/
|
||||
protected function getSearchQuery(Builder $query, $column, $relevance, array $words, $relevance_multiplier, $pre_word = '', $post_word = '')
|
||||
{
|
||||
$like_comparator = $this->getDatabaseDriver() == 'pgsql' ? 'ILIKE' : 'LIKE';
|
||||
$cases = [];
|
||||
|
||||
foreach ($words as $word)
|
||||
{
|
||||
$cases[] = $this->getCaseCompare($column, $like_comparator, $relevance * $relevance_multiplier);
|
||||
$this->search_bindings[] = $pre_word . $word . $post_word;
|
||||
}
|
||||
|
||||
return implode(' + ', $cases);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the comparison string.
|
||||
*
|
||||
* @param string $column
|
||||
* @param string $compare
|
||||
* @param float $relevance
|
||||
* @return string
|
||||
*/
|
||||
protected function getCaseCompare($column, $compare, $relevance) {
|
||||
if($this->getDatabaseDriver() == 'pgsql') {
|
||||
$field = "LOWER(" . $column . ") " . $compare . " ?";
|
||||
return '(case when ' . $field . ' then ' . $relevance . ' else 0 end)';
|
||||
}
|
||||
|
||||
$column = str_replace('.', '`.`', $column);
|
||||
$field = "LOWER(`" . $column . "`) " . $compare . " ?";
|
||||
return '(case when ' . $field . ' then ' . $relevance . ' else 0 end)';
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the bindings to the query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param array $bindings
|
||||
*/
|
||||
protected function addBindingsToQuery(Builder $query, array $bindings) {
|
||||
$count = $this->getDatabaseDriver() != 'mysql' ? 2 : 1;
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
foreach($bindings as $binding) {
|
||||
$type = $i == 0 ? 'select' : 'having';
|
||||
$query->addBinding($binding, $type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge our cloned query builder with the original one.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $clone
|
||||
* @param \Illuminate\Database\Eloquent\Builder $original
|
||||
*/
|
||||
protected function mergeQueries(Builder $clone, Builder $original) {
|
||||
$tableName = DB::connection($this->connection)->getTablePrefix() . $this->getTable();
|
||||
if ($this->getDatabaseDriver() == 'pgsql') {
|
||||
$original->from(DB::connection($this->connection)->raw("({$clone->toSql()}) as {$tableName}"));
|
||||
} else {
|
||||
$original->from(DB::connection($this->connection)->raw("({$clone->toSql()}) as `{$tableName}`"));
|
||||
}
|
||||
$original->mergeBindings($clone->getQuery());
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user