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

DoctrineCollectionDataSource::getAggregationData()   C

Complexity

Conditions 14
Paths 33

Size

Total Lines 34
Code Lines 23

Duplication

Lines 34
Ratio 100 %

Importance

Changes 0
Metric Value
dl 34
loc 34
rs 5.0864
c 0
b 0
f 0
cc 14
eloc 23
nc 33
nop 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * @copyright   Copyright (c) 2015 ublaboo <[email protected]>
5
 * @author      Martin Procházka <[email protected]>
6
 * @package     Ublaboo
7
 */
8
9
namespace Ublaboo\DataGrid\DataSource;
10
11
use Doctrine\Common\Collections\Collection;
12
use Doctrine\Common\Collections\Criteria;
13
use Ublaboo\DataGrid\Column\ColumnAggregationFunction;
14
use Ublaboo\DataGrid\Filter;
15
use Ublaboo\DataGrid\Utils\DateTimeHelper;
16
use Ublaboo\DataGrid\Utils\Sorting;
17
18
final class DoctrineCollectionDataSource extends FilterableDataSource implements IDataSource
19
{
20
21
	/**
22
	 * @var Collection
23
	 */
24
	private $data_source;
25
26
	/**
27
	 * @var string
28
	 */
29
	private $primary_key;
30
31
	/**
32
	 * @var Criteria
33
	 */
34
	private $criteria;
35
36
	/**
37
	 * @var array
38
	 */
39
	protected $aggregations = [];
40
41
	/**
42
	 * @param Collection  $collection
43
	 * @param string      $primary_key
44
	 */
45
	public function __construct(Collection $collection, $primary_key)
46
	{
47
		$this->criteria = Criteria::create();
48
		$this->data_source = $collection;
49
		$this->primary_key = $primary_key;
50
	}
51
52
53
	/**
54
	 * @return Collection
55
	 */
56
	private function getFilteredCollection()
57
	{
58
		return $this->data_source->matching($this->criteria);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Doctrine\Common\Collections\Collection as the method matching() does only exist in the following implementations of said interface: Doctrine\Common\Collections\ArrayCollection, Doctrine\ORM\LazyCriteriaCollection, Doctrine\ORM\PersistentCollection.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
59
	}
60
61
62
	/********************************************************************************
63
	 *                          IDataSource implementation                          *
64
	 ********************************************************************************/
65
66
67
	/**
68
	 * Get count of data
69
	 * @return int
70
	 */
71
	public function getCount()
72
	{
73
		return $this->getFilteredCollection()->count();
74
	}
75
76
77
	/**
78
	 * Get the data
79
	 * @return array
80
	 */
81
	public function getData()
82
	{
83
		return $this->getFilteredCollection()->toArray();
84
	}
85
86
87
	/**
88
	 * Filter data - get one row
89
	 * @param array $condition
90
	 * @return static
91
	 */
92
	public function filterOne(array $condition)
93
	{
94
		foreach ($condition as $column => $value) {
95
			$expr = Criteria::expr()->eq($column, $value);
96
			$this->criteria->andWhere($expr);
97
		}
98
99
		return $this;
100
	}
101
102
103
	/**
104
	 * Filter by date
105
	 * @param  Filter\FilterDate $filter
106
	 * @return void
107
	 */
108
	public function applyFilterDate(Filter\FilterDate $filter)
109
	{
110
		foreach ($filter->getCondition() as $column => $value) {
111
			$date = DateTimeHelper::tryConvertToDateTime($value, [$filter->getPhpFormat()]);
112
113
			$from = Criteria::expr()->gte($filter->getColumn(), $date->format('Y-m-d 00:00:00'));
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, Doctrine\Common\Collecti...xpressionBuilder::gte() 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...
114
			$to = Criteria::expr()->lte($filter->getColumn(), $date->format('Y-m-d 23:59:59'));
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, Doctrine\Common\Collecti...xpressionBuilder::lte() 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...
115
116
			$this->criteria->andWhere($from)->andWhere($to);
117
		}
118
	}
119
120
121
	/**
122
	 * Filter by date range
123
	 * @param  Filter\FilterDateRange $filter
124
	 * @return void
125
	 */
126
	public function applyFilterDateRange(Filter\FilterDateRange $filter)
127
	{
128
		$values = $conditions[$filter->getColumn()];
0 ignored issues
show
Bug introduced by
The variable $conditions does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
129
130 View Code Duplication
		if ($value_from = $values['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...
131
			$date_from = DateTimeHelper::tryConvertToDateTime($value_from, [$filter->getPhpFormat()]);
132
			$date_from->setTime(0, 0, 0);
133
134
			$expr = Criteria::expr()->gte($filter->getColumn(), $date_from->format('Y-m-d H:i:s'));
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, Doctrine\Common\Collecti...xpressionBuilder::gte() 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...
135
			$this->criteria->andWhere($expr);
136
		}
137
138 View Code Duplication
		if ($value_to = $values['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...
139
			$date_to = DateTimeHelper::tryConvertToDateTime($value_to, [$filter->getPhpFormat()]);
140
			$date_to->setTime(23, 59, 59);
141
142
			$expr = Criteria::expr()->lte($filter->getColumn(), $date_to->format('Y-m-d H:i:s'));
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, Doctrine\Common\Collecti...xpressionBuilder::lte() 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...
143
			$this->criteria->andWhere($expr);
144
		}
145
	}
146
147
148
	/**
149
	 * Filter by range
150
	 * @param  Filter\FilterRange $filter
151
	 * @return void
152
	 */
153
	public function applyFilterRange(Filter\FilterRange $filter)
154
	{
155
		$values = $conditions[$filter->getColumn()];
0 ignored issues
show
Bug introduced by
The variable $conditions does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
156
157
		if ($value_from = $values['from']) {
158
			$expr = Criteria::expr()->gte($filter->getColumn(), $value_from);
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, Doctrine\Common\Collecti...xpressionBuilder::gte() 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...
159
			$this->criteria->andWhere($expr);
160
		}
161
162
		if ($value_to = $values['to']) {
163
			$expr = Criteria::expr()->lte($filter->getColumn(), $value_to);
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, Doctrine\Common\Collecti...xpressionBuilder::lte() 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...
164
			$this->criteria->andWhere($expr);
165
		}
166
	}
167
168
169
	/**
170
	 * Filter by keyword
171
	 * @param  Filter\FilterText $filter
172
	 * @return void
173
	 */
174
	public function applyFilterText(Filter\FilterText $filter)
175
	{
176
		$exprs = [];
177
178
		foreach ($filter->getCondition() as $column => $value) {
179
			if($filter->isExactSearch()) {
180
				$exprs[] = Criteria::expr()->eq($column, $value);
181
				continue;
182
			}
183
184 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...
185
				$words = [$value];
186
			} else {
187
				$words = explode(' ', $value);
188
			}
189
190
			foreach ($words as $word) {
191
				$exprs[] = Criteria::expr()->contains($column, $word);
192
			}
193
		}
194
195
		$expr = call_user_func_array([Criteria::expr(), 'orX'], $exprs);
196
		$this->criteria->andWhere($expr);
197
	}
198
199
200
	/**
201
	 * Filter by multi select value
202
	 * @param  Filter\FilterMultiSelect $filter
203
	 * @return void
204
	 */
205
	public function applyFilterMultiSelect(Filter\FilterMultiSelect $filter)
206
	{
207
		$values = $filter->getCondition()[$filter->getColumn()];
208
209
		$expr = Criteria::expr()->in($filter->getColumn(), $values);
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, Doctrine\Common\Collecti...ExpressionBuilder::in() 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...
210
		$this->criteria->andWhere($expr);
211
	}
212
213
214
	/**
215
	 * Filter by select value
216
	 * @param  Filter\FilterSelect $filter
217
	 * @return void
218
	 */
219
	public function applyFilterSelect(Filter\FilterSelect $filter)
220
	{
221
		foreach ($filter->getCondition() as $column => $value) {
222
			$expr = Criteria::expr()->eq($column, $value);
223
			$this->criteria->andWhere($expr);
224
		}
225
	}
226
227
228
	/**
229
	 * Apply limit and offset on data
230
	 * @param int $offset
231
	 * @param int $limit
232
	 * @return static
233
	 */
234
	public function limit($offset, $limit)
235
	{
236
		$this->criteria->setFirstResult($offset)->setMaxResults($limit);
237
238
		return $this;
239
	}
240
241
242
	/**
243
	 * Sort data
244
	 * @param Sorting $sorting
245
	 * @return static
246
	 */
247
	public function sort(Sorting $sorting)
248
	{
249
		if (is_callable($sorting->getSortCallback())) {
250
			call_user_func(
251
				$sorting->getSortCallback(),
252
				$this->criteria,
253
				$sorting->getSort()
254
			);
255
256
			return $this;
257
		}
258
259
		if ($sort = $sorting->getSort()) {
260
			$this->criteria->orderBy($sort);
261
			return $this;
262
		}
263
264
		$this->criteria->orderBy([$this->primary_key => 'ASC']);
265
266
		return $this;
267
	}
268
269
	/**
270
	 * @param string $aggregation_type
271
	 * @param string $column
272
	 * @return mixed
273
	 */
274
	public function addAggregationColumn($aggregation_type, $column)
275
	{
276
		$this->aggregations[$column] = $aggregation_type;
277
	}
278
279
	/**
280
	 * get aggregation row
281
	 * @return array
282
	 */
283 View Code Duplication
	public function getAggregationData()
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...
284
	{
285
		$result = [];
286
		foreach ($this->data_source as $row) {
287
			foreach ($this->aggregations as $column => $aggregation_type) {
288
				switch ($aggregation_type) {
289
					case ColumnAggregationFunction::AGGREGATION_TYPE_SUM:
290
					case ColumnAggregationFunction::AGGREGATION_TYPE_AVG:
291
						if (!isset($result[$column])) {
292
							$result[$column] = 0;
293
						}
294
						$result[$column] += $row[$column];
295
						break;
296
					case ColumnAggregationFunction::AGGREGATION_TYPE_MIN:
297
						if (!isset($result[$column]) || $row[$column] < $result[$column]) {
298
							$result[$column] = $row[$column];
299
						}
300
						break;
301
					case ColumnAggregationFunction::AGGREGATION_TYPE_MAX:
302
						if (!isset($result[$column]) || $row[$column] > $result[$column]) {
303
							$result[$column] = $row[$column];
304
						}
305
						break;
306
				}
307
			}
308
		}
309
310
		foreach ($this->aggregations as $column => $aggregation_type) {
311
			if ($aggregation_type == ColumnAggregationFunction::AGGREGATION_TYPE_AVG) {
312
				$result[$column] =  $result[$column] / $this->data_source->count();
313
			}
314
		}
315
		return $result;
316
	}
317
}
318