Passed
Pull Request — master (#54)
by Damien
03:27
created

Query::getOrderBy()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
dl 0
loc 3
c 1
b 0
f 0
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
namespace DH\Auditor\Provider\Doctrine\Persistence\Reader;
4
5
use DH\Auditor\Exception\InvalidArgumentException;
6
use DH\Auditor\Model\Entry;
7
use DH\Auditor\Provider\Doctrine\Persistence\Helper\SchemaHelper;
8
use DH\Auditor\Provider\Doctrine\Persistence\Reader\Filter\DateRangeFilter;
9
use DH\Auditor\Provider\Doctrine\Persistence\Reader\Filter\FilterInterface;
10
use DH\Auditor\Provider\Doctrine\Persistence\Reader\Filter\RangeFilter;
11
use DH\Auditor\Provider\Doctrine\Persistence\Reader\Filter\SimpleFilter;
12
use Doctrine\DBAL\Connection;
13
use Doctrine\DBAL\Query\QueryBuilder;
14
use Exception;
15
16
class Query
17
{
18
    public const TYPE = 'type';
19
    public const CREATED_AT = 'created_at';
20
    public const TRANSACTION_HASH = 'transaction_hash';
21
    public const OBJECT_ID = 'object_id';
22
    public const USER_ID = 'blame_id';
23
    public const ID = 'id';
24
    public const DISCRIMINATOR = 'discriminator';
25
26
    /**
27
     * @var array
28
     */
29
    private $filters = [];
30
31
    /**
32
     * @var array
33
     */
34
    private $orderBy = [];
35
36
    /**
37
     * @var Connection
38
     */
39
    private $connection;
40
41
    /**
42
     * @var string
43
     */
44
    private $table;
45
46
    /**
47
     * @var int
48
     */
49
    private $offset = 0;
50
51
    /**
52
     * @var int
53
     */
54
    private $limit = 0;
55
56
    public function __construct(string $table, Connection $connection)
57
    {
58
        $this->connection = $connection;
59
        $this->table = $table;
60
61
        foreach ($this->getSupportedFilters() as $filterType) {
62
            $this->filters[$filterType] = [];
63
        }
64
    }
65
66
    public function execute(): array
67
    {
68
        $queryBuilder = $this->buildQueryBuilder();
69
        $statement = $queryBuilder->executeQuery();
70
71
        $result = [];
72
        foreach ($statement->fetchAllAssociative() as $row) {
73
            $result[] = Entry::fromArray($row);
74
        }
75
76
        return $result;
77
    }
78
79
    public function count(): int
80
    {
81
        $queryBuilder = $this->buildQueryBuilder();
82
83
        try {
84
            $result = $queryBuilder
85
                ->resetQueryPart('select')
86
                ->resetQueryPart('orderBy')
87
                ->setMaxResults(null)
88
                ->setFirstResult(null)
89
                ->select('COUNT(id)')
90
                ->executeQuery()
91
                ->fetchOne()
92
            ;
93
        } catch (Exception $e) {
94
            $result = false;
95
        }
96
97
        return false === $result ? 0 : $result;
98
    }
99
100
    public function addFilter(FilterInterface $filter): self
101
    {
102
        $this->checkFilter($filter->getName());
103
        $this->filters[$filter->getName()][] = $filter;
104
105
        return $this;
106
    }
107
108
    public function addOrderBy(string $field, string $direction = 'DESC'): self
109
    {
110
        $this->checkFilter($field);
111
112
        if (!\in_array($direction, ['ASC', 'DESC'], true)) {
113
            throw new InvalidArgumentException('Invalid sort direction, allowed value: ASC, DESC');
114
        }
115
116
        $this->orderBy[$field] = $direction;
117
118
        return $this;
119
    }
120
121
    public function limit(int $limit, int $offset = 0): self
122
    {
123
        if (0 > $limit) {
124
            throw new InvalidArgumentException('Limit cannot be negative.');
125
        }
126
        if (0 > $offset) {
127
            throw new InvalidArgumentException('Offset cannot be negative.');
128
        }
129
130
        $this->limit = $limit;
131
        $this->offset = $offset;
132
133
        return $this;
134
    }
135
136
    public function getSupportedFilters(): array
137
    {
138
        return array_keys(SchemaHelper::getAuditTableIndices('fake'));
139
    }
140
141
    public function getFilters(): array
142
    {
143
        return $this->filters;
144
    }
145
146
    public function getOrderBy(): array
147
    {
148
        return $this->orderBy;
149
    }
150
151
    public function getLimit(): array
152
    {
153
        return [$this->limit, $this->offset];
154
    }
155
156
    private function buildQueryBuilder(): QueryBuilder
157
    {
158
        $queryBuilder = $this->connection->createQueryBuilder();
159
        $queryBuilder
160
            ->select('*')
161
            ->from($this->table, 'at')
162
        ;
163
164
        // build WHERE clause(s)
165
        $queryBuilder = $this->buildWhere($queryBuilder);
166
167
        // build ORDER BY part
168
        $queryBuilder = $this->buildOrderBy($queryBuilder);
169
170
        // build LIMIT part
171
        return $this->buildLimit($queryBuilder);
172
    }
173
174
    private function groupFilters(array $filters): array
175
    {
176
        $grouped = [];
177
178
        foreach ($filters as $filter) {
179
            $class = \get_class($filter);
180
            if (!isset($grouped[$class])) {
181
                $grouped[$class] = [];
182
            }
183
            $grouped[$class][] = $filter;
184
        }
185
186
        return $grouped;
187
    }
188
189
    private function mergeSimpleFilters(array $filters): SimpleFilter
190
    {
191
        $merged = [];
192
        $name = null;
193
194
        foreach ($filters as $filter) {
195
            if (null === $name) {
196
                $name = $filter->getName();
197
            }
198
199
            if (\is_array($filter->getValue())) {
200
                $merged = array_merge($merged, $filter->getValue());
201
            } else {
202
                $merged[] = $filter->getValue();
203
            }
204
        }
205
206
        return new SimpleFilter($name, $merged);
207
    }
208
209
    private function buildWhere(QueryBuilder $queryBuilder): QueryBuilder
210
    {
211
        foreach ($this->filters as $name => $rawFilters) {
212
            if (0 === \count($rawFilters)) {
213
                continue;
214
            }
215
216
            // group filters by class
217
            $grouped = $this->groupFilters($rawFilters);
218
219
            foreach ($grouped as $class => $filters) {
220
                switch ($class) {
221
                    case SimpleFilter::class:
222
                        $filters = [$this->mergeSimpleFilters($filters)];
223
224
                        break;
225
                    case RangeFilter::class:
226
                    case DateRangeFilter::class:
227
                        break;
228
                }
229
230
                foreach ($filters as $filter) {
231
                    $data = $filter->getSQL();
232
233
                    $queryBuilder->andWhere($data['sql']);
234
235
                    foreach ($data['params'] as $name => $value) {
0 ignored issues
show
Comprehensibility Bug introduced by
$name is overwriting a variable from outer foreach loop.
Loading history...
236
                        if (\is_array($value)) {
237
                            $queryBuilder->setParameter($name, $value, Connection::PARAM_STR_ARRAY);
238
                        } else {
239
                            $queryBuilder->setParameter($name, $value);
240
                        }
241
                    }
242
                }
243
            }
244
        }
245
246
        return $queryBuilder;
247
    }
248
249
    private function buildOrderBy(QueryBuilder $queryBuilder): QueryBuilder
250
    {
251
        foreach ($this->orderBy as $field => $direction) {
252
            $queryBuilder->addOrderBy($field, $direction);
253
        }
254
255
        return $queryBuilder;
256
    }
257
258
    private function buildLimit(QueryBuilder $queryBuilder): QueryBuilder
259
    {
260
        if (0 < $this->limit) {
261
            $queryBuilder->setMaxResults($this->limit);
262
        }
263
        if (0 < $this->offset) {
264
            $queryBuilder->setFirstResult($this->offset);
265
        }
266
267
        return $queryBuilder;
268
    }
269
270
    private function checkFilter(string $filter): void
271
    {
272
        if (!\in_array($filter, $this->getSupportedFilters(), true)) {
273
            throw new InvalidArgumentException(sprintf('Unsupported "%s" filter, allowed filters: %s.', $filter, implode(', ', $this->getSupportedFilters())));
274
        }
275
    }
276
}
277