Test Failed
Push — master ( 4426b4...2ad047 )
by Mathieu
05:02
created

Datatable   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 369
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 41
eloc 95
dl 0
loc 369
rs 9.1199
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 2
A createGlobalFilters() 0 14 1
A result() 0 9 1
A count() 0 14 2
A processColumnSelect() 0 3 1
A limit() 0 6 1
A get() 0 19 3
A setGlobalSearch() 0 5 1
A setNameIdentifier() 0 5 1
A getNameIdentifier() 0 3 1
B createHavingPart() 0 12 7
A processColumnIdentifier() 0 3 2
A orderBy() 0 8 1
A createQueryResult() 0 9 2
B createFoundationQuery() 0 22 8
B createWherePart() 0 12 7

How to fix   Complexity   

Complex Class

Complex classes like Datatable 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 Datatable, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace DoctrineDatatable;
4
5
use Doctrine\ORM\QueryBuilder;
6
use DoctrineDatatable\Exception\MinimumColumn;
7
8
/**
9
 * Class Datatable.
10
 *
11
 * @author Mathieu Petrini <[email protected]>
12
 */
13
class Datatable
14
{
15
    /**
16
     * @var string
17
     */
18
    private $identifier;
19
20
    /**
21
     * @var int
22
     */
23
    private $resultPerPage;
24
25
    /**
26
     * @var string
27
     */
28
    private $nameIdentifier;
29
30
    /**
31
     * @var bool
32
     */
33
    private $globalSearch;
34
35
    /**
36
     * @var QueryBuilder
37
     */
38
    protected $query;
39
40
    /**
41
     * @var Column[]
42
     */
43
    protected $columns;
44
45
    private const DEFAULT_NAME_IDENTIFIER = 'DT_RowId';
46
47
    public const RESULT_PER_PAGE = 30;
48
49
    /**
50
     * Datatable constructor.
51
     *
52
     * @author Mathieu Petrini <[email protected]>
53
     *
54
     * @param QueryBuilder $query
55
     * @param string       $identifier
56
     * @param array        $columns
57
     * @param int|null     $resultPerPage
58
     *
59
     * @throws MinimumColumn
60
     */
61
    public function __construct(
62
        QueryBuilder $query,
63
        string $identifier,
64
        array $columns,
65
        ?int $resultPerPage = self::RESULT_PER_PAGE
66
    ) {
67
        if (empty($columns)) {
68
            throw new MinimumColumn();
69
        }
70
        $this->query = $query;
71
        $this->identifier = $identifier;
72
        $this->columns = $columns;
73
        $this->resultPerPage = $resultPerPage ?? self::RESULT_PER_PAGE;
74
        $this->nameIdentifier = self::DEFAULT_NAME_IDENTIFIER;
75
        $this->globalSearch = false;
76
    }
77
78
    /**
79
     * PRIVATE METHODS.
80
     */
81
82
    /**
83
     * @author Mathieu Petrini <[email protected]>
84
     *
85
     * @param array $filters
86
     *
87
     * @return array
88
     */
89
    private function createGlobalFilters(array $filters): array
90
    {
91
        $temp = array(
92
            'columns' => array(),
93
        );
94
        array_map(function () use ($filters, &$temp) {
95
            $temp['columns'][] = array(
96
                'search' => array(
97
                    'value' => $filters['search'][Column::GLOBAL_ALIAS],
98
                ),
99
            );
100
        }, $this->columns);
101
102
        return $temp;
103
    }
104
105
    /**
106
     * @author Mathieu Petrini <[email protected]>
107
     *
108
     * @param QueryBuilder $query
109
     * @param array        $filters
110
     *
111
     * @return string
112
     *
113
     * @throws Exception\ResolveColumnNotHandle
114
     * @throws Exception\UnfilterableColumn
115
     * @throws Exception\WhereColumnNotHandle
116
     */
117
    private function createWherePart(QueryBuilder &$query, array $filters): string
118
    {
119
        $temp = '';
120
121
        foreach ($filters['columns'] as $index => $filter) {
122
            if (isset($this->columns[$index]) && !empty($filter['search']['value']) && !$this->columns[$index]->isHaving()) {
123
                $temp .= (!empty($temp) ? ' '.($this->globalSearch ? 'OR' : 'AND').' ' : '').
124
                    '('.$this->columns[$index]->where($query, $filter['search']['value']).')';
125
            }
126
        }
127
128
        return $temp;
129
    }
130
131
    /**
132
     * @author Mathieu Petrini <[email protected]>
133
     *
134
     * @param QueryBuilder $query
135
     * @param array        $filters
136
     *
137
     * @return string
138
     *
139
     * @throws Exception\ResolveColumnNotHandle
140
     * @throws Exception\UnfilterableColumn
141
     * @throws Exception\WhereColumnNotHandle
142
     */
143
    private function createHavingPart(QueryBuilder &$query, array $filters): string
144
    {
145
        $temp = '';
146
147
        foreach ($filters['columns'] as $index => $filter) {
148
            if (isset($this->columns[$index]) && !empty($filter['search']['value']) && $this->columns[$index]->isHaving()) {
149
                $temp .= (!empty($temp) ? ' '.($this->globalSearch ? 'OR' : 'AND').' ' : '').
150
                    '('.$this->columns[$index]->where($query, $filter['search']['value']).')';
151
            }
152
        }
153
154
        return $temp;
155
    }
156
157
    /**
158
     * @author Mathieu Petrini <[email protected]>
159
     *
160
     * @param QueryBuilder $query
161
     * @param bool         $withAlias (optional) (default=true)
162
     *
163
     * @return string
164
     */
165
    private function processColumnIdentifier(QueryBuilder &$query, bool $withAlias = true): string
166
    {
167
        return $query->getRootAliases()[0].'.'.$this->identifier.($withAlias ? ' AS '.$this->nameIdentifier : '');
168
    }
169
170
    /**
171
     * @author Mathieu Petrini <[email protected]>
172
     *
173
     * @param QueryBuilder $query
174
     * @param Column       $column
175
     */
176
    private function processColumnSelect(QueryBuilder &$query, Column $column): void
177
    {
178
        $query->addSelect($column->getName().' AS '.$column->getAlias());
179
    }
180
181
    /**
182
     * @author Mathieu Petrini <[email protected]>
183
     *
184
     * @param QueryBuilder $query
185
     * @param int          $index
186
     * @param string       $direction
187
     *
188
     * @return Datatable
189
     */
190
    private function orderBy(QueryBuilder &$query, int $index, string $direction): self
191
    {
192
        $query->orderBy(
193
            \array_slice($this->columns, $index, 1)[0]->getAlias(),
194
            $direction
195
        );
196
197
        return $this;
198
    }
199
200
    /**
201
     * @param QueryBuilder $query
202
     * @param array        $filters
203
     *
204
     * @return Datatable
205
     */
206
    private function limit(QueryBuilder &$query, array $filters): self
207
    {
208
        $query->setFirstResult($filters['start'] ?? 0)
209
            ->setMaxResults($filters['length'] ?? $this->resultPerPage);
210
211
        return $this;
212
    }
213
214
    /**
215
     * @author Mathieu Petrini <[email protected]>
216
     *
217
     * @param QueryBuilder $query
218
     *
219
     * @return int
220
     */
221
    private function count(QueryBuilder $query): int
222
    {
223
        $query = clone $query;
224
        $result = $query->select('COUNT(DISTINCT '.$this->processColumnIdentifier($query, false).') as count')
225
            ->resetDQLPart('orderBy')
226
            ->resetDQLPart('groupBy')
227
            ->setFirstResult(null)
228
            ->setMaxResults(null)
229
            ->getQuery()
230
            ->getScalarResult();
231
232
        return !empty($result) ?
233
            (int) $result[0]['count'] :
234
            0;
235
    }
236
237
    /**
238
     * PROTECTED METHODS.
239
     */
240
241
    /**
242
     * @author Mathieu Petrini <[email protected]>
243
     *
244
     * @param QueryBuilder $query
245
     * @param array        $filters
246
     *
247
     * @return QueryBuilder
248
     *
249
     * @throws Exception\ResolveColumnNotHandle
250
     * @throws Exception\UnfilterableColumn
251
     * @throws Exception\WhereColumnNotHandle
252
     */
253
    protected function createFoundationQuery(QueryBuilder &$query, array $filters): QueryBuilder
254
    {
255
        // If global search we erase all specific where and only keep the unified filter
256
        if ($this->globalSearch && isset($filters['search']) && !empty($filters['search'][Column::GLOBAL_ALIAS])) {
257
            $filters = $this->createGlobalFilters($filters);
258
        }
259
260
        $temp = isset($filters['columns']) ?
261
            $this->createWherePart($query, $filters) :
262
            '';
263
264
        $having = isset($filters['columns']) ?
265
            $this->createHavingPart($query, $filters) :
266
            '';
267
268
        if (!empty($temp)) {
269
            $query->andWhere($temp);
270
        }
271
272
        return !empty($having) ?
273
            $query->andHaving($having) :
274
            $query;
275
    }
276
277
    /**
278
     * @return QueryBuilder
279
     */
280
    protected function createQueryResult(): QueryBuilder
281
    {
282
        $query = clone $this->query;
283
        $query->select($this->processColumnIdentifier($query));
284
        foreach ($this->columns as $column) {
285
            $this->processColumnSelect($query, $column);
286
        }
287
288
        return $query;
289
    }
290
291
    /**
292
     * @author Mathieu Petrini <[email protected]>
293
     *
294
     * @param QueryBuilder $query
295
     * @param int          $index
296
     * @param string       $direction
297
     *
298
     * @return array
299
     */
300
    protected function result(
301
        QueryBuilder &$query,
302
        int $index,
303
        string $direction
304
    ): array {
305
        $this->orderBy($query, $index, $direction);
306
307
        return $query->getQuery()
308
            ->getResult();
309
    }
310
311
    /**
312
     * PUBLIC METHODS.
313
     */
314
315
    /**
316
     * @author Mathieu Petrini <[email protected]>
317
     *
318
     * @param array $filters
319
     *
320
     * @return array
321
     *
322
     * @throws Exception\ResolveColumnNotHandle
323
     * @throws Exception\UnfilterableColumn
324
     * @throws Exception\WhereColumnNotHandle
325
     */
326
    public function get(array $filters): array
327
    {
328
        $query = $this->createQueryResult();
329
        $this->createFoundationQuery($query, $filters);
330
331
        $data = $this->limit($query, $filters)->result(
332
            $query,
333
            isset($filters['order']) ?
334
                $filters['order'][0]['column'] :
335
                0,
336
            isset($filters['order']) ?
337
                $filters['order'][0]['dir'] :
338
                'ASC'
339
        );
340
341
        return array(
342
            'recordsTotal' => $this->count($query),
343
            'recordsFiltered' => \count($data),
344
            'data' => $data,
345
        );
346
    }
347
348
    /**
349
     * GETTERS / SETTERS.
350
     */
351
352
    /**
353
     * @return string
354
     */
355
    public function getNameIdentifier(): string
356
    {
357
        return $this->nameIdentifier;
358
    }
359
360
    /**
361
     * @param string $nameIdentifier
362
     *
363
     * @return Datatable
364
     */
365
    public function setNameIdentifier(string $nameIdentifier): self
366
    {
367
        $this->nameIdentifier = $nameIdentifier;
368
369
        return $this;
370
    }
371
372
    /**
373
     * @param bool $globalSearch
374
     *
375
     * @return Datatable
376
     */
377
    public function setGlobalSearch(bool $globalSearch): self
378
    {
379
        $this->globalSearch = $globalSearch;
380
381
        return $this;
382
    }
383
}
384