Datatable::orderBy()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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