Test Failed
Push — master ( 3ef5fd...d16714 )
by Mathieu
04:17
created

Datatable::createQueryResult()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 9
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
1
<?php
2
3
namespace DoctrineDatatable;
4
5
use Doctrine\ORM\Query\Expr;
6
use Doctrine\ORM\QueryBuilder;
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 QueryBuilder
18
     */
19
    private $query;
20
21
    /**
22
     * @var string
23
     */
24
    private $identifier;
25
26
    /**
27
     * @var Column[]
28
     */
29
    private $columns;
30
31
    /**
32
     * @var int
33
     */
34
    private $resultPerPage;
35
36
    /**
37
     * @var string
38
     */
39
    private $nameIdentifier;
40
41
    /**
42
     * @var bool
43
     */
44
    private $globalSearch;
45
46
    private const DEFAULT_NAME_IDENTIFIER = 'DT_RowID';
47
48
    public const RESULT_PER_PAGE = 30;
49
50
    /**
51
     * Datatable constructor.
52
     *
53
     * @author Mathieu Petrini <[email protected]>
54
     *
55
     * @param QueryBuilder $query
56
     * @param string       $identifier
57
     * @param array        $columns
58
     * @param int|null     $resultPerPage
59
     *
60
     * @throws MinimumColumn
61
     */
62
    public function __construct(
63
        QueryBuilder $query,
64
        string $identifier,
65
        array $columns,
66
        ?int $resultPerPage = self::RESULT_PER_PAGE
67
    ) {
68
        if (empty($columns)) {
69
            throw new MinimumColumn();
70
        }
71
        $this->query = $query;
72
        $this->identifier = $identifier;
73
        $this->columns = $columns;
74
        $this->resultPerPage = $resultPerPage ?? self::RESULT_PER_PAGE;
75
        $this->nameIdentifier = self::DEFAULT_NAME_IDENTIFIER;
76
        $this->globalSearch = false;
77
    }
78
79
    /**
80
     * PRIVATE METHODS.
81
     */
82
83
    /**
84
     * @author Mathieu Petrini <[email protected]>
85
     *
86
     * @param array $filters
87
     *
88
     * @return array
89
     */
90
    private function createGlobalFilters(array $filters): array
91
    {
92
        $temp = array(
93
            'columns' => array(),
94
        );
95
        array_map(function (Column $column) use ($filters, &$temp) {
0 ignored issues
show
Unused Code introduced by
The parameter $column is not used and could be removed. ( Ignorable by Annotation )

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

95
        array_map(function (/** @scrutinizer ignore-unused */ Column $column) use ($filters, &$temp) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
96
            $temp['columns'][] = array(
97
                'search' => array(
98
                    'value' => $filters['search'][Column::GLOBAL_ALIAS],
99
                ),
100
            );
101
        }, $this->columns);
102
103
        return $temp;
104
    }
105
106
    /**
107
     * @author Mathieu Petrini <[email protected]>
108
     *
109
     * @param QueryBuilder $query
110
     * @param array        $filters
111
     *
112
     * @return string
113
     *
114
     * @throws Exception\ResolveColumnNotHandle
115
     * @throws Exception\UnfilterableColumn
116
     * @throws Exception\WhereColumnNotHandle
117
     */
118
    private function createWherePart(QueryBuilder &$query, array $filters): string
119
    {
120
        $expr = new Expr();
121
        $temp = '';
122
123
        $filt = isset($filters['columns']) ? $filters['columns'] : array();
124
125
        foreach ($filt as $index => $filter) {
126
            $column = isset($this->columns[$index]) ? $this->columns[$index] : null;
127
            if ($column instanceof Column && !empty($filter['search']['value'])) {
128
                $temp .= $this->globalSearch ?
129
                    (!empty($temp) ? ' OR ' : '').$expr->orX($column->where($query, $filter['search']['value'])) :
130
                    (!empty($temp) ? ' AND ' : '').$expr->andX($column->where($query, $filter['search']['value']));
131
            }
132
        }
133
134
        return $temp;
135
    }
136
137
    /**
138
     * @author Mathieu Petrini <[email protected]>
139
     *
140
     * @param QueryBuilder $query
141
     * @param array        $filters
142
     *
143
     * @return QueryBuilder
144
     *
145
     * @throws Exception\ResolveColumnNotHandle
146
     * @throws Exception\UnfilterableColumn
147
     * @throws Exception\WhereColumnNotHandle
148
     */
149
    private function createFoundationQuery(QueryBuilder &$query, array $filters): QueryBuilder
150
    {
151
        // If global search we erase all specific where and only keep the unified filter
152
        if ($this->globalSearch && isset($filters['search']) && !empty($filters['search'][Column::GLOBAL_ALIAS])) {
153
            $filters = $this->createGlobalFilters($filters);
154
        }
155
156
        $temp = $this->createWherePart($query, $filters);
157
158
        return !empty($temp) ?
159
            $query->andWhere($temp) :
160
            $query;
161
    }
162
163
    /**
164
     * @return QueryBuilder
165
     */
166
    private function createQueryResult(): QueryBuilder
167
    {
168
        $query = clone $this->query;
169
        $query->select($this->processColumnIdentifier($query));
170
        foreach ($this->columns as $column) {
171
            $this->processColumnSelect($query, $column);
172
        }
173
174
        return $query;
175
    }
176
177
    /**
178
     * @author Mathieu Petrini <[email protected]>
179
     *
180
     * @param QueryBuilder $query
181
     * @param bool         $withAlias (optional) (default=true)
182
     *
183
     * @return string
184
     */
185
    private function processColumnIdentifier(QueryBuilder &$query, bool $withAlias = true): string
186
    {
187
        return $query->getRootAliases()[0].'.'.$this->identifier.($withAlias ? ' AS '.$this->nameIdentifier : '');
188
    }
189
190
    /**
191
     * @author Mathieu Petrini <[email protected]>
192
     *
193
     * @param QueryBuilder $query
194
     * @param Column       $column
195
     */
196
    private function processColumnSelect(QueryBuilder &$query, Column $column): void
197
    {
198
        $query->addSelect($column->getName().' AS '.$column->getAlias());
199
    }
200
201
    /**
202
     * @author Mathieu Petrini <[email protected]>
203
     *
204
     * @param QueryBuilder $query
205
     * @param int          $index
206
     * @param string       $direction
207
     *
208
     * @return Datatable
209
     */
210
    private function orderBy(QueryBuilder &$query, int $index, string $direction): self
211
    {
212
        $query->orderBy(
213
            \array_slice($this->columns, $index, 1)[0]->getAlias(),
214
            $direction
215
        );
216
217
        return $this;
218
    }
219
220
    /**
221
     * @param QueryBuilder $query
222
     * @param int          $start
223
     * @param int|null     $length
224
     *
225
     * @return Datatable
226
     */
227
    private function limit(QueryBuilder &$query, int $start, int $length = null): self
228
    {
229
        $query->setFirstResult($start)
230
            ->setMaxResults($length ?? $this->resultPerPage);
231
232
        return $this;
233
    }
234
235
    /**
236
     * @author Mathieu Petrini <[email protected]>
237
     *
238
     * @param QueryBuilder $query
239
     * @param int          $index
240
     * @param string       $direction
241
     *
242
     * @return array
243
     */
244
    private function result(
245
        QueryBuilder &$query,
246
        int $index,
247
        string $direction
248
    ): array {
249
        $this->orderBy($query, $index, $direction);
250
251
        return $query->getQuery()
252
            ->getResult();
253
    }
254
255
    /**
256
     * @author Mathieu Petrini <[email protected]>
257
     *
258
     * @param QueryBuilder $query
259
     *
260
     * @return int
261
     *
262
     * @throws \Doctrine\ORM\NonUniqueResultException
263
     */
264
    private function count(QueryBuilder $query): int
265
    {
266
        $query = clone $query;
267
268
        return (int) ($query->select('COUNT(DISTINCT '.$this->processColumnIdentifier($query, false).')')
269
            ->resetDQLPart('orderBy')
270
            ->getQuery()
271
            ->getSingleScalarResult());
272
    }
273
274
    /**
275
     * @author Mathieu Petrini <[email protected]>
276
     *
277
     * @return array
278
     */
279
    private function columns(): array
280
    {
281
        return array_map(static function (Column $column) {
282
            return array(
283
                'data' => $column->getAlias(),
284
            );
285
        }, $this->columns);
286
    }
287
288
    /**
289
     * PUBLIC METHODS.
290
     */
291
292
    /**
293
     * @author Mathieu Petrini <[email protected]>
294
     *
295
     * @param array  $filters
296
     * @param int    $index       (optional) (default=0)
297
     * @param string $direction   (optional) (default='ASC')
298
     * @param bool   $withColumns (optional) (default=false)
299
     *
300
     * @return array
301
     *
302
     * @throws Exception\ResolveColumnNotHandle
303
     * @throws Exception\UnfilterableColumn
304
     * @throws Exception\WhereColumnNotHandle
305
     * @throws \Doctrine\ORM\NonUniqueResultException
306
     */
307
    public function get(
308
        array $filters,
309
        int $index = 0,
310
        string $direction = 'ASC',
311
        bool $withColumns = false
312
    ): array {
313
        $query = $this->createQueryResult();
314
        $this->createFoundationQuery($query, $filters);
315
316
        $data = $this->limit(
317
            $query,
318
            isset($filters['start']) ? $filters['start'] : 0,
319
            isset($filters['length']) ? $filters['length'] : $this->resultPerPage
320
        )->result($query, $index, $direction);
321
322
        $ret = array(
323
            'recordsTotal' => $this->count($query),
324
            'recordsFiltered' => \count($data),
325
            'data' => $data,
326
        );
327
328
        if ($withColumns) {
329
            $ret['columns'] = $this->columns();
330
        }
331
332
        return $ret;
333
    }
334
335
    /**
336
     * GETTERS / SETTERS.
337
     */
338
339
    /**
340
     * @return string
341
     */
342
    public function getNameIdentifier(): string
343
    {
344
        return $this->nameIdentifier;
345
    }
346
347
    /**
348
     * @param string $nameIdentifier
349
     *
350
     * @return Datatable
351
     */
352
    public function setNameIdentifier(string $nameIdentifier): self
353
    {
354
        $this->nameIdentifier = $nameIdentifier;
355
356
        return $this;
357
    }
358
359
    /**
360
     * @param bool $globalSearch
361
     *
362
     * @return Datatable
363
     */
364
    public function setGlobalSearch(bool $globalSearch): self
365
    {
366
        $this->globalSearch = $globalSearch;
367
368
        return $this;
369
    }
370
}
371