Total Complexity | 50 |
Total Lines | 272 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like DataTables often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use DataTables, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
18 | class DataTables |
||
19 | { |
||
20 | /** @var Request $request */ |
||
21 | private $request; |
||
22 | |||
23 | /** @var string $table */ |
||
24 | private $table; |
||
25 | |||
26 | /** @var array $tableColumns */ |
||
27 | private $tableColumns; |
||
28 | |||
29 | /** @var Model $model */ |
||
30 | private $model; |
||
31 | |||
32 | /** @var Builder $query */ |
||
33 | private $query; |
||
34 | |||
35 | /** @var Builder $query */ |
||
36 | private $originalQuery; |
||
37 | |||
38 | /** @var array|null $aliases */ |
||
39 | private $aliases; |
||
40 | |||
41 | /** @var DatabaseManager */ |
||
42 | private $DB; |
||
43 | |||
44 | public function __construct(Request $request, DatabaseManager $DB) |
||
45 | { |
||
46 | $this->request = $request; |
||
47 | $this->DB = $DB; |
||
48 | } |
||
49 | |||
50 | /** |
||
51 | * @param \Illuminate\Database\Eloquent\Model $model |
||
52 | * @param \Illuminate\Database\Eloquent\Builder $query |
||
53 | * @param array $aliases |
||
54 | * |
||
55 | * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse |
||
56 | */ |
||
57 | public function provide(Model $model, Builder $query = null, array $aliases = null) |
||
58 | { |
||
59 | if ($this->request->has(['draw', 'start', 'length'])) { |
||
60 | $this->model = $model; |
||
61 | $this->query = $query ?? $this->model->newQuery(); |
||
62 | $this->aliases = $aliases; |
||
63 | |||
64 | $this->table = $model->getTable(); |
||
65 | $this->tableColumns = $this->DB |
||
66 | ->connection($model->getConnectionName()) |
||
67 | ->getDoctrineSchemaManager() |
||
68 | ->listTableColumns($this->table); |
||
69 | |||
70 | $reqData = $this->request->all(); |
||
71 | $response = []; |
||
72 | |||
73 | $this->prepareSelects(); |
||
74 | $this->originalQuery = clone $query; |
||
75 | |||
76 | if (array_key_exists('columns', $reqData) && is_array($reqData['columns'])) { |
||
77 | $columns = $reqData['columns']; |
||
78 | |||
79 | if (is_array($reqData['columns'])) { |
||
80 | $this->applySearch($columns); |
||
81 | $this->applyOrder($reqData, $columns); |
||
82 | } |
||
83 | } |
||
84 | |||
85 | $response['draw'] = +$reqData['draw']; |
||
86 | $response = $this->setResultCounters($response); |
||
87 | |||
88 | $this->applyPagination($reqData); |
||
89 | |||
90 | $response['data'] = $this->query |
||
91 | ->get() |
||
92 | ->toArray(); |
||
93 | |||
94 | return new JsonResponse($response); |
||
95 | } |
||
96 | |||
97 | return new Response('', 400); |
||
98 | } |
||
99 | |||
100 | private function prepareSelects() |
||
101 | { |
||
102 | $tableAttr = array_keys( |
||
103 | array_diff_key( |
||
104 | array_flip( |
||
105 | array_keys($this->tableColumns) |
||
106 | ), |
||
107 | array_flip($this->model->getHidden()) |
||
108 | ) |
||
109 | ); |
||
110 | |||
111 | if ($tableAttr) { |
||
|
|||
112 | foreach ($tableAttr as $attr) { |
||
113 | $selects[] = $this->DB->raw($this->table.'.'.$attr); |
||
114 | } |
||
115 | } |
||
116 | |||
117 | if ($this->aliases) { |
||
118 | foreach ($this->aliases as $alias => $value) { |
||
119 | $selects[] = $this->DB->raw($value.' AS '.$alias); |
||
120 | } |
||
121 | } |
||
122 | |||
123 | if (isset($selects)) { |
||
124 | $this->query->select($selects); |
||
125 | } |
||
126 | } |
||
127 | |||
128 | private function applySearch(array $columns) |
||
129 | { |
||
130 | foreach ($columns as $column) { |
||
131 | $searchValue = Arr::get($column, 'search.value'); |
||
132 | $searchColumn = Arr::get($column, 'data'); |
||
133 | |||
134 | if (!is_null($searchValue) && !is_null($searchColumn)) { |
||
135 | $searchField = $this->getField($searchColumn); |
||
136 | if (!$searchField) { |
||
137 | continue; |
||
138 | } |
||
139 | |||
140 | $searchMethod = $this->getSearchMethod($searchField); |
||
141 | [$searchQuery, $searchBindings] = $this->getSearchQuery($searchField, $searchValue, $searchColumn); |
||
142 | |||
143 | $this->query->{$searchMethod}($searchQuery, $searchBindings); |
||
144 | } |
||
145 | } |
||
146 | } |
||
147 | |||
148 | private function getSearchQuery($searchField, $searchValue, $column) |
||
149 | { |
||
150 | if ($this->isDateRange($searchValue)) { |
||
151 | [$from, $to] = explode(' - ', $searchValue); |
||
152 | |||
153 | $from = $this->toMySQLDate($from); |
||
154 | $to = $this->toMySQLDate($to, 1); |
||
155 | |||
156 | return [ |
||
157 | $searchField.' between ? and ?', |
||
158 | [$from, $to], |
||
159 | ]; |
||
160 | } else { |
||
161 | if ($this->shouldUseLike($this->tableColumns, $column)) { |
||
162 | return [ |
||
163 | $searchField.' like ?', |
||
164 | ['%'.$searchValue.'%'], |
||
165 | ]; |
||
166 | } else { |
||
167 | return [ |
||
168 | $searchField.' = ?', |
||
169 | [$searchValue], |
||
170 | ]; |
||
171 | } |
||
172 | } |
||
173 | } |
||
174 | |||
175 | private function isDateRange($value) : bool |
||
179 | } |
||
180 | |||
181 | private function toMySQLDate($value, $plusDay = 0) |
||
186 | } |
||
187 | |||
188 | private function applyOrder(array $reqData, array $columns) |
||
189 | { |
||
190 | if (array_key_exists('order', $reqData)) { |
||
191 | $orderColumnId = Arr::get($reqData, 'order.0.column'); |
||
192 | $orderByColumn = Arr::get($columns, $orderColumnId.'.data'); |
||
193 | |||
194 | if (!is_null($orderColumnId) && !is_null($orderByColumn)) { |
||
195 | $direction = Arr::get($reqData, 'order.0.dir'); |
||
196 | if ($direction !== 'asc' && $direction !== 'desc') { |
||
197 | return; |
||
198 | } |
||
199 | |||
200 | $orderField = $this->getField($orderByColumn); |
||
201 | if (!$orderField) { |
||
202 | return; |
||
203 | } |
||
204 | |||
205 | $this->query->orderByRaw($orderField.' '.$direction); |
||
206 | } |
||
207 | } |
||
208 | } |
||
209 | |||
210 | private function getField($column) |
||
211 | { |
||
212 | if (empty($this->aliases) || !array_key_exists($column, $this->aliases)) { |
||
213 | if (array_key_exists($column, $this->tableColumns)) { |
||
214 | return $this->table.'.'.$column; |
||
215 | } else { |
||
216 | return null; |
||
217 | } |
||
218 | } else { |
||
219 | return $this->aliases[$column]; |
||
220 | } |
||
221 | } |
||
222 | |||
223 | private function setResultCounters(array $response) : array |
||
224 | { |
||
225 | $response['recordsTotal'] = $this->getCount($this->originalQuery); |
||
226 | |||
227 | if (!empty($this->query->getQuery()->wheres) && |
||
228 | $this->originalQuery->getQuery()->wheres !== $this->query->getQuery()->wheres) { |
||
229 | $response['recordsFiltered'] = $this->getCount($this->query); |
||
230 | } else { |
||
231 | $response['recordsFiltered'] = $response['recordsTotal']; |
||
232 | } |
||
233 | |||
234 | return $response; |
||
235 | } |
||
236 | |||
237 | private function getCount(Builder $query) : int |
||
238 | { |
||
239 | if (!empty($query->getQuery()->groups) || !empty($query->getQuery()->havings)) { |
||
240 | return $this->DB |
||
241 | ->table($this->DB->raw('('.$query->toSql().') as s')) |
||
242 | ->setBindings($query->getBindings()) |
||
243 | ->selectRaw('count(*) as count') |
||
244 | ->first() |
||
245 | ->count; |
||
246 | } else { |
||
247 | return $query->count(); |
||
248 | } |
||
249 | } |
||
250 | |||
251 | private function applyPagination(array $reqData) |
||
252 | { |
||
253 | if (array_key_exists('start', $reqData)) { |
||
254 | $this->query->offset(+$reqData['start']); |
||
255 | } |
||
256 | |||
257 | if (array_key_exists('length', $reqData)) { |
||
258 | $this->query->limit(+$reqData['length']); |
||
259 | } |
||
260 | } |
||
261 | |||
262 | private function getSearchMethod($alias) |
||
263 | { |
||
264 | $mustUseHaving = ['GROUP_CONCAT', 'COUNT', 'MIN', 'IFNULL']; |
||
265 | |||
266 | foreach ($mustUseHaving as $m) { |
||
267 | if (strpos($alias, $m) !== false) { |
||
268 | return 'havingRaw'; |
||
269 | } |
||
270 | } |
||
271 | |||
272 | return 'whereRaw'; |
||
273 | } |
||
274 | |||
275 | /** |
||
276 | * @param Column[] $tableColumns |
||
277 | * @param string $column |
||
278 | * |
||
279 | * @return mixed |
||
280 | */ |
||
281 | private function shouldUseLike($tableColumns, $column) |
||
290 | } |
||
291 | } |
||
292 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.