Completed
Push — master ( be0c13...a6f017 )
by Pavel
02:37
created

DoctrineDataSource::getData()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 1 Features 2
Metric Value
c 4
b 1
f 2
dl 0
loc 7
rs 9.4285
cc 2
eloc 2
nc 2
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\QueryBuilder,
13
	Ublaboo\DataGrid\Filter,
14
	Nette\Utils\Callback,
15
	Nette\Utils\Strings,
16
	Doctrine;
17
18
class DoctrineDataSource implements IDataSource
19
{
20
21
	/**
22
	 * @var QueryBuilder
23
	 */
24
	protected $data_source;
25
26
	/**
27
	 * @var array
28
	 */
29
	protected $data = [];
30
31
	/**
32
	 * @var string
33
	 */
34
	protected $primary_key;
35
36
	/**
37
	 * @var int
38
	 */
39
	protected $placeholder = 0;
40
41
42
	/**
43
	 * @param QueryBuilder $data_source
44
	 * @param string       $primary_key
45
	 */
46
	public function __construct(QueryBuilder $data_source, $primary_key)
47
	{
48
		$this->data_source = $data_source;
49
		$this->primary_key = $primary_key;
50
	}
51
52
53
	/**
54
	 * @return Doctrine\ORM\Query
55
	*/
56
	public function getQuery()
57
	{
58
		return $this->data_source->getQuery();
59
	}
60
61
62
	private function checkAliases($column)
63
	{
64
		if (Strings::contains($column, ".")) {
65
			return $column;
66
		}
67
68
		return current($this->data_source->getRootAliases()) . '.' . $column;
69
	}
70
71
72
	/********************************************************************************
73
	 *                          IDataSource implementation                          *
74
	 ********************************************************************************/
75
76
77
	/**
78
	 * Get count of data
79
	 * @return int
80
	 */
81
	public function getCount()
82
	{
83
		$paginator = new Doctrine\ORM\Tools\Pagination\Paginator($this->getQuery());
84
		$count = count($paginator);
85
86
		return $count;
87
	}
88
89
	/**
90
	 * Get the data
91
	 * @return array
92
	 */
93
	public function getData()
94
	{
95
		/**
96
		 * Paginator is better if the query uses ManyToMany associations
97
		 */
98
		return $this->data ?: $this->data_source->getQuery()->getResult();
99
	}
100
101
102
	/**
103
	 * Filter data
104
	 * @param array $filters
105
	 * @return self
106
	 */
107 View Code Duplication
	public function filter(array $filters)
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...
108
	{
109
		foreach ($filters as $filter) {
110
			if ($filter->isValueSet()) {
111
				if ($filter->hasConditionCallback()) {
112
					Callback::invokeArgs(
113
						$filter->getConditionCallback(),
114
						[$this->data_source, $filter->getValue()]
115
					);
116
				} else {
117
					if ($filter instanceof Filter\FilterText) {
118
						$this->applyFilterText($filter);
119
					} else if ($filter instanceof Filter\FilterSelect) {
120
						$this->applyFilterSelect($filter);
121
					} else if ($filter instanceof Filter\FilterDate) {
122
						$this->applyFilterDate($filter);
123
					} else if ($filter instanceof Filter\FilterDateRange) {
124
						$this->applyFilterDateRange($filter);
125
					} else if ($filter instanceof Filter\FilterRange) {
126
						$this->applyFilterRange($filter);
127
					}
128
				}
129
			}
130
		}
131
132
		return $this;
133
	}
134
135
136
	/**
137
	 * Filter data - get one row
138
	 * @param array $condition
139
	 * @return self
140
	 */
141
	public function filterOne(array $condition)
142
	{
143
		$p = $this->getPlaceholder();
144
145 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...
146
			$c = $this->checkAliases($column);
147
148
			$this->data_source->andWhere("$c = ?$p")
149
				->setParameter($p, $value);
150
		}
151
152
		return $this;
153
	}
154
155
156
	/**
157
	 * Filter by date
158
	 * @param  Filter\FilterDate $filter
159
	 * @return self
160
	 */
161
	public function applyFilterDate(Filter\FilterDate $filter)
162
	{
163
		$p1 = $this->getPlaceholder();
164
		$p2 = $this->getPlaceholder();
165
166
		foreach ($filter->getCondition() as $column => $value) {
167
			$date = \DateTime::createFromFormat($filter->getPhpFormat(), $value);
168
			$c = $this->checkAliases($column);
169
170
			$this->data_source
171
				->andWhere("$c >= ?$p1")
172
				->andWhere("$c <= ?$p2")
173
				->setParameter($p1, $date->format('Y-m-d 00:00:00'))
174
				->setParameter($p2, $date->format('Y-m-d 23:59:59'));
175
		}
176
177
		return $this;
178
	}
179
180
181
	/**
182
	 * Filter by date range
183
	 * @param  Filter\FilterDateRange $filter
184
	 * @return void
185
	 */
186
	public function applyFilterDateRange(Filter\FilterDateRange $filter)
187
	{
188
		$conditions = $filter->getCondition();
189
		$c = $this->checkAliases($filter->getColumn());
190
191
		$value_from = $conditions[$filter->getColumn()]['from'];
192
		$value_to   = $conditions[$filter->getColumn()]['to'];
193
194 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...
195
			$date_from = \DateTime::createFromFormat($filter->getPhpFormat(), $value_from);
196
			$date_from->setTime(0, 0, 0);
197
198
			$p = $this->getPlaceholder();
199
200
			$this->data_source->andWhere("$c >= ?$p")->setParameter($p, $date_from->format('Y-m-d H:i:s'));
201
		}
202
203 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...
204
			$date_to = \DateTime::createFromFormat($filter->getPhpFormat(), $value_to);
205
			$date_to->setTime(23, 59, 59);
206
207
			$p = $this->getPlaceholder();
208
209
			$this->data_source->andWhere("$c <= ?$p")->setParameter($p, $date_to->format('Y-m-d H:i:s'));
210
		}
211
	}
212
213
214
	/**
215
	 * Filter by range
216
	 * @param  Filter\FilterRange $filter
217
	 * @return void
218
	 */
219
	public function applyFilterRange(Filter\FilterRange $filter)
220
	{
221
		$conditions = $filter->getCondition();
222
		$c = $this->checkAliases($filter->getColumn());
223
224
		$value_from = $conditions[$filter->getColumn()]['from'];
225
		$value_to   = $conditions[$filter->getColumn()]['to'];
226
227
		if ($value_from) {
228
			$p = $this->getPlaceholder();
229
			$this->data_source->andWhere("$c >= ?$p")->setParameter($p, $value_from);
230
		}
231
232
		if ($value_to) {
233
			$p = $this->getPlaceholder();
234
			$this->data_source->andWhere("$c <= ?$p")->setParameter($p, $value_to);
235
		}
236
	}
237
238
239
	/**
240
	 * Filter by keyword
241
	 * @param  Filter\FilterText $filter
242
	 * @return void
243
	 */
244
	public function applyFilterText(Filter\FilterText $filter)
245
	{
246
		$condition = $filter->getCondition();
247
248
		foreach ($condition as $column => $value) {
249
			$words = explode(' ', $value);
250
			$c = $this->checkAliases($column);
251
252
			foreach ($words as $word) {
253
				$exprs[] = $this->data_source->expr()->like($c, $this->data_source->expr()->literal("%$word%"));
0 ignored issues
show
Coding Style Comprehensibility introduced by
$exprs was never initialized. Although not strictly required by PHP, it is generally a good practice to add $exprs = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
254
255
				/**
256
				 * @todo Manage somehow COLLATE statement in DQL
257
				 */
258
				/*if (preg_match("/[\x80-\xFF]/", $word)) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
259
					$or[] = "$c LIKE $escaped COLLATE utf8_bin";
260
				} else {
261
					$escaped = Strings::toAscii($escaped);
262
					$or[] = "$c LIKE $escaped COLLATE utf8_general_ci";
263
				}*/
264
			}
265
		}
266
267
		$or = call_user_func_array([$this->data_source->expr(), 'orX'], $exprs);
0 ignored issues
show
Bug introduced by
The variable $exprs 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...
268
269
		$this->data_source->andWhere($or);
270
	}
271
272
273
	/**
274
	 * Filter by select value
275
	 * @param  Filter\FilterSelect $filter
276
	 * @return void
277
	 */
278
	public function applyFilterSelect(Filter\FilterSelect $filter)
279
	{
280
		$p = $this->getPlaceholder();
281
282 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...
283
			$c = $this->checkAliases($column);
284
285
			$this->data_source->andWhere("$c = ?$p")
286
				->setParameter($p, $value);
287
		}
288
	}
289
290
291
	/**
292
	 * Apply limit and offet on data
293
	 * @param int $offset
294
	 * @param int $limit
295
	 * @return self
296
	 */
297
	public function limit($offset, $limit)
298
	{
299
		$this->data_source->setFirstResult($offset)->setMaxResults($limit);
300
301
		return $this;
302
	}
303
304
305
	/**
306
	 * Order data
307
	 * @param  array  $sorting
308
	 * @return self
309
	 */
310
	public function sort(array $sorting)
311
	{
312
		$alias = current($this->data_source->getDQLPart('from'))->getAlias();
313
314
		if ($sorting) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $sorting of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
315
			foreach ($sorting as $column => $sort) {
316
				$this->data_source->orderBy("{$alias}.{$column}", $sort);
317
			}
318
		} else {
319
			/**
320
			 * Has the statement already a order by clause?
321
			 */
322
			if (!$this->data_source->getDQLPart('orderBy')) {
323
				$this->data_source->orderBy("{$alias}.{$this->primary_key}");
324
			}
325
		}
326
327
		return $this;
328
	}
329
330
331
	/**
332
	 * Get unique int value for each instance class (self)
333
	 * @return int
334
	 */
335
	public function getPlaceholder()
336
	{
337
		$this->placeholder++;
338
339
		return $this->placeholder;
340
	}
341
342
}
343