Passed
Push — master ( d6ab08...9ab969 )
by Radu
01:35
created

AbstractDataTablesDatabase::getResponse()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 48
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
eloc 31
c 6
b 0
f 0
dl 0
loc 48
rs 8.8017
cc 6
nc 8
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace WebServCo\Framework\DataTables;
6
7
use WebServCo\Framework\Database\Order as DatabaseOrder;
8
use WebServCo\Framework\Exceptions\DatabaseException;
9
use WebServCo\Framework\Interfaces\ArrayObjectInterface;
10
use WebServCo\Framework\Interfaces\DatabaseInterface;
11
12
abstract class AbstractDataTablesDatabase implements \WebServCo\Framework\Interfaces\DataTablesInterface
13
{
14
15
    protected DatabaseInterface $db;
16
17
    /**
18
    * @param string $orderPart,
19
    */
20
    abstract protected function getQuery(string $searchPart, string $orderPart, string $limitPart): string;
21
22
    abstract protected function getRecordsTotalQuery(): string;
23
24
    public function __construct(DatabaseInterface $db)
25
    {
26
        $this->db = $db;
27
    }
28
29
    public function getResponse(Request $request): Response
30
    {
31
        $params = [];
32
        $limitPart = '';
33
34
        $columnArrayObject = $request->getColumns();
35
        [$searchPart, $searchParams] = $this->getSearchQueryPart($columnArrayObject);
36
37
        if (!\is_string($searchPart)) {
0 ignored issues
show
introduced by
The condition is_string($searchPart) is always true.
Loading history...
38
            throw new \InvalidArgumentException('"searchQueryPart" is not a string');
39
        }
40
        if (!\is_array($searchParams)) {
0 ignored issues
show
introduced by
The condition is_array($searchParams) is always true.
Loading history...
41
            throw new \InvalidArgumentException('"searchParams" is not an array');
42
        }
43
44
        $params = \array_merge($params, $searchParams);
45
46
        $orderArrayObject = $request->getOrder();
47
        $orderPart = $this->getOrderQueryPart($columnArrayObject, $orderArrayObject);
48
49
        $length = $request->getLength();
50
        if (-1 !== $length) {
51
            $limitPart = 'LIMIT ?, ?';
52
            $params[] = $request->getStart();
53
            $params[] = $length;
54
        }
55
        $query = $this->getQuery($searchPart, $orderPart, $limitPart);
56
        try {
57
            $pdoStatement = $this->db->query($query, $params);
58
        } catch (DatabaseException $e) {
59
            // Rethrow in order to pinpoint the query location in the logs.
60
            throw new DatabaseException($e->getMessage(), $e);
61
        }
62
63
        $data = $this->getData($columnArrayObject, $pdoStatement);
64
65
        $recordsFiltered = $this->getRecordsFiltered();
66
67
        // Extra query for total only if there is a search, otherwise the total is equal to the $recordsFiltered
68
        $recordsTotal = $searchPart
69
            ? $this->getRecordsTotal()
70
            : $recordsFiltered;
71
72
        return new Response(
73
            $request->getDraw(),
74
            $recordsTotal,
75
            $recordsFiltered,
76
            $data,
77
        );
78
    }
79
80
    protected function assertColumnArrayObject(ArrayObjectInterface $arrayObject): bool
81
    {
82
        if (!$arrayObject instanceof ColumnArrayObject) {
83
            throw new \InvalidArgumentException(\sprintf('Object is not an instance of %s.', 'ColumnArrayObject'));
84
        }
85
        return true;
86
    }
87
88
    protected function assertOrderArrayObject(ArrayObjectInterface $arrayObject): bool
89
    {
90
        if (!$arrayObject instanceof OrderArrayObject) {
91
            throw new \InvalidArgumentException(\sprintf('Object is not an instance of %s.', 'OrderArrayObject'));
92
        }
93
        return true;
94
    }
95
96
    /**
97
    * @return array<int,array<int|string,mixed>>
98
    */
99
    protected function getData(ArrayObjectInterface $columnArrayObject, \PDOStatement $pdoStatement): array
100
    {
101
        $this->assertColumnArrayObject($columnArrayObject);
102
        $data = [];
103
        while ($row = $pdoStatement->fetch(\PDO::FETCH_ASSOC)) {
104
            $item = [];
105
            foreach ($columnArrayObject as $column) {
106
                $name = $column->getData();
107
                $item[$name] = $row[$name] ?? null;
108
            }
109
            $data[] = $item;
110
        }
111
        return $data;
112
    }
113
114
    protected function getDatabaseColumnName(string $dataTablesColumnName): string
115
    {
116
        try {
117
            return $this->db->escapeIdentifier($dataTablesColumnName);
118
        } catch (DatabaseException $e) {
119
            // Rethrow in order to pinpoint the query location in the logs.
120
            throw new DatabaseException($e->getMessage(), $e);
121
        }
122
    }
123
124
    protected function getOrderQueryPart(
125
        ArrayObjectInterface $columnArrayObject,
126
        ArrayObjectInterface $orderArrayObject
127
    ): string {
128
        $this->assertColumnArrayObject($columnArrayObject);
129
        $this->assertOrderArrayObject($orderArrayObject);
130
        $items = [];
131
        foreach ($orderArrayObject as $order) {
132
            $columnKey = (string) $order->getColumn();
133
            $column = $columnArrayObject->offsetGet($columnKey);
134
            if (!($column instanceof Column)) {
135
                continue;
136
            }
137
138
            if (!$column->getOrderable()) {
139
                continue;
140
            }
141
142
            $dir = \strtoupper($order->getDir());
143
            $dir = \in_array($dir, [DatabaseOrder::ASC, DatabaseOrder::DESC], true)
144
                ? $dir
145
                : DatabaseOrder::ASC;
146
            $columnName = $this->getDatabaseColumnName($column->getData());
147
            $items[] = \sprintf(' %s %s', $columnName, $dir);
148
        }
149
        if (!$items) {
150
            // No order, or none of the columns are actually orderable
151
            return '';
152
        }
153
        return \sprintf(
154
            'ORDER BY%s',
155
            \implode(",", $items),
156
        );
157
    }
158
159
    protected function getRecordsFiltered(): int
160
    {
161
        try {
162
            return (int) $this->db->getColumn("SELECT FOUND_ROWS()", [], 0);
163
        } catch (DatabaseException $e) {
164
            // Rethrow in order to pinpoint the query location in the logs.
165
            throw new DatabaseException($e->getMessage(), $e);
166
        }
167
    }
168
169
    protected function getRecordsTotal(): int
170
    {
171
        try {
172
            return (int) $this->db->getColumn( // grand total - query without the search, order, limits
173
                $this->getRecordsTotalQuery(),
174
                [],
175
            );
176
        } catch (DatabaseException $e) {
177
            // Rethrow in order to pinpoint the query location in the logs.
178
            throw new DatabaseException($e->getMessage(), $e);
179
        }
180
    }
181
182
    /**
183
    * @return array<int,array<int,string>|string>
184
    */
185
    protected function getSearchQueryPart(ArrayObjectInterface $columnArrayObject): array
186
    {
187
        $this->assertColumnArrayObject($columnArrayObject);
188
        $query = '';
189
        $params = [];
190
        foreach ($columnArrayObject as $column) {
191
            if (!$column->getSearchable()) {
192
                continue;
193
            }
194
195
            $search = $column->getSearch();
196
            $searchValue = $search->getValue();
197
            if ('' === $searchValue) {
198
                continue;
199
            }
200
            // make sure it works also for "0"
201
            $query .= \sprintf(
202
                " AND %s LIKE ?",
203
                $this->getDatabaseColumnName($column->getData()),
204
            );
205
            $params[] = \sprintf('%%%s%%', $searchValue);
206
        }
207
        return [$query, $params];
208
    }
209
}
210