| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- <?php
- namespace App\Http\Controllers;
- use App\Helpers\DateHelper;
- use Illuminate\Database\Eloquent\Builder;
- use Illuminate\Database\Eloquent\Model;
- use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
- use Illuminate\Foundation\Validation\ValidatesRequests;
- use Illuminate\Http\Request;
- use Illuminate\Routing\Controller as BaseController;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Str;
- class Controller extends BaseController
- {
- use AuthorizesRequests, ValidatesRequests;
- protected array $data = [
- 'filters' => [],
- 'ranges' => [],
- 'dates' => [],
- ];
- /**
- * @param Model $model
- * @param string ...$columnNames
- * @return void
- */
- protected function createDateFilters(Model $model, string ...$columnNames): void
- {
- foreach ($columnNames as $columnName) {
- $this->data['dates'][$columnName] = [
- 'title' => $this->data['header'][$columnName],
- 'min' => DateHelper::getDateForDB($model::query()->min($columnName) ?? ''),
- 'max' => DateHelper::getDateForDB($model::query()->max($columnName) ?? ''),
- ];
- }
- }
- /**
- * @param Model $model
- * @param array $columnNames
- * @return void
- */
- protected function createRangeFilters(Model $model, string ...$columnNames): void
- {
- foreach ($columnNames as $columnName) {
- $rangeKey = $columnName;
- if (!isset($this->data['header'][$columnName]) && isset($this->data['header'][$columnName . '_txt'])) {
- $rangeKey = $columnName . '_txt';
- }
- $title = $this->data['header'][$rangeKey] ?? $columnName;
- if(str_ends_with($columnName, '_price')) {
- $this->data['ranges'][$rangeKey] = [
- 'title' => $title,
- 'min' => $model::query()->min($columnName) / 100,
- 'max' => $model::query()->max($columnName) / 100,
- ];
- } else {
- $this->data['ranges'][$rangeKey] = [
- 'title' => $title,
- 'min' => $model::query()->min($columnName),
- 'max' => $model::query()->max($columnName),
- ];
- }
- }
- }
- /**
- * @param Model $model
- * @param array $columns
- * @return void
- */
- protected function createFilters(Model $model, string ...$columns): void
- {
- foreach ($columns as $column) {
- $uniqueValues = $model::query()->distinct()->get($column)->pluck($column)->toArray();
- foreach ($uniqueValues as $k => $v) {
- if(!$v) {
- $uniqueValues[$k] = '-пусто-';
- }
- }
- $result = [];
- foreach ($uniqueValues as $val){
- if(str_ends_with($column, '_id')) {
- $relation = Str::camel(str_replace('_id', '', $column));
- $result[$val] = $model::query()->where($column, '=', $val)->first()?->$relation->name;
- } else {
- $result[$val] = $val;
- }
- }
- $this->data['filters'][$column] = [
- 'title' => $this->data['header'][$column],
- 'values' => $result
- ];
- }
- }
- /**
- * @param Model $model
- * @param Request $request
- * @return void
- */
- protected function setSortAndOrderBy(Model $model, Request $request): void
- {
- // ------- setup sort and order --------------------------------------------------------------------------------
- $this->data['sortBy'] = (!empty($request->sortBy))
- ? Str::replace('_txt', '', $request->sortBy) // remove '_txt' fields modifier
- : $model::DEFAULT_SORT_BY ?? 'created_at';
- // check for sortBy is valid field
- $p = new $model();
- if(!in_array($this->data['sortBy'], array_merge(['id', 'created_at'], $p->getFillable()))) {
- $this->data['sortBy'] = $model::DEFAULT_SORT_BY ?? 'created_at';
- }
- if(!empty($request->per_page)) {
- $this->data['per_page'] = $request->per_page;
- } else {
- $this->data['per_page'] = config('pagination.per_page');
- }
- session(['per_page' => $request->per_page]);
- // set order
- $this->data['orderBy'] = (empty($request->order)) ? 'asc' : 'desc';
- }
- /**
- * @param Builder $query
- * @param Request $request
- * @return void
- */
- protected function acceptFilters(Builder $query, Request $request): void
- {
- // accept filters
- if(!empty($request->filters) && is_array($request->filters)) {
- // Собираем имена range и date фильтров для пропуска _from/_to в основном цикле
- $rangeKeys = array_keys($this->data['ranges'] ?? []);
- $dateKeys = array_keys($this->data['dates'] ?? []);
- $rangeAndDateKeys = array_merge($rangeKeys, $dateKeys);
- foreach ($request->filters as $filterName => $filterValue) {
- if(!$filterValue && $filterValue !== '0') continue;
- // Пропускаем _from/_to — они обрабатываются ниже
- if(Str::endsWith($filterName, ['_from', '_to'])) {
- continue;
- }
- // Резолвим виртуальные столбцы и значения
- [$dbColumn, $dbValue] = $this->resolveFilterColumn($filterName, $filterValue);
- if(Str::contains($dbValue, '||')) {
- $values = explode('||', $dbValue);
- $query->where(function ($q) use ($dbColumn, $values) {
- $nonNullValues = [];
- foreach ($values as $v) {
- if ($v == '-пусто-') {
- $q->orWhereNull($dbColumn);
- } else {
- $nonNullValues[] = $v;
- }
- }
- if (!empty($nonNullValues)) {
- $q->orWhereIn($dbColumn, $nonNullValues);
- }
- });
- } else {
- if($dbValue == '-пусто-') {
- $query->whereNull($dbColumn);
- } else {
- $query->where($dbColumn, $dbValue);
- }
- }
- }
- // Обработка range-фильтров (_from / _to)
- foreach ($rangeAndDateKeys as $columnName) {
- $fromValue = $request->filters[$columnName . '_from'] ?? null;
- $toValue = $request->filters[$columnName . '_to'] ?? null;
- // Для price-полей значения в форме в рублях, в БД в копейках
- $dbColumn = str_ends_with($columnName, '_txt') ? substr($columnName, 0, -4) : $columnName;
- $multiplier = str_ends_with($dbColumn, '_price') ? 100 : 1;
- if($fromValue !== null && $fromValue !== '') {
- $query->where($dbColumn, '>=', $fromValue * $multiplier);
- }
- if($toValue !== null && $toValue !== '') {
- $query->where($dbColumn, '<=', $toValue * $multiplier);
- }
- }
- }
- }
- /**
- * Резолвит виртуальные имена столбцов и маппит отображаемые значения обратно на значения БД.
- *
- * @return array{0: string, 1: string} [реальный_столбец, значение_для_бд]
- */
- protected function resolveFilterColumn(string $filterName, string $filterValue): array
- {
- $table = $this->data['id'] ?? '';
- $columnMap = FilterController::COLUMN_MAP[$table] ?? [];
- $valueMap = FilterController::VALUE_MAP[$table] ?? [];
- // Резолвим имя столбца: явный маппинг → убираем _txt
- if (isset($columnMap[$filterName])) {
- $dbColumn = $columnMap[$filterName];
- } elseif (str_ends_with($filterName, '_txt')) {
- $dbColumn = substr($filterName, 0, -4);
- } else {
- $dbColumn = $filterName;
- }
- // Обратный маппинг значений
- if (isset($valueMap[$dbColumn])) {
- $reverseMap = array_flip($valueMap[$dbColumn]);
- $parts = explode('||', $filterValue);
- $mapped = array_map(fn($v) => (string)($reverseMap[$v] ?? $v), $parts);
- $filterValue = implode('||', $mapped);
- }
- // Для цен: значения в рублях, в БД в копейках
- if (str_ends_with($dbColumn, '_price')) {
- $parts = explode('||', $filterValue);
- $mapped = array_map(function ($v) {
- if ($v === '-пусто-') {
- return $v;
- }
- $normalized = str_replace(',', '.', (string)$v);
- if (!is_numeric($normalized)) {
- return $v;
- }
- return (string) round(((float)$normalized) * 100);
- }, $parts);
- $filterValue = implode('||', $mapped);
- }
- return [$dbColumn, $filterValue];
- }
- /**
- * @param Builder $query
- * @param Request $request
- * @return void
- */
- protected function acceptSearch(Builder $query, Request $request): void
- {
- // accept search
- if(!empty($request->s)) {
- $s = $request->s;
- $searchFields = $this->data['searchFields'];
- $query->where(function ($query) use ($searchFields, $s) {
- foreach ($searchFields as $searchField) {
- if(Str::contains($searchField, '-')) {
- list($relation, $column) = explode('-', $searchField);
- $query->orWhereHas($relation, function ($query) use ($s, $column) {
- $query->where($column, 'LIKE', "%{$s}%");
- });
- } else {
- $query->orWhere($searchField, 'LIKE', '%' . $s . '%');
- }
- }
- });
- }
- }
- }
|