Completed
Pull Request — master (#375)
by Dalibor
05:23
created

addAggregationColumn()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 2
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\Filter;
14
use Ublaboo\DataGrid\Utils\Sorting;
15
16
final class DoctrineCollectionDataSource extends FilterableDataSource implements IDataSource
17
{
18
19
	/**
20
	 * @var Collection
21
	 */
22
	private $data_source;
23
24
	/**
25
	 * @var string
26
	 */
27
	private $primary_key;
28
29
	/**
30
	 * @var Criteria
31
	 */
32
	private $criteria;
33
34
	/**
35
	 * @var array
36
	 */
37
	protected $aggregations = [];
38
39
	/**
40
	 * @param Collection  $collection
41
	 * @param string      $primary_key
42
	 */
43
	public function __construct(Collection $collection, $primary_key)
44
	{
45
		$this->criteria = Criteria::create();
46
		$this->data_source = $collection;
47
		$this->primary_key = $primary_key;
48
	}
49
50
51
	/**
52
	 * @return Collection
53
	 */
54
	private function getFilteredCollection()
55
	{
56
		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...
57
	}
58
59
60
	/********************************************************************************
61
	 *                          IDataSource implementation                          *
62
	 ********************************************************************************/
63
64
65
	/**
66
	 * Get count of data
67
	 * @return int
68
	 */
69
	public function getCount()
70
	{
71
		return $this->getFilteredCollection()->count();
72
	}
73
74
75
	/**
76
	 * Get the data
77
	 * @return array
78
	 */
79
	public function getData()
80
	{
81
		return $this->getFilteredCollection()->toArray();
82
	}
83
84
85
	/**
86
	 * Filter data - get one row
87
	 * @param array $condition
88
	 * @return static
89
	 */
90
	public function filterOne(array $condition)
91
	{
92
		foreach ($condition as $column => $value) {
93
			$expr = Criteria::expr()->eq($column, $value);
94
			$this->criteria->andWhere($expr);
95
		}
96
97
		return $this;
98
	}
99
100
101
	/**
102
	 * Filter by date
103
	 * @param  Filter\FilterDate $filter
104
	 * @return void
105
	 */
106
	public function applyFilterDate(Filter\FilterDate $filter)
107
	{
108
		foreach ($filter->getCondition() as $column => $value) {
109
			$date = \DateTime::createFromFormat($filter->getPhpFormat(), $value)
110
				->format('Y-m-d H:i:s');
111
112
			$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...
113
			$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...
114
115
			$this->criteria->andWhere($from)->andWhere($to);
116
		}
117
	}
118
119
120
	/**
121
	 * Filter by date range
122
	 * @param  Filter\FilterDateRange $filter
123
	 * @return void
124
	 */
125
	public function applyFilterDateRange(Filter\FilterDateRange $filter)
126
	{
127
		$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...
128
129 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...
130
			$date_from = \DateTime::createFromFormat($filter->getPhpFormat(), $value_from)
131
				->setTime(0, 0, 0)
132
				->format('Y-m-d H:i:s');
133
134
			$expr = Criteria::expr()->gte($filter->getColumn(), $date_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...
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 = \DateTime::createFromFormat($filter->getPhpFormat(), $value_to)
140
				->setTime(23, 59, 59)
141
				->format('Y-m-d H:i:s');
142
143
			$expr = Criteria::expr()->lte($filter->getColumn(), $date_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...
144
			$this->criteria->andWhere($expr);
145
		}
146
	}
147
148
149
	/**
150
	 * Filter by range
151
	 * @param  Filter\FilterRange $filter
152
	 * @return void
153
	 */
154
	public function applyFilterRange(Filter\FilterRange $filter)
155
	{
156
		$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...
157
158
		if ($value_from = $values['from']) {
159
			$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...
160
			$this->criteria->andWhere($expr);
161
		}
162
163
		if ($value_to = $values['to']) {
164
			$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...
165
			$this->criteria->andWhere($expr);
166
		}
167
	}
168
169
170
	/**
171
	 * Filter by keyword
172
	 * @param  Filter\FilterText $filter
173
	 * @return void
174
	 */
175
	public function applyFilterText(Filter\FilterText $filter)
176
	{
177
		$exprs = [];
178
179
		foreach ($filter->getCondition() as $column => $value) {
180
			if($filter->isExactSearch()) {
181
				$exprs[] = Criteria::expr()->eq($column, $value);
182
				continue;
183
			}
184
185 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...
186
				$words = [$value];
187
			} else {
188
				$words = explode(' ', $value);
189
			}
190
191
			foreach ($words as $word) {
192
				$exprs[] = Criteria::expr()->contains($column, $word);
193
			}
194
		}
195
196
		$expr = call_user_func_array([Criteria::expr(), 'orX'], $exprs);
197
		$this->criteria->andWhere($expr);
198
	}
199
200
201
	/**
202
	 * Filter by multi select value
203
	 * @param  Filter\FilterMultiSelect $filter
204
	 * @return void
205
	 */
206
	public function applyFilterMultiSelect(Filter\FilterMultiSelect $filter)
207
	{
208
		$values = $filter->getCondition()[$filter->getColumn()];
209
210
		$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...
211
		$this->criteria->andWhere($expr);
212
	}
213
214
215
	/**
216
	 * Filter by select value
217
	 * @param  Filter\FilterSelect $filter
218
	 * @return void
219
	 */
220
	public function applyFilterSelect(Filter\FilterSelect $filter)
221
	{
222
		foreach ($filter->getCondition() as $column => $value) {
223
			$expr = Criteria::expr()->eq($column, $value);
224
			$this->criteria->andWhere($expr);
225
		}
226
	}
227
228
229
	/**
230
	 * Apply limit and offset on data
231
	 * @param int $offset
232
	 * @param int $limit
233
	 * @return static
234
	 */
235
	public function limit($offset, $limit)
236
	{
237
		$this->criteria->setFirstResult($offset)->setMaxResults($limit);
238
239
		return $this;
240
	}
241
242
243
	/**
244
	 * Sort data
245
	 * @param Sorting $sorting
246
	 * @return static
247
	 */
248
	public function sort(Sorting $sorting)
249
	{
250
		if (is_callable($sorting->getSortCallback())) {
251
			call_user_func(
252
				$sorting->getSortCallback(),
253
				$this->criteria,
254
				$sorting->getSort()
255
			);
256
257
			return $this;
258
		}
259
260
		if ($sort = $sorting->getSort()) {
261
			$this->criteria->orderBy($sort);
262
			return $this;
263
		}
264
265
		$this->criteria->orderBy([$this->primary_key => 'ASC']);
266
267
		return $this;
268
	}
269
270
	/**
271
	 * @param string $aggregation_type
272
	 * @param string $column
273
	 * @return mixed
274
	 */
275
	public function addAggregationColumn($aggregation_type, $column)
276
	{
277
		$this->aggregations[$column] = $aggregation_type;
278
	}
279
280
	/**
281
	 * get aggregation row
282
	 * @return array
283
	 */
284 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...
285
	{
286
		$result = [];
287
		foreach ($this->data_source as $row) {
288
			foreach ($this->aggregations as $column => $aggregation_type) {
289
				switch ($aggregation_type) {
290
					case ColumnAggregationFunction::$aggregation_type_sum:
291
					case ColumnAggregationFunction::$aggregation_type_avg:
292
						if (!isset($result[$column])) {
293
							$result[$column] = 0;
294
						}
295
						$result[$column] += $row[$column];
296
						break;
297
					case ColumnAggregationFunction::$aggregation_type_min:
298
						if (!isset($result[$column]) || $row[$column] < $result[$column]) {
299
							$result[$column] = $row[$column];
300
						}
301
						break;
302
					case ColumnAggregationFunction::$aggregation_type_max:
303
						if (!isset($result[$column]) || $row[$column] > $result[$column]) {
304
							$result[$column] = $row[$column];
305
						}
306
						break;
307
				}
308
			}
309
		}
310
311
		foreach ($this->aggregations as $column => $aggregation_type) {
312
			if ($aggregation_type == ColumnAggregationFunction::$aggregation_type_avg) {
313
				$result[$column] =  $result[$column] / $this->data_source->count();
314
			}
315
		}
316
		return $result;
317
	}
318
}
319