Test Failed
Push — master ( cedcc2...4426b4 )
by Mathieu
06:30 queued 11s
created

Datatable::processColumnIdentifier()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 2
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 array        $filters
162
     *
163
     * @return string
164
     *
165
     * @throws Exception\ResolveColumnNotHandle
166
     * @throws Exception\UnfilterableColumn
167
     * @throws Exception\WhereColumnNotHandle
168
     */
169
    private function createHavingPart(QueryBuilder &$query, array $filters): string
170
    {
171
        $temp = '';
172
173
        foreach ($filters['columns'] as $index => $filter) {
174
            if (isset($this->columns[$index]) && !empty($filter['search']['value']) && $this->columns[$index]->isHaving()) {
175
                $temp .= (!empty($temp) ? ' '.($this->globalSearch ? 'OR' : 'AND').' ' : '').
176
                    '('.$this->columns[$index]->where($query, $filter['search']['value']).')';
177
            }
178
        }
179
180
        return $temp;
181
    }
182
183
    /**
184
     * @author Mathieu Petrini <[email protected]>
185
     *
186
     * @param QueryBuilder $query
187
     * @param bool         $withAlias (optional) (default=true)
188
     *
189
     * @return string
190
     */
191
    private function processColumnIdentifier(QueryBuilder &$query, bool $withAlias = true): string
192
    {
193
        return $query->getRootAliases()[0].'.'.$this->identifier.($withAlias ? ' AS '.$this->nameIdentifier : '');
194
    }
195
196
    /**
197
     * @author Mathieu Petrini <[email protected]>
198
     *
199
     * @param QueryBuilder $query
200
     * @param Column       $column
201
     */
202
    private function processColumnSelect(QueryBuilder &$query, Column $column): void
203
    {
204
        $query->addSelect($column->getName().' AS '.$column->getAlias());
205
    }
206
207
    /**
208
     * @author Mathieu Petrini <[email protected]>
209
     *
210
     * @param QueryBuilder $query
211
     * @param int          $index
212
     * @param string       $direction
213
     *
214
     * @return Datatable
215
     */
216
    private function orderBy(QueryBuilder &$query, int $index, string $direction): self
217
    {
218
        $query->orderBy(
219
            \array_slice($this->columns, $index, 1)[0]->getAlias(),
220
            $direction
221
        );
222
223
        return $this;
224
    }
225
226
    /**
227
     * @param QueryBuilder $query
228
     * @param array        $filters
229
     *
230
     * @return Datatable
231
     */
232
    private function limit(QueryBuilder &$query, array $filters): self
233
    {
234
        $query->setFirstResult($filters['start'] ?? 0)
235
            ->setMaxResults($filters['length'] ?? $this->resultPerPage);
236
237
        return $this;
238
    }
239
240
    /**
241
     * @author Mathieu Petrini <[email protected]>
242
     *
243
     * @param QueryBuilder $query
244
     *
245
     * @return int
246
     */
247
    private function count(QueryBuilder $query): int
248
    {
249
        $query = clone $query;
250
        $result = $query->select('COUNT(DISTINCT '.$this->processColumnIdentifier($query, false).') as count')
251
            ->resetDQLPart('orderBy')
252
            ->resetDQLPart('groupBy')
253
            ->setFirstResult(null)
254
            ->setMaxResults(null)
255
            ->getQuery()
256
            ->getScalarResult();
257
258
        return !empty($result) ?
259
            (int) $result[0]['count'] :
260
            0;
261
    }
262
263
    /**
264
     * PROTECTED METHODS.
265
     */
266
267
    /**
268
     * @author Mathieu Petrini <[email protected]>
269
     *
270
     * @param QueryBuilder $query
271
     * @param array        $filters
272
     *
273
     * @return QueryBuilder
274
     *
275
     * @throws Exception\ResolveColumnNotHandle
276
     * @throws Exception\UnfilterableColumn
277
     * @throws Exception\WhereColumnNotHandle
278
     */
279
    protected function createFoundationQuery(QueryBuilder &$query, array $filters): QueryBuilder
280
    {
281
        // If global search we erase all specific where and only keep the unified filter
282
        if ($this->globalSearch && isset($filters['search']) && !empty($filters['search'][Column::GLOBAL_ALIAS])) {
283
            $filters = $this->createGlobalFilters($filters);
284
        }
285
286
        $temp = isset($filters['columns']) ?
287
            $this->createWherePart($query, $filters) :
288
            '';
289
290
        $having = isset($filters['columns']) ?
291
            $this->createHavingPart($query, $filters) :
292
            '';
293
294
        if (!empty($temp)) {
295
            $query->andWhere($temp);
296
        }
297
298
        return !empty($having) ?
299
            $query->andHaving($having) :
300
            $query;
301
    }
302
303
    /**
304
     * @return QueryBuilder
305
     */
306
    protected function createQueryResult(): QueryBuilder
307
    {
308
        $query = clone $this->query;
309
        $query->select($this->processColumnIdentifier($query));
310
        foreach ($this->columns as $column) {
311
            $this->processColumnSelect($query, $column);
312
        }
313
314
        return $query;
315
    }
316
317
    /**
318
     * @author Mathieu Petrini <[email protected]>
319
     *
320
     * @param QueryBuilder $query
321
     * @param int          $index
322
     * @param string       $direction
323
     *
324
     * @return array
325
     */
326
    protected function result(
327
        QueryBuilder &$query,
328
        int $index,
329
        string $direction
330
    ): array {
331
        $this->orderBy($query, $index, $direction);
332
333
        return $query->getQuery()
334
            ->getResult();
335
    }
336
337
    /**
338
     * PUBLIC METHODS.
339
     */
340
341
    /**
342
     * @author Mathieu Petrini <[email protected]>
343
     *
344
     * @param array $filters
345
     *
346
     * @return array
347
     *
348
     * @throws Exception\ResolveColumnNotHandle
349
     * @throws Exception\UnfilterableColumn
350
     * @throws Exception\WhereColumnNotHandle
351
     */
352
    public function get(array $filters): array
353
    {
354
        $query = $this->createQueryResult();
355
        $this->createFoundationQuery($query, $filters);
356
357
        $data = $this->limit($query, $filters)->result(
358
            $query,
359
            isset($filters['order']) ?
360
                $filters['order'][0]['column'] :
361
                0,
362
            isset($filters['order']) ?
363
                $filters['order'][0]['dir'] :
364
                'ASC'
365
        );
366
367
        return array(
368
            'recordsTotal' => $this->count($query),
369
            'recordsFiltered' => \count($data),
370
            'data' => $data,
371
        );
372
    }
373
374
    /**
375
     * GETTERS / SETTERS.
376
     */
377
378
    /**
379
     * @return string
380
     */
381
    public function getNameIdentifier(): string
382
    {
383
        return $this->nameIdentifier;
384
    }
385
386
    /**
387
     * @param string $nameIdentifier
388
     *
389
     * @return Datatable
390
     */
391
    public function setNameIdentifier(string $nameIdentifier): self
392
    {
393
        $this->nameIdentifier = $nameIdentifier;
394
395
        return $this;
396
    }
397
398
    /**
399
     * @param bool $globalSearch
400
     *
401
     * @return Datatable
402
     */
403
    public function setGlobalSearch(bool $globalSearch): self
404
    {
405
        $this->globalSearch = $globalSearch;
406
407
        return $this;
408
    }
409
}
410