• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

contributte / datagrid-nette-database-data-source / #6

14 Mar 2026 11:10AM UTC coverage: 75.0% (-1.1%) from 76.05%
#6

push

github

f3l1x
Makefile: run coverage targets with php

Use the PHP CLI for coverage targets to match the org-wide phpdbg removal tracked in contributte/contributte#73.

165 of 220 relevant lines covered (75.0%)

0.75 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.78
/src/NetteDatabaseDataSource.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Ublaboo\NetteDatabaseDataSource;
6

7
use Nette\Database\Context;
8
use Nette\Database\ResultSet;
9
use PHPSQLParser\PHPSQLParser;
10
use Ublaboo\DataGrid\DataSource\IDataSource;
11
use Ublaboo\DataGrid\Filter\FilterDate;
12
use Ublaboo\DataGrid\Filter\FilterDateRange;
13
use Ublaboo\DataGrid\Filter\FilterMultiSelect;
14
use Ublaboo\DataGrid\Filter\FilterRange;
15
use Ublaboo\DataGrid\Filter\FilterSelect;
16
use Ublaboo\DataGrid\Filter\FilterText;
17
use Ublaboo\DataGrid\Utils\DateTimeHelper;
18
use Ublaboo\DataGrid\Utils\Sorting;
19

20
class NetteDatabaseDataSource implements IDataSource
1 ✔
21
{
22

23
        /**
24
         * @var Context
25
         */
26
        protected $connection;
27

28
        /**
29
         * @var mixed[]
30
         */
31
        protected $data = [];
32

33
        /**
34
         * Query parameters
35
         * @var mixed[]
36
         */
37
        protected $queryParameters;
38

39
        /**
40
         * Own query
41
         * @var string
42
         */
43
        protected $sql;
44

45
        /**
46
         * @var PHPSQLParser
47
         */
48
        protected $sqlParser;
49

50
        /**
51
         * @var QueryHelper
52
         */
53
        protected $queryHelper;
54

55
        /**
56
         * @param mixed[] $params
57
         */
58
        public function __construct(Context $connection, string $sql, array $params = [])
59
        {
60
                $this->connection = $connection;
1 ✔
61
                $this->sql = $sql;
1 ✔
62

63
                $this->queryParameters = $params;
1 ✔
64

65
                $this->queryHelper = new QueryHelper($this->sql);
1 ✔
66
        }
1 ✔
67

68
        /**
69
         * Get current sql + query parameters
70
         *
71
         * @return mixed[]
72
         */
73
        public function getQuery(): array
74
        {
75
                $sql = preg_replace('/_\?\w{13}\?_/', '?', $this->sql);
1 ✔
76

77
                return [$sql, $this->queryParameters];
1 ✔
78
        }
79

80
        /**
81
         * {@inheritDoc}
82
         */
83
        public function filter(array $filters): void
84
        {
85
                foreach ($filters as $filter) {
1 ✔
86
                        if ($filter->isValueSet()) {
1 ✔
87
                                if ($filter->getConditionCallback() !== null) {
1 ✔
88
                                        $this->sql = call_user_func_array(
×
89
                                                $filter->getConditionCallback(),
×
90
                                                [$this->sql, $filter->getValue(), &$this->queryParameters]
×
91
                                        );
92
                                        $this->queryHelper->resetQuery($this->sql);
×
93
                                } else {
94
                                        if ($filter instanceof FilterText) {
1 ✔
95
                                                $this->applyFilterText($filter);
1 ✔
96
                                        } elseif ($filter instanceof FilterMultiSelect) {
1 ✔
97
                                                $this->applyFilterMultiSelect($filter);
×
98
                                        } elseif ($filter instanceof FilterSelect) {
1 ✔
99
                                                $this->applyFilterSelect($filter);
1 ✔
100
                                        } elseif ($filter instanceof FilterDate) {
1 ✔
101
                                                $this->applyFilterDate($filter);
1 ✔
102
                                        } elseif ($filter instanceof FilterDateRange) {
1 ✔
103
                                                $this->applyFilterDateRange($filter);
1 ✔
104
                                        } elseif ($filter instanceof FilterRange) {
1 ✔
105
                                                $this->applyFilterRange($filter);
1 ✔
106
                                        }
107
                                }
108
                        }
109
                }
110
        }
1 ✔
111

112
        public function getCount(): int
113
        {
114
                $sql = $this->queryHelper->getCountSelect();
×
115
                $query = $this->query($sql)->fetch();
×
116

117
                return $query === null
×
118

119
                        ? 0
×
120

121
                        : $query['count'];
×
122
        }
123

124

125
        /**
126
         * @return mixed[]
127
         */
128
        public function getData(): array
129
        {
130
                if ($this->data === []) {
1 ✔
131
                        $this->data = $this->query($this->sql)->fetchAll();
1 ✔
132
                }
133

134
                return $this->data;
1 ✔
135
        }
136

137

138
        /**
139
         * @param mixed[] $condition
140
         */
141
        public function filterOne(array $condition): IDataSource
142
        {
143
                foreach ($condition as $column => $value) {
1 ✔
144
                        $this->applyWhere($column, $value);
1 ✔
145
                }
146

147
                return $this;
1 ✔
148
        }
149

150
        public function limit(int $offset, int $limit): IDataSource
151
        {
152
                $sql = $this->queryHelper->limit($limit, $offset);
×
153

154
                $this->data = $this->query($sql)->fetchAll();
×
155

156
                return $this;
×
157
        }
158

159
        public function sort(Sorting $sorting): IDataSource
160
        {
161
                if (is_callable($sorting->getSortCallback())) {
1 ✔
162
                        call_user_func(
×
163
                                $sorting->getSortCallback(),
×
164
                                $this->sql,
×
165
                                $sorting->getSort()
×
166
                        );
167

168
                        return $this;
×
169
                }
170

171
                $sort = $sorting->getSort();
1 ✔
172

173
                if ($sort !== []) {
1 ✔
174
                        foreach ($sort as $column => $order) {
1 ✔
175
                                $this->sql = $this->queryHelper->orderBy((string) $column, $order);
1 ✔
176
                        }
177
                }
178

179
                return $this;
1 ✔
180
        }
181

182
        /**
183
         * @return mixed[]
184
         */
185
        protected function addParams(string $sql): array
186
        {
187
                $params = $this->queryParameters;
1 ✔
188

189
                array_unshift($params, $sql);
1 ✔
190

191
                return $params;
1 ✔
192
        }
193

194
        /**
195
         * Call Context::query() with current sql + params
196
         *
197
         * @return ResultSet<mixed>
198
         */
199
        protected function query(string $sql): ResultSet
200
        {
201
                $sql = preg_replace('/_\?\w{13}\?_/', '?', $sql);
1 ✔
202

203
                if ($sql === null) {
1 ✔
204
                        throw new \UnexpectedValueException;
×
205
                }
206

207
                return $this->connection->query(...$this->addParams($sql));
1 ✔
208
        }
209

210
        /**
211
         * @param mixed $value
212
         */
213
        protected function applyWhere(string $column, $value, string $operator = '='): void
214
        {
215
                $id = '_?' . uniqid() . '?_';
1 ✔
216

217
                $this->sql = $this->queryHelper->where($column, $id, $operator);
1 ✔
218

219
                /**
220
                 * Find occurances of placeholders ('?') before inserted placeholder
221
                 */
222
                [$before,] = explode($id, $this->sql);
1 ✔
223

224
                if ($before === null) {
1 ✔
225
                        throw new \UnexpectedValueException;
×
226
                }
227

228
                $occurances = substr_count($before, '?');
1 ✔
229

230
                /**
231
                 * Add $value to query parameters at proper place
232
                 */
233
                if ($occurances === 0) {
1 ✔
234
                        array_unshift($this->queryParameters, $value);
1 ✔
235
                } else {
236
                        array_splice($this->queryParameters, $occurances, 0, $value);
1 ✔
237
                }
238
        }
1 ✔
239
        /********************************************************************************
240
         *                          IDataSource implementation *
241
         ********************************************************************************/
242

243

244
        protected function applyFilterDate(FilterDate $filter): void
245
        {
246
                $conditions = $filter->getCondition();
1 ✔
247

248
                $date = \DateTime::createFromFormat(
1 ✔
249
                        $filter->getPhpFormat(),
1 ✔
250
                        $conditions[$filter->getColumn()]
1 ✔
251
                );
252

253
                if ($date === false) {
1 ✔
254
                        throw new \UnexpectedValueException;
×
255
                }
256

257
                $this->applyWhere("DATE({$filter->getColumn()})", $date->format('Y-m-d'));
1 ✔
258
        }
1 ✔
259

260
        protected function applyFilterDateRange(FilterDateRange $filter): void
261
        {
262
                $conditions = $filter->getCondition();
1 ✔
263

264
                $valueFrom = $conditions[$filter->getColumn()]['from'];
1 ✔
265
                $valueTo = $conditions[$filter->getColumn()]['to'];
1 ✔
266

267
                if ($valueFrom) {
1 ✔
268
                        $dateFrom = DateTimeHelper::tryConvertToDateTime($valueFrom, [$filter->getPhpFormat()]);
1 ✔
269
                        $dateFrom->setTime(0, 0, 0);
1 ✔
270

271
                        $dateFrom->setTime(0, 0, 0);
1 ✔
272

273
                        $this->applyWhere("DATE({$filter->getColumn()})", $dateFrom->format('Y-m-d'), '>=');
1 ✔
274
                }
275

276
                if ($valueTo) {
1 ✔
277
                        $dateTo = DateTimeHelper::tryConvertToDateTime($valueTo, [$filter->getPhpFormat()]);
1 ✔
278
                        $dateTo->setTime(23, 59, 59);
1 ✔
279

280
                        $this->applyWhere("DATE({$filter->getColumn()})", $dateTo->format('Y-m-d'), '<=');
1 ✔
281
                }
282
        }
1 ✔
283

284
        protected function applyFilterRange(FilterRange $filter): void
285
        {
286
                $conditions = $filter->getCondition();
1 ✔
287

288
                $valueFrom = $conditions[$filter->getColumn()]['from'];
1 ✔
289
                $valueTo = $conditions[$filter->getColumn()]['to'];
1 ✔
290

291
                if ($valueFrom !== null && $valueFrom !== '') {
1 ✔
292
                        $this->applyWhere($filter->getColumn(), $valueFrom, '>=');
1 ✔
293
                }
294

295
                if ($valueTo !== null && $valueTo !== '') {
1 ✔
296
                        $this->applyWhere($filter->getColumn(), $valueTo, '<=');
1 ✔
297
                }
298
        }
1 ✔
299

300
        protected function applyFilterText(FilterText $filter): void
301
        {
302
                $or = [];
1 ✔
303
                $args = [];
1 ✔
304
                $big_or = '(';
1 ✔
305
                $big_or_args = [];
1 ✔
306
                $condition = $filter->getCondition();
1 ✔
307
                $isSeachExact = $filter->isExactSearch();
1 ✔
308
                $operator = $isSeachExact
1 ✔
309
                        ? '='
1 ✔
310
                        : 'LIKE';
1 ✔
311

312
                foreach ($condition as $column => $value) {
1 ✔
313

314
                        $like = '(';
1 ✔
315
                        $args = [];
1 ✔
316

317
                        if ($filter->hasSplitWordsSearch() === false) {
1 ✔
318
                                $like .= "$column $operator ? OR ";
1 ✔
319
                                $args[] = $isSeachExact
1 ✔
320
                                        ? $value
1 ✔
321
                                        : "%$value%";
1 ✔
322
                        } else {
323
                                $words = explode(' ', $value);
1 ✔
324

325
                                foreach ($words as $word) {
1 ✔
326
                                        $like .= "$column $operator ? OR ";
1 ✔
327
                                        $args[] = $isSeachExact
1 ✔
328
                                                ? $word
1 ✔
329
                                                : "%$word%";
1 ✔
330
                                }
331
                        }
332

333
                        $like = substr($like, 0, strlen($like) - 4) . ')';
1 ✔
334

335
                        $or[] = $like;
1 ✔
336
                        $big_or .= "$like OR ";
1 ✔
337
                        $big_or_args = array_merge($big_or_args, $args);
1 ✔
338
                }
339

340
                if (sizeof($or) > 1) {
1 ✔
341
                        $or = substr($big_or, 0, strlen($big_or) - 4) . ')';
1 ✔
342

343
                        $args = $big_or_args;
1 ✔
344
                } else {
345
                        $or = reset($or);
1 ✔
346
                }
347

348
                if ($or === false) {
1 ✔
349
                        throw new \LogicException;
×
350
                }
351

352
                $this->sql = $this->queryHelper->whereSql($or);
1 ✔
353

354
                foreach ($args as $arg) {
1 ✔
355
                        $this->queryParameters[] = $arg;
1 ✔
356
                }
357
        }
1 ✔
358

359
        protected function applyFilterSelect(FilterSelect $filter): void
360
        {
361
                foreach ($filter->getCondition() as $column => $value) {
1 ✔
362
                        $this->applyWhere($column, $value);
1 ✔
363
                }
364
        }
1 ✔
365

366
        protected function applyFilterMultiSelect(FilterMultiSelect $filter): void
367
        {
368
                $or = [];
×
369
                $args = [];
×
370
                $big_or = '';
×
371
                $big_or_args = [];
×
372
                $condition = $filter->getCondition();
×
373
                foreach ($condition as $column => $values) {
×
374
                        $queryPart = '(';
×
375
                        foreach ($values as $value) {
×
376
                                $queryPart .= "$column = ? OR ";
×
377
                                $args[] = $value;
×
378
                        }
379

380
                        $queryPart = substr($queryPart, 0, strlen($queryPart) - 4) . ')';
×
381
                        $or[] = $queryPart;
×
382
                        $big_or .= "$queryPart OR ";
×
383
                        $big_or_args = array_merge($big_or_args, $args);
×
384
                }
385

386
                if (sizeof($or) > 1) {
×
387
                        $or = substr($big_or, 0, strlen($big_or) - 4) . ')';
×
388
                        $args = $big_or_args;
×
389
                } else {
390
                        $or = reset($or);
×
391
                }
392

393
                if ($or === false) {
×
394
                        throw new \LogicException;
×
395
                }
396

397
                $this->sql = $this->queryHelper->whereSql($or);
×
398

399
                foreach ($args as $arg) {
×
400
                        $this->queryParameters[] = $arg;
×
401
                }
402
        }
×
403
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc