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

contributte / datagrid / 8230347368

11 Mar 2024 09:16AM UTC coverage: 34.102%. First build
8230347368

Pull #1060

github

radimvaculik
PHP 8.1 is new minimal version
Pull Request #1060: [7.x] Next

117 of 435 new or added lines in 54 files covered. (26.9%)

1124 of 3296 relevant lines covered (34.1%)

0.34 hits per line

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

64.71
/src/DataSource/DoctrineDataSource.php
1
<?php declare(strict_types = 1);
2

3
namespace Contributte\Datagrid\DataSource;
4

5
use Contributte\Datagrid\AggregationFunction\IAggregatable;
6
use Contributte\Datagrid\AggregationFunction\IAggregationFunction;
7
use Contributte\Datagrid\Exception\DatagridDateTimeHelperException;
8
use Contributte\Datagrid\Exception\DatagridException;
9
use Contributte\Datagrid\Filter\FilterDate;
10
use Contributte\Datagrid\Filter\FilterDateRange;
11
use Contributte\Datagrid\Filter\FilterMultiSelect;
12
use Contributte\Datagrid\Filter\FilterRange;
13
use Contributte\Datagrid\Filter\FilterSelect;
14
use Contributte\Datagrid\Filter\FilterText;
15
use Contributte\Datagrid\Utils\DateTimeHelper;
16
use Contributte\Datagrid\Utils\Sorting;
17
use Doctrine\ORM\Query;
18
use Doctrine\ORM\QueryBuilder;
19
use Doctrine\ORM\Tools\Pagination\Paginator;
20
use Nette\SmartObject;
21
use function str_contains;
22

23
/**
24
 * @method void onDataLoaded(array $result)
25
 */
26
class DoctrineDataSource extends FilterableDataSource implements IDataSource, IAggregatable
27
{
28

29
        use SmartObject;
30

31
        /**
32
         * Event called when datagrid data is loaded.
33
         *
34
         * @var array|callable[]
35
         */
36
        public array $onDataLoaded;
37

38
        protected QueryBuilder $dataSource;
39

40
        protected ?string $rootAlias = null;
41

42
        protected int $placeholder = 0;
43

44
        /** @var array<string, mixed> */
45
        protected array $hints = [];
46

47
        public function __construct(QueryBuilder $dataSource, protected string $primaryKey)
1✔
48
        {
49
                $this->placeholder = count($dataSource->getParameters());
1✔
50
                $this->dataSource = $dataSource;
1✔
51
        }
1✔
52

53
        public function setQueryHint(string $name, mixed $value): IDataSource
1✔
54
        {
55
                $this->hints[$name] = $value;
1✔
56

57
                return $this;
1✔
58
        }
59

60
        public function getQuery(): Query
61
        {
62
                $query = $this->dataSource->getQuery();
1✔
63

64
                foreach ($this->hints as $name => $value) {
1✔
65
                        $query->setHint($name, $value);
1✔
66
                }
67

68
                return $query;
1✔
69
        }
70

71
        public function getCount(): int
72
        {
73
                if ($this->usePaginator()) {
1✔
74
                        return (new Paginator($this->getQuery()))->count();
×
75
                }
76

77
                $dataSource = clone $this->dataSource;
1✔
78
                $dataSource->select(sprintf('COUNT(%s)', $this->checkAliases($this->primaryKey)));
1✔
79
                $dataSource->resetDQLPart('orderBy');
1✔
80

81
                return (int) $dataSource->getQuery()->getSingleScalarResult();
1✔
82
        }
83

84
        /**
85
         * {@inheritDoc}
86
         */
87
        public function getData(): array
88
        {
89
                if ($this->usePaginator()) {
1✔
90
                        $iterator = (new Paginator($this->getQuery()))->getIterator();
×
91

92
                        $data = iterator_to_array($iterator);
×
93
                } else {
94
                        $data = $this->getQuery()->getResult();
1✔
95
                }
96

97
                $this->onDataLoaded($data);
1✔
98

99
                return $data;
1✔
100
        }
101

102
        /**
103
         * {@inheritDoc}
104
         */
105
        public function filterOne(array $condition): IDataSource
1✔
106
        {
107
                $p = $this->getPlaceholder();
1✔
108

109
                foreach ($condition as $column => $value) {
1✔
110
                        $c = $this->checkAliases($column);
1✔
111

112
                        $this->dataSource->andWhere(sprintf('%s = :%s', $c, $p))
1✔
113
                                ->setParameter($p, $value);
1✔
114
                }
115

116
                return $this;
1✔
117
        }
118

119
        public function limit(int $offset, int $limit): IDataSource
1✔
120
        {
121
                $this->dataSource->setFirstResult($offset)->setMaxResults($limit);
1✔
122

123
                return $this;
1✔
124
        }
125

126
        public function sort(Sorting $sorting): IDataSource
1✔
127
        {
128
                if (is_callable($sorting->getSortCallback())) {
1✔
129
                        call_user_func(
×
130
                                $sorting->getSortCallback(),
×
131
                                $this->dataSource,
×
132
                                $sorting->getSort()
×
133
                        );
134

135
                        return $this;
×
136
                }
137

138
                $sort = $sorting->getSort();
1✔
139

140
                if ($sort !== []) {
1✔
141
                        foreach ($sort as $column => $order) {
1✔
142
                                $this->dataSource->addOrderBy($this->checkAliases((string) $column), $order);
1✔
143
                        }
144
                } else {
145
                        /**
146
                         * Has the statement already a order by clause?
147
                         */
148
                        if (! (bool) $this->dataSource->getDQLPart('orderBy')) {
×
149
                                $this->dataSource->orderBy($this->checkAliases($this->primaryKey));
×
150
                        }
151
                }
152

153
                return $this;
1✔
154
        }
155

156
        /**
157
         * Get unique int value for each instance class (self)
158
         */
159
        public function getPlaceholder(): string
160
        {
161
                $return = 'param' . ($this->placeholder + 1);
1✔
162

163
                $this->placeholder++;
1✔
164

165
                return $return;
1✔
166
        }
167

168
        public function processAggregation(IAggregationFunction $function): void
169
        {
170
                $function->processDataSource(clone $this->dataSource);
×
171
        }
172

173
        protected function applyFilterDate(FilterDate $filter): void
174
        {
175
                $p1 = $this->getPlaceholder();
×
176
                $p2 = $this->getPlaceholder();
×
177

178
                foreach ($filter->getCondition() as $column => $value) {
×
179
                        try {
180
                                $date = DateTimeHelper::tryConvertToDateTime($value, [$filter->getPhpFormat()]);
×
181
                                $c = $this->checkAliases($column);
×
182

NEW
183
                                $this->dataSource->andWhere(sprintf('%s >= :%s AND %s <= :%s', $c, $p1, $c, $p2))
×
184
                                        ->setParameter($p1, $date->format('Y-m-d 00:00:00'))
×
185
                                        ->setParameter($p2, $date->format('Y-m-d 23:59:59'));
×
NEW
186
                        } catch (DatagridDateTimeHelperException) {
×
187
                                // ignore the invalid filter value
188
                        }
189
                }
190
        }
191

192
        protected function applyFilterDateRange(FilterDateRange $filter): void
193
        {
194
                $conditions = $filter->getCondition();
×
195
                $c = $this->checkAliases($filter->getColumn());
×
196

197
                $valueFrom = $conditions[$filter->getColumn()]['from'];
×
198
                $valueTo = $conditions[$filter->getColumn()]['to'];
×
199

200
                if ($valueFrom) {
×
201
                        try {
202
                                $dateFrom = DateTimeHelper::tryConvertToDate($valueFrom, [$filter->getPhpFormat()]);
×
203
                                $dateFrom->setTime(0, 0, 0);
×
204

205
                                $p = $this->getPlaceholder();
×
206

NEW
207
                                $this->dataSource->andWhere(sprintf('%s >= :%s', $c, $p))->setParameter(
×
208
                                        $p,
×
209
                                        $dateFrom->format('Y-m-d H:i:s')
×
210
                                );
NEW
211
                        } catch (DatagridDateTimeHelperException) {
×
212
                                // ignore the invalid filter value
213
                        }
214
                }
215

216
                if ($valueTo) {
×
217
                        try {
218
                                $dateTo = DateTimeHelper::tryConvertToDate($valueTo, [$filter->getPhpFormat()]);
×
219
                                $dateTo->setTime(23, 59, 59);
×
220

221
                                $p = $this->getPlaceholder();
×
222

NEW
223
                                $this->dataSource->andWhere(sprintf('%s <= :%s', $c, $p))->setParameter(
×
224
                                        $p,
×
225
                                        $dateTo->format('Y-m-d H:i:s')
×
226
                                );
NEW
227
                        } catch (DatagridDateTimeHelperException) {
×
228
                                // ignore the invalid filter value
229
                        }
230
                }
231
        }
232

233
        protected function applyFilterRange(FilterRange $filter): void
1✔
234
        {
235
                $conditions = $filter->getCondition();
1✔
236
                $c = $this->checkAliases($filter->getColumn());
1✔
237

238
                $valueFrom = $conditions[$filter->getColumn()]['from'];
1✔
239
                $valueTo = $conditions[$filter->getColumn()]['to'];
1✔
240

241
                if (is_numeric($valueFrom)) {
1✔
242
                        $p = $this->getPlaceholder();
1✔
243
                        $this->dataSource->andWhere(sprintf('%s >= :%s', $c, $p))->setParameter($p, $valueFrom);
1✔
244
                }
245

246
                if (is_numeric($valueTo)) {
1✔
247
                        $p = $this->getPlaceholder();
1✔
248
                        $this->dataSource->andWhere(sprintf('%s <= :%s', $c, $p))->setParameter($p, $valueTo);
1✔
249
                }
250
        }
1✔
251

252
        protected function applyFilterText(FilterText $filter): void
1✔
253
        {
254
                $condition = $filter->getCondition();
1✔
255
                $exprs = [];
1✔
256

257
                foreach ($condition as $column => $value) {
1✔
258
                        $c = $this->checkAliases($column);
1✔
259

260
                        if ($filter->isExactSearch()) {
1✔
261
                                $exprs[] = $this->dataSource->expr()->eq(
1✔
262
                                        $c,
1✔
263
                                        $this->dataSource->expr()->literal($value)
1✔
264
                                );
265

266
                                continue;
1✔
267
                        }
268

269
                        $words = $filter->hasSplitWordsSearch() === false ? [$value] : explode(' ', $value);
1✔
270

271
                        foreach ($words as $word) {
1✔
272
                                $exprs[] = $this->dataSource->expr()->like(
1✔
273
                                        $this->dataSource->expr()->lower($c),
1✔
274
                                        $this->dataSource->expr()->lower(
1✔
275
                                                $this->dataSource->expr()->literal('%' . $word . '%')
1✔
276
                                        )
277
                                );
278
                        }
279
                }
280

281
                $or = call_user_func_array([$this->dataSource->expr(), 'orX'], $exprs);
1✔
282

283
                $this->dataSource->andWhere($or);
1✔
284
        }
1✔
285

286
        protected function applyFilterMultiSelect(FilterMultiSelect $filter): void
287
        {
288
                $c = $this->checkAliases($filter->getColumn());
×
289
                $p = $this->getPlaceholder();
×
290

291
                $values = $filter->getCondition()[$filter->getColumn()];
×
292
                $expr = $this->dataSource->expr()->in($c, ':' . $p);
×
293

294
                $this->dataSource->andWhere($expr)->setParameter($p, $values);
×
295
        }
296

297
        protected function applyFilterSelect(FilterSelect $filter): void
1✔
298
        {
299
                $p = $this->getPlaceholder();
1✔
300

301
                foreach ($filter->getCondition() as $column => $value) {
1✔
302
                        $c = $this->checkAliases($column);
1✔
303

304
                        $this->dataSource->andWhere(sprintf('%s = :%s', $c, $p))
1✔
305
                                ->setParameter($p, $value);
1✔
306
                }
307
        }
1✔
308

309
        protected function getDataSource(): QueryBuilder
310
        {
311
                return $this->dataSource;
×
312
        }
313

314
        private function checkAliases(string $column): string
1✔
315
        {
316
                if (str_contains($column, '.')) {
1✔
317
                        return $column;
×
318
                }
319

320
                if (!isset($this->rootAlias)) {
1✔
321
                        $rootAlias = $this->dataSource->getRootAliases();
1✔
322

323
                        if ($rootAlias === []) {
1✔
NEW
324
                                throw new DatagridException('No root alias given from datasource');
×
325
                        }
326

327
                        $this->rootAlias = current($rootAlias);
1✔
328
                }
329

330
                return $this->rootAlias . '.' . $column;
1✔
331
        }
332

333
        private function usePaginator(): bool
334
        {
335
                $hasJoin = (bool) $this->dataSource->getDQLPart('join');
1✔
336
                $hasGroupBy = (bool) $this->dataSource->getDQLPart('groupBy');
1✔
337

338
                return $hasJoin || $hasGroupBy;
1✔
339
        }
340

341
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc