Completed
Pull Request — master (#375)
by Dalibor
02:34
created

DoctrineDataSource::getAggregationData()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 7
nc 3
nop 0
1
<?php
2
3
/**
4
 * @copyright   Copyright (c) 2015 ublaboo <[email protected]>
5
 * @author      Jakub Kontra <[email protected]>
6
 * @author      Pavel Janda <[email protected]>
7
 * @package     Ublaboo
8
 */
9
10
namespace Ublaboo\DataGrid\DataSource;
11
12
use Doctrine\ORM\Query;
13
use Doctrine\ORM\QueryBuilder;
14
use Doctrine\ORM\Tools\Pagination\Paginator;
15
use Nette\Utils\Strings;
16
use Ublaboo\DataGrid\Filter;
17
use Ublaboo\DataGrid\Utils\DateTimeHelper;
18
use Ublaboo\DataGrid\Utils\Sorting;
19
20
/**
21
 * @method void onDataLoaded(array $result)
22
 */
23
class DoctrineDataSource extends FilterableDataSource implements IDataSource
24
{
25
26
	/**
27
	 * Event called when datagrid data is loaded.
28
	 * @var callable[]
29
	 */
30
	public $onDataLoaded;
31
32
	/**
33
	 * @var QueryBuilder
34
	 */
35
	protected $data_source;
36
37
	/**
38
	 * @var array
39
	 */
40
	protected $aggregations = [];
41
42
	/**
43
	 * @var string
44
	 */
45
	protected $primary_key;
46
47
	/**
48
	 * @var string
49
	 */
50
	protected $root_alias;
51
52
	/**
53
	 * @var int
54
	 */
55
	protected $placeholder;
56
57
58
	/**
59
	 * @param QueryBuilder $data_source
60
	 * @param string       $primary_key
61
	 */
62
	public function __construct(QueryBuilder $data_source, $primary_key)
63
	{
64
		$this->placeholder = count($data_source->getParameters());
65
		$this->data_source = $data_source;
66
		$this->primary_key = $primary_key;
67
	}
68
69
70
	/**
71
	 * @return \Doctrine\ORM\Query
72
	*/
73
	public function getQuery()
74
	{
75
		return $this->data_source->getQuery();
76
	}
77
78
79
	/**
80
	 * @param  string  $column
81
	 * @return string
82
	 */
83
	private function checkAliases($column)
84
	{
85
		if (Strings::contains($column, ".")) {
86
			return $column;
87
		}
88
89
		if (!isset($this->root_alias)) {
90
			$this->root_alias = $this->data_source->getRootAliases();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->data_source->getRootAliases() of type array is incompatible with the declared type string of property $root_alias.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
91
			$this->root_alias = current($this->root_alias);
92
		}
93
94
		return $this->root_alias.'.'.$column;
95
	}
96
97
98
	/********************************************************************************
99
	 *                          IDataSource implementation                          *
100
	 ********************************************************************************/
101
102
103
	/**
104
	 * Get count of data
105
	 * @return int
106
	 */
107
	public function getCount()
108
	{
109
		return (new Paginator($this->getQuery()))->count();
110
	}
111
112
113
	/**
114
	 * Get the data
115
	 * @return array
116
	 */
117
	public function getData()
118
	{
119
		$iterator = (new Paginator($this->getQuery()))->getIterator();
120
121
		$data = iterator_to_array($iterator);
122
123
		$this->onDataLoaded($data);
124
125
		return $data;
126
	}
127
128
129
	/**
130
	 * Filter data - get one row
131
	 * @param  array  $condition
132
	 * @return static
133
	 */
134
	public function filterOne(array $condition)
135
	{
136
		$p = $this->getPlaceholder();
137
138 View Code Duplication
		foreach ($condition as $column => $value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
139
			$c = $this->checkAliases($column);
140
141
			$this->data_source->andWhere("$c = ?$p")
142
				->setParameter($p, $value);
143
		}
144
145
		return $this;
146
	}
147
148
149
	/**
150
	 * Filter by date
151
	 * @param Filter\FilterDate  $filter
152
	 */
153
	public function applyFilterDate(Filter\FilterDate $filter)
154
	{
155
		$p1 = $this->getPlaceholder();
156
		$p2 = $this->getPlaceholder();
157
158
		foreach ($filter->getCondition() as $column => $value) {
159
			$date = DateTimeHelper::tryConvertToDateTime($value, [$filter->getPhpFormat()]);
160
			$c = $this->checkAliases($column);
161
162
			$this->data_source
163
				->andWhere("$c >= ?$p1")
164
				->andWhere("$c <= ?$p2")
165
				->setParameter($p1, $date->format('Y-m-d 00:00:00'))
166
				->setParameter($p2, $date->format('Y-m-d 23:59:59'));
167
		}
168
	}
169
170
171
	/**
172
	 * Filter by date range
173
	 * @param Filter\FilterDateRange  $filter
174
	 */
175
	public function applyFilterDateRange(Filter\FilterDateRange $filter)
176
	{
177
		$conditions = $filter->getCondition();
178
		$c = $this->checkAliases($filter->getColumn());
0 ignored issues
show
Bug introduced by
It seems like $filter->getColumn() targeting Ublaboo\DataGrid\Filter\Filter::getColumn() can also be of type array; however, Ublaboo\DataGrid\DataSou...aSource::checkAliases() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
179
180
		$value_from = $conditions[$filter->getColumn()]['from'];
181
		$value_to   = $conditions[$filter->getColumn()]['to'];
182
183 View Code Duplication
		if ($value_from) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
184
			$date_from = DateTimeHelper::tryConvertToDate($value_from, [$filter->getPhpFormat()]);
185
			$date_from->setTime(0, 0, 0);
186
187
			$p = $this->getPlaceholder();
188
189
			$this->data_source->andWhere("$c >= ?$p")->setParameter($p, $date_from->format('Y-m-d H:i:s'));
190
		}
191
192 View Code Duplication
		if ($value_to) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
193
			$date_to = DateTimeHelper::tryConvertToDate($value_to, [$filter->getPhpFormat()]);
194
			$date_to->setTime(23, 59, 59);
195
196
			$p = $this->getPlaceholder();
197
198
			$this->data_source->andWhere("$c <= ?$p")->setParameter($p, $date_to->format('Y-m-d H:i:s'));
199
		}
200
	}
201
202
203
	/**
204
	 * Filter by range
205
	 * @param Filter\FilterRange  $filter
206
	 */
207
	public function applyFilterRange(Filter\FilterRange $filter)
208
	{
209
		$conditions = $filter->getCondition();
210
		$c = $this->checkAliases($filter->getColumn());
0 ignored issues
show
Bug introduced by
It seems like $filter->getColumn() targeting Ublaboo\DataGrid\Filter\Filter::getColumn() can also be of type array; however, Ublaboo\DataGrid\DataSou...aSource::checkAliases() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
211
212
		$value_from = $conditions[$filter->getColumn()]['from'];
213
		$value_to   = $conditions[$filter->getColumn()]['to'];
214
215
		if ($value_from) {
216
			$p = $this->getPlaceholder();
217
			$this->data_source->andWhere("$c >= ?$p")->setParameter($p, $value_from);
218
		}
219
220
		if ($value_to) {
221
			$p = $this->getPlaceholder();
222
			$this->data_source->andWhere("$c <= ?$p")->setParameter($p, $value_to);
223
		}
224
	}
225
226
227
	/**
228
	 * Filter by keyword
229
	 * @param Filter\FilterText  $filter
230
	 */
231
	public function applyFilterText(Filter\FilterText $filter)
232
	{
233
		$condition = $filter->getCondition();
234
		$exprs = [];
235
236
		foreach ($condition as $column => $value) {
237
			$c = $this->checkAliases($column);
238
239
			if($filter->isExactSearch()){
240
				$exprs[] = $this->data_source->expr()->eq($c, $this->data_source->expr()->literal($value));
241
				continue;
242
			}
243
244 View Code Duplication
			if ($filter->hasSplitWordsSearch() === FALSE) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
245
				$words = [$value];
246
			} else {
247
				$words = explode(' ', $value);
248
			}
249
250
			foreach ($words as $word) {
251
				$exprs[] = $this->data_source->expr()->like($c, $this->data_source->expr()->literal("%$word%"));
252
			}
253
		}
254
255
		$or = call_user_func_array([$this->data_source->expr(), 'orX'], $exprs);
256
257
		$this->data_source->andWhere($or);
258
	}
259
260
261
	/**
262
	 * Filter by multi select value
263
	 * @param Filter\FilterMultiSelect  $filter
264
	 */
265
	public function applyFilterMultiSelect(Filter\FilterMultiSelect $filter)
266
	{
267
		$c = $this->checkAliases($filter->getColumn());
0 ignored issues
show
Bug introduced by
It seems like $filter->getColumn() targeting Ublaboo\DataGrid\Filter\Filter::getColumn() can also be of type array; however, Ublaboo\DataGrid\DataSou...aSource::checkAliases() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
268
		$p = $this->getPlaceholder();
269
270
		$values = $filter->getCondition()[$filter->getColumn()];
271
		$expr = $this->data_source->expr()->in($c, '?'.$p);
272
273
		$this->data_source->andWhere($expr)->setParameter($p, $values);
274
	}
275
276
277
	/**
278
	 * Filter by select value
279
	 * @param Filter\FilterSelect  $filter
280
	 */
281
	public function applyFilterSelect(Filter\FilterSelect $filter)
282
	{
283
		$p = $this->getPlaceholder();
284
285 View Code Duplication
		foreach ($filter->getCondition() as $column => $value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
286
			$c = $this->checkAliases($column);
287
288
			$this->data_source->andWhere("$c = ?$p")
289
				->setParameter($p, $value);
290
		}
291
	}
292
293
294
	/**
295
	 * Apply limit and offset on data
296
	 * @param  int  $offset
297
	 * @param  int  $limit
298
	 * @return static
299
	 */
300
	public function limit($offset, $limit)
301
	{
302
		$this->data_source->setFirstResult($offset)->setMaxResults($limit);
303
304
		return $this;
305
	}
306
307
308
	/**
309
	 * Sort data
310
	 * @param  Sorting $sorting
311
	 * @return static
312
	 */
313 View Code Duplication
	public function sort(Sorting $sorting)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
314
	{
315
		if (is_callable($sorting->getSortCallback())) {
316
			call_user_func(
317
				$sorting->getSortCallback(),
318
				$this->data_source,
319
				$sorting->getSort()
320
			);
321
322
			return $this;
323
		}
324
325
		$sort = $sorting->getSort();
326
327
		if (!empty($sort)) {
328
			foreach ($sort as $column => $order) {
329
				$this->data_source->addOrderBy($this->checkAliases($column), $order);
330
			}
331
		} else {
332
			/**
333
			 * Has the statement already a order by clause?
334
			 */
335
			if (!$this->data_source->getDQLPart('orderBy')) {
336
				$this->data_source->orderBy($this->checkAliases($this->primary_key));
337
			}
338
		}
339
340
		return $this;
341
	}
342
343
344
	/**
345
	 * Get unique int value for each instance class (self)
346
	 * @return int
347
	 */
348
	public function getPlaceholder()
349
	{
350
		return $this->placeholder++;
351
	}
352
353
	/**
354
	 * @param string $aggregation_type
355
	 * @param string $column
356
	 * @return mixed
357
	 */
358
	public function addAggregationColumn($aggregation_type, $column)
359
	{
360
		$this->aggregations[$column] = $aggregation_type;
361
	}
362
363
	/**
364
	 * get aggregation row
365
	 * @return array
366
	 */
367
	public function getAggregationData()
368
	{
369
		if (count($this->aggregations) === 0) {
370
			return [];
371
		}
372
		$this->data_source->resetDQLPart('select');
373
		foreach ($this->aggregations as $column => $aggregation_type) {
374
			$query = $this->data_source->addSelect("$aggregation_type(" . $this->checkAliases($column) . ") AS $column");
375
		}
376
		return $query->getQuery()->getSingleResult();
0 ignored issues
show
Bug introduced by
The variable $query does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
377
	}
378
}
379