BaseDataTableService   A
last analyzed

Complexity

Total Complexity 25

Size/Duplication

Total Lines 263
Duplicated Lines 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
wmc 25
eloc 77
c 6
b 0
f 0
dl 0
loc 263
rs 10

14 Methods

Rating   Name   Duplication   Size   Complexity  
A getUpdatableColumns() 0 7 2
A getCustomColumnNames() 0 7 2
A buildSearchQuery() 0 7 1
A getColumnsWithoutPrimaryKey() 0 6 1
A getDatabaseColumnNames() 0 3 1
A getSelectableColumns() 0 7 2
A getRecords() 0 25 5
A response() 0 12 1
A getModel() 0 11 2
A getTable() 0 3 1
A builder() 0 13 3
A getDisplayableColumns() 0 9 2
A hasSearchQuery() 0 3 1
A resolveQueryParts() 0 31 1
1
<?php
2
3
namespace Laravel\DataTables\Services;
4
5
use Arr;
6
use Illuminate\Database\Eloquent\Builder;
7
use Illuminate\Database\Eloquent\Collection;
8
use Illuminate\Database\Eloquent\Model;
9
use Illuminate\Database\QueryException;
10
use Illuminate\Http\Request;
11
use Illuminate\Support\Traits\Macroable;
12
use Laravel\DataTables\Contracts\Displayable;
13
use Laravel\DataTables\Exceptions\EloquentBuilderWasSetToNullException;
14
use Laravel\DataTables\Exceptions\InvalidColumnSearchException;
15
use Schema;
16
17
abstract class BaseDataTableService implements Displayable
18
{
19
    use Macroable;
0 ignored issues
show
Bug introduced by
The trait Illuminate\Support\Traits\Macroable requires the property $name which is not provided by Laravel\DataTables\Services\BaseDataTableService.
Loading history...
20
21
    /**
22
     * Load the relationships associated with the collection that will be returned.
23
     *
24
     * @var array
25
     */
26
    public $relations;
27
28
    /**
29
     * Get/Set the eloquent builder.
30
     *
31
     * @return \Illuminate\Database\Eloquent\Builder
32
     */
33
    public function builder(): Builder
34
    {
35
        /**
36
         * @var mixed
37
         */
38
        static $builder = null;
39
40
        if (! is_null($builder) && ! app()->environment('testing')) {
0 ignored issues
show
introduced by
The method environment() does not exist on Illuminate\Container\Container. Are you sure you never get this type here, but always one of the subclasses? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

40
        if (! is_null($builder) && ! app()->/** @scrutinizer ignore-call */ environment('testing')) {
Loading history...
41
            return $builder;
42
        }
43
        $builder = $this->query()->newQuery();
44
45
        return $builder;
46
    }
47
48
    /**
49
     * @param $columns
50
     */
51
    public function getColumnsWithoutPrimaryKey($columns)
52
    {
53
        $primaryKey = $this->getModel()->getKeyName();
54
55
        return array_filter($columns, function ($column) use ($primaryKey) {
56
            return $primaryKey !== $column;
57
        });
58
    }
59
60
    /**
61
     * Get Custom Column Names.
62
     *
63
     * @return array
64
     */
65
    public function getCustomColumnNames(): array
66
    {
67
        if (method_exists($model = $this->getModel(), 'getCustomColumnNames')) {
68
            return $model->getCustomColumnNames();
69
        }
70
71
        return [];
72
    }
73
74
    /**
75
     * Get displayable columns.
76
     *
77
     * @return array
78
     */
79
    public function getDisplayableColumns(): array
80
    {
81
        if (method_exists($model = $this->getModel(), 'getDisplayableColumns')) {
82
            return $model->getDisplayableColumns();
83
        }
84
85
        return array_diff(
86
            $this->getDatabaseColumnNames(),
87
            $this->getModel()->getHidden()
88
        );
89
    }
90
91
    /**
92
     * @return mixed
93
     */
94
    public function getModel(): Model
95
    {
96
        /**
97
         * @var mixed
98
         */
99
        static $model = null;
100
        if (! is_null($model)) {
101
            return $model;
102
        }
103
104
        return $model = $this->builder()->getModel();
105
    }
106
107
    /**
108
     * Fetch records from the database.
109
     *
110
     * @param  Request    $request
111
     * @param  callable   $callback
112
     * @return Collection
113
     */
114
    public function getRecords(Request $request = null, callable $callback = null): Collection
115
    {
116
        $builder = $this->builder();
117
118
        // we will check if the request has a query string for search. the query string for searching must contain column, operator which identified at resolveQueryParts method in form of the keys of the array. and the value that user is trying to search for
119
        // example: http://localhost:8000/api/posts?column=title&operator=contains&value=hello
120
        if ($request && $this->hasSearchQuery($request)) {
121
            $builder = $this->buildSearchQuery($builder, $request);
122
        }
123
124
        // Turn on the flexibility for the programmer to apply his own query to chain on the current query then we will retrieve back the query builder after the programmer applies his logic and proceed our own queries.
125
        if ($callback) {
126
            $builder = $callback($builder);
127
            throw_unless($builder, new EloquentBuilderWasSetToNullException);
128
        }
129
130
        // we will try to parse the query and return the output of it, if anything goes wrong, by default we will be returning an empty collection.
131
        try {
132
            // if the request doesn't have a limit, it will return null, and since limit takes an integer value >= 0, then it won't limit
133
            // at all since we will replace the null with a negative number.
134
            return $builder->select(...$this->getSelectableColumns())->limit(
135
                $request->limit ?? -1
136
            )->get();
137
        } catch (QueryException $e) {
138
            return collect([]);
139
        }
140
    }
141
142
    /**
143
     * @return mixed
144
     */
145
    public function getSelectableColumns(): array
146
    {
147
        if (method_exists($model = $this->getModel(), 'getSelectableColumns')) {
148
            return $model->getSelectableColumns();
149
        }
150
151
        return $this->getDisplayableColumns();
152
    }
153
154
    /**
155
     * Get The Table Name.
156
     *
157
     * @return string
158
     */
159
    public function getTable(): string
160
    {
161
        return $this->getModel()->getTable();
162
    }
163
164
    /**
165
     * @return array
166
     */
167
    public function getUpdatableColumns(): array
168
    {
169
        if (method_exists($this->getModel(), 'getUpdatableColumns')) {
170
            return $this->getColumnsWithoutPrimaryKey($this->getModel()->getUpdatableColumns());
171
        }
172
173
        return $this->getColumnsWithoutPrimaryKey($this->getDisplayableColumns());
174
    }
175
176
    /**
177
     * Get query source of dataTable.
178
     *
179
     * @return \Illuminate\Database\Eloquent\Builder
180
     */
181
    abstract public function query(): Builder;
182
183
    /**
184
     * Return the response skeleton.
185
     *
186
     * @return array
187
     */
188
    public function response(callable $callback = null): array
189
    {
190
        return [
191
            'table' => $this->getTable(),
192
            'displayable' => $this->getDisplayableColumns(),
193
            'records' => $this->getRecords(request(), $callback)->load((array) $this->relations),
194
            'updatable' => $this->getUpdatableColumns(),
195
            'custom_columns' => $this->getCustomColumnNames(),
196
            'allow' => [
197
                'creatable' => $this->allowCreating ?? false,
0 ignored issues
show
Bug Best Practice introduced by
The property allowCreating does not exist on Laravel\DataTables\Services\BaseDataTableService. Did you maybe forget to declare it?
Loading history...
198
                'deletable' => $this->allowDeleting ?? false,
0 ignored issues
show
Bug Best Practice introduced by
The property allowDeleting does not exist on Laravel\DataTables\Services\BaseDataTableService. Did you maybe forget to declare it?
Loading history...
199
                'updatable' => $this->allowUpdating ?? false,
0 ignored issues
show
Bug Best Practice introduced by
The property allowUpdating does not exist on Laravel\DataTables\Services\BaseDataTableService. Did you maybe forget to declare it?
Loading history...
200
            ],
201
        ];
202
    }
203
204
    /**
205
     * Build Search Query.
206
     *
207
     * @param  Builder $builder
208
     * @param  Request $request
209
     * @return Builder
210
     */
211
    protected function buildSearchQuery(Builder $builder, Request $request): Builder
212
    {
213
        [ 'operator' => $operator, 'value' => $value ] = $this->resolveQueryParts($request->operator, $request->value);
214
215
        throw_unless(in_array($request->column, $this->getDisplayableColumns()), InvalidColumnSearchException::class);
216
217
        return $builder->where($request->column, $operator, $value);
218
    }
219
220
    /**
221
     * Get Database Column Names.
222
     *
223
     * @return array
224
     */
225
    protected function getDatabaseColumnNames(): array
226
    {
227
        return Schema::getColumnListing($this->getTable());
228
    }
229
230
    /**
231
     * Check if the request has a search query.
232
     *
233
     * @param \Illuminate\Http\Request $request.
234
     * @return bool
235
     */
236
    protected function hasSearchQuery(Request $request): bool
237
    {
238
        return count(array_filter($request->only(['column', 'operator', 'value']))) === 3;
239
    }
240
241
    /**
242
     * Resolve Query Parts.
243
     *
244
     * @param string $operator
245
     * @param string $value
246
     *
247
     * @return array
248
     */
249
    protected function resolveQueryParts(string $operator, string $value): array
250
    {
251
        return Arr::get([
252
            'equals' => [
253
                'operator' => '=',
254
                'value' => $value,
255
            ],
256
            'contains' => [
257
                'operator' => 'LIKE',
258
                'value' => "%{$value}%",
259
            ],
260
            'starts_with' => [
261
                'operator' => 'LIKE',
262
                'value' => "{$value}%",
263
            ],
264
            'ends_with' => [
265
                'operator' => 'LIKE',
266
                'value' => "%{$value}",
267
268
            ],
269
            'greater_than' => [
270
                'operator' => '>',
271
                'value' => $value,
272
273
            ],
274
            'less_than' => [
275
                'operator' => '<',
276
                'value' => $value,
277
278
            ],
279
        ], $operator);
280
    }
281
}
282