PageRenderTime 27ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/nicolaslopezj/searchable/src/SearchableTrait.php

https://gitlab.com/DhanapalRudram/Maritime_Managers
PHP | 342 lines | 183 code | 46 blank | 113 comment | 29 complexity | b0ce68147e1091aba8ba97558741974d MD5 | raw file
  1. <?php namespace Nicolaslopezj\Searchable;
  2. use Illuminate\Database\Eloquent\Builder;
  3. use Illuminate\Database\Query\Expression;
  4. use Illuminate\Support\Facades\Config;
  5. use Illuminate\Support\Facades\DB;
  6. use Illuminate\Support\Str;
  7. /**
  8. * Trait SearchableTrait
  9. * @package Nicolaslopezj\Searchable
  10. * @property array $searchable
  11. * @property string $table
  12. * @property string $primaryKey
  13. * @method string getTable()
  14. */
  15. trait SearchableTrait
  16. {
  17. /**
  18. * @var array
  19. */
  20. protected $search_bindings = [];
  21. /**
  22. * Creates the search scope.
  23. *
  24. * @param \Illuminate\Database\Eloquent\Builder $q
  25. * @param string $search
  26. * @param float|null $threshold
  27. * @param boolean $entireText
  28. * @param boolean $entireTextOnly
  29. * @return \Illuminate\Database\Eloquent\Builder
  30. */
  31. public function scopeSearch(Builder $q, $search, $threshold = null, $entireText = false, $entireTextOnly = false)
  32. {
  33. return $this->scopeSearchRestricted($q, $search, null, $threshold, $entireText, $entireTextOnly);
  34. }
  35. public function scopeSearchRestricted(Builder $q, $search, $restriction, $threshold = null, $entireText = false, $entireTextOnly = false)
  36. {
  37. $query = clone $q;
  38. $query->select($this->getTable() . '.*');
  39. $this->makeJoins($query);
  40. if ( ! $search)
  41. {
  42. return $q;
  43. }
  44. $search = mb_strtolower(trim($search));
  45. $words = explode(' ', $search);
  46. $selects = [];
  47. $this->search_bindings = [];
  48. $relevance_count = 0;
  49. foreach ($this->getColumns() as $column => $relevance)
  50. {
  51. $relevance_count += $relevance;
  52. if (!$entireTextOnly) {
  53. $queries = $this->getSearchQueriesForColumn($query, $column, $relevance, $words);
  54. } else {
  55. $queries = [];
  56. }
  57. if ( ($entireText === true && count($words) > 1) || $entireTextOnly === true )
  58. {
  59. $queries[] = $this->getSearchQuery($query, $column, $relevance, [$search], 50, '', '');
  60. $queries[] = $this->getSearchQuery($query, $column, $relevance, [$search], 30, '%', '%');
  61. }
  62. foreach ($queries as $select)
  63. {
  64. $selects[] = $select;
  65. }
  66. }
  67. $this->addSelectsToQuery($query, $selects);
  68. // Default the threshold if no value was passed.
  69. if (is_null($threshold)) {
  70. $threshold = $relevance_count / 4;
  71. }
  72. $this->filterQueryWithRelevance($query, $selects, $threshold);
  73. $this->makeGroupBy($query);
  74. $this->addBindingsToQuery($query, $this->search_bindings);
  75. if(is_callable($restriction)) {
  76. $query = $restriction($query);
  77. }
  78. $this->mergeQueries($query, $q);
  79. return $q;
  80. }
  81. /**
  82. * Returns database driver Ex: mysql, pgsql, sqlite.
  83. *
  84. * @return array
  85. */
  86. protected function getDatabaseDriver() {
  87. $key = $this->connection ?: Config::get('database.default');
  88. return Config::get('database.connections.' . $key . '.driver');
  89. }
  90. /**
  91. * Returns the search columns.
  92. *
  93. * @return array
  94. */
  95. protected function getColumns()
  96. {
  97. if (array_key_exists('columns', $this->searchable)) {
  98. $driver = $this->getDatabaseDriver();
  99. $prefix = Config::get("database.connections.$driver.prefix");
  100. $columns = [];
  101. foreach($this->searchable['columns'] as $column => $priority){
  102. $columns[$prefix . $column] = $priority;
  103. }
  104. return $columns;
  105. } else {
  106. return DB::connection()->getSchemaBuilder()->getColumnListing($this->table);
  107. }
  108. }
  109. /**
  110. * Returns whether or not to keep duplicates.
  111. *
  112. * @return array
  113. */
  114. protected function getGroupBy()
  115. {
  116. if (array_key_exists('groupBy', $this->searchable)) {
  117. return $this->searchable['groupBy'];
  118. }
  119. return false;
  120. }
  121. /**
  122. * Returns the table columns.
  123. *
  124. * @return array
  125. */
  126. public function getTableColumns()
  127. {
  128. return $this->searchable['table_columns'];
  129. }
  130. /**
  131. * Returns the tables that are to be joined.
  132. *
  133. * @return array
  134. */
  135. protected function getJoins()
  136. {
  137. return array_get($this->searchable, 'joins', []);
  138. }
  139. /**
  140. * Adds the sql joins to the query.
  141. *
  142. * @param \Illuminate\Database\Eloquent\Builder $query
  143. */
  144. protected function makeJoins(Builder $query)
  145. {
  146. foreach ($this->getJoins() as $table => $keys) {
  147. $query->leftJoin($table, function ($join) use ($keys) {
  148. $join->on($keys[0], '=', $keys[1]);
  149. if (array_key_exists(2, $keys) && array_key_exists(3, $keys)) {
  150. $join->where($keys[2], '=', $keys[3]);
  151. }
  152. });
  153. }
  154. }
  155. /**
  156. * Makes the query not repeat the results.
  157. *
  158. * @param \Illuminate\Database\Eloquent\Builder $query
  159. */
  160. protected function makeGroupBy(Builder $query)
  161. {
  162. if ($groupBy = $this->getGroupBy()) {
  163. $query->groupBy($groupBy);
  164. } else {
  165. $driver = $this->getDatabaseDriver();
  166. if ($driver == 'sqlsrv') {
  167. $columns = $this->getTableColumns();
  168. } else {
  169. $columns = $this->getTable() . '.' .$this->primaryKey;
  170. }
  171. $query->groupBy($columns);
  172. $joins = array_keys(($this->getJoins()));
  173. foreach ($this->getColumns() as $column => $relevance) {
  174. array_map(function ($join) use ($column, $query) {
  175. if (Str::contains($column, $join)) {
  176. $query->groupBy($column);
  177. }
  178. }, $joins);
  179. }
  180. }
  181. }
  182. /**
  183. * Puts all the select clauses to the main query.
  184. *
  185. * @param \Illuminate\Database\Eloquent\Builder $query
  186. * @param array $selects
  187. */
  188. protected function addSelectsToQuery(Builder $query, array $selects)
  189. {
  190. $selects = new Expression('max(' . implode(' + ', $selects) . ') as relevance');
  191. $query->addSelect($selects);
  192. }
  193. /**
  194. * Adds the relevance filter to the query.
  195. *
  196. * @param \Illuminate\Database\Eloquent\Builder $query
  197. * @param array $selects
  198. * @param float $relevance_count
  199. */
  200. protected function filterQueryWithRelevance(Builder $query, array $selects, $relevance_count)
  201. {
  202. $comparator = $this->getDatabaseDriver() != 'mysql' ? implode(' + ', $selects) : 'relevance';
  203. $relevance_count=number_format($relevance_count,2,'.','');
  204. $query->havingRaw("$comparator > $relevance_count");
  205. $query->orderBy('relevance', 'desc');
  206. // add bindings to postgres
  207. }
  208. /**
  209. * Returns the search queries for the specified column.
  210. *
  211. * @param \Illuminate\Database\Eloquent\Builder $query
  212. * @param string $column
  213. * @param float $relevance
  214. * @param array $words
  215. * @return array
  216. */
  217. protected function getSearchQueriesForColumn(Builder $query, $column, $relevance, array $words)
  218. {
  219. $queries = [];
  220. $queries[] = $this->getSearchQuery($query, $column, $relevance, $words, 15);
  221. $queries[] = $this->getSearchQuery($query, $column, $relevance, $words, 5, '', '%');
  222. $queries[] = $this->getSearchQuery($query, $column, $relevance, $words, 1, '%', '%');
  223. return $queries;
  224. }
  225. /**
  226. * Returns the sql string for the given parameters.
  227. *
  228. * @param \Illuminate\Database\Eloquent\Builder $query
  229. * @param string $column
  230. * @param string $relevance
  231. * @param array $words
  232. * @param string $compare
  233. * @param float $relevance_multiplier
  234. * @param string $pre_word
  235. * @param string $post_word
  236. * @return string
  237. */
  238. protected function getSearchQuery(Builder $query, $column, $relevance, array $words, $relevance_multiplier, $pre_word = '', $post_word = '')
  239. {
  240. $like_comparator = $this->getDatabaseDriver() == 'pgsql' ? 'ILIKE' : 'LIKE';
  241. $cases = [];
  242. foreach ($words as $word)
  243. {
  244. $cases[] = $this->getCaseCompare($column, $like_comparator, $relevance * $relevance_multiplier);
  245. $this->search_bindings[] = $pre_word . $word . $post_word;
  246. }
  247. return implode(' + ', $cases);
  248. }
  249. /**
  250. * Returns the comparison string.
  251. *
  252. * @param string $column
  253. * @param string $compare
  254. * @param float $relevance
  255. * @return string
  256. */
  257. protected function getCaseCompare($column, $compare, $relevance) {
  258. if($this->getDatabaseDriver() == 'pgsql') {
  259. $field = "LOWER(" . $column . ") " . $compare . " ?";
  260. return '(case when ' . $field . ' then ' . $relevance . ' else 0 end)';
  261. }
  262. $column = str_replace('.', '`.`', $column);
  263. $field = "LOWER(`" . $column . "`) " . $compare . " ?";
  264. return '(case when ' . $field . ' then ' . $relevance . ' else 0 end)';
  265. }
  266. /**
  267. * Adds the bindings to the query.
  268. *
  269. * @param \Illuminate\Database\Eloquent\Builder $query
  270. * @param array $bindings
  271. */
  272. protected function addBindingsToQuery(Builder $query, array $bindings) {
  273. $count = $this->getDatabaseDriver() != 'mysql' ? 2 : 1;
  274. for ($i = 0; $i < $count; $i++) {
  275. foreach($bindings as $binding) {
  276. $type = $i == 0 ? 'select' : 'having';
  277. $query->addBinding($binding, $type);
  278. }
  279. }
  280. }
  281. /**
  282. * Merge our cloned query builder with the original one.
  283. *
  284. * @param \Illuminate\Database\Eloquent\Builder $clone
  285. * @param \Illuminate\Database\Eloquent\Builder $original
  286. */
  287. protected function mergeQueries(Builder $clone, Builder $original) {
  288. $tableName = DB::connection($this->connection)->getTablePrefix() . $this->getTable();
  289. if ($this->getDatabaseDriver() == 'pgsql') {
  290. $original->from(DB::connection($this->connection)->raw("({$clone->toSql()}) as {$tableName}"));
  291. } else {
  292. $original->from(DB::connection($this->connection)->raw("({$clone->toSql()}) as `{$tableName}`"));
  293. }
  294. $original->mergeBindings($clone->getQuery());
  295. }
  296. }