1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* This file is part of the ReportBundle package |
5
|
|
|
* |
6
|
|
|
* (c) symball <http://simonball.me> |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please view the LICENSE file |
9
|
|
|
* that was distributed with this source code. |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace Symball\ReportBundle\Query; |
13
|
|
|
|
14
|
|
|
use Doctrine\Common\Persistence\ObjectRepository; |
15
|
|
|
use Symball\ReportBundle\Interfaces\QueryInterface; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* The query class is responsible for performing information gathering operations |
19
|
|
|
* and typically involves a database query |
20
|
|
|
* |
21
|
|
|
* @author Simon Ball <simonball at simonball dot me> |
22
|
|
|
*/ |
23
|
|
|
class Base implements QueryInterface |
24
|
|
|
{ |
25
|
|
|
protected $numberDataSets = 1; |
26
|
|
|
protected $currentDataSet = 0; |
27
|
|
|
protected $modifiers = []; |
28
|
|
|
protected $queryBase; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* Stringify the current data set |
32
|
|
|
* |
33
|
|
|
* @return string |
34
|
|
|
*/ |
35
|
|
|
public function __toString() |
36
|
|
|
{ |
37
|
|
|
return $this->getTitle(); |
38
|
|
|
} |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Set the tracking parameters back to their base value |
42
|
|
|
* @return $this |
43
|
|
|
*/ |
44
|
|
|
public function reset() |
45
|
|
|
{ |
46
|
|
|
$this->numberDataSets = 1; |
47
|
|
|
$this->currentDataset = 0; |
|
|
|
|
48
|
|
|
|
49
|
|
|
return $this; |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Inform the service how many rounds the report will go through |
54
|
|
|
* |
55
|
|
|
* @param integer $count |
56
|
|
|
* @return $this |
57
|
|
|
* @throws \InvalidArgumentException if count not an integer, zero or less |
58
|
|
|
*/ |
59
|
|
|
public function setNumberDataSets($count) |
60
|
|
|
{ |
61
|
|
|
if (!is_int($count)) { |
62
|
|
|
throw new \InvalidArgumentException('Must be an integer'); |
63
|
|
|
} |
64
|
|
|
if ($count < 1) { |
65
|
|
|
throw new \InvalidArgumentException('Cannot have a negative number of sets'); |
66
|
|
|
} |
67
|
|
|
$this->numberDataSets = $count; |
68
|
|
|
|
69
|
|
|
return $this; |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* Attempt to move the query counter forward and halt operations if at end |
74
|
|
|
* |
75
|
|
|
* @return boolean True if can continue |
76
|
|
|
*/ |
77
|
|
|
public function tick() |
78
|
|
|
{ |
79
|
|
|
++$this->currentDataSet; |
80
|
|
|
if ($this->currentDataSet <= $this->numberDataSets) { |
81
|
|
|
return true; |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
// TODO shutdown procedure on query interface? |
85
|
|
|
return false; |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* Set the Doctrine query repository to be used |
90
|
|
|
* |
91
|
|
|
* @param ObjectRepository $repository |
92
|
|
|
* @return $this |
93
|
|
|
*/ |
94
|
|
|
public function setRepository(ObjectRepository $repository) |
95
|
|
|
{ |
96
|
|
|
$this->repository = $repository; |
|
|
|
|
97
|
|
|
|
98
|
|
|
return $this; |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Set the Query Builder object which will form the basis of all information |
103
|
|
|
* gathering |
104
|
|
|
* |
105
|
|
|
* @param object $query |
106
|
|
|
* @return $this |
107
|
|
|
*/ |
108
|
|
|
public function setQueryBase($query) |
109
|
|
|
{ |
110
|
|
|
// TODO Check if it is really a querybuilder once storage agnostic |
111
|
|
|
$this->queryBase = $query; |
112
|
|
|
|
113
|
|
|
return $this; |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* Influence the current query that will be run by adding modifier conditions |
118
|
|
|
* |
119
|
|
|
* @param string $field The database field |
120
|
|
|
* @param array $options Options associated with the modifier |
121
|
|
|
* @return $this |
122
|
|
|
* @throws \InvalidArgumentException If minimum options are not set |
123
|
|
|
*/ |
124
|
|
|
public function addModifier($field, $options = []) |
125
|
|
|
{ |
126
|
|
|
|
127
|
|
|
if (!isset($options['type']) || !isset($options['value'])) { |
128
|
|
|
throw new \InvalidArgumentException('Modifier must have at least type and value present'); |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
$this->modifiers[$field] = $options; |
132
|
|
|
|
133
|
|
|
return $this; |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* Using the query base and available modifiers, create the base part of the |
138
|
|
|
* database query to be run in the current data set |
139
|
|
|
* |
140
|
|
|
* @return object QueryBuilder |
141
|
|
|
* @throws \Exception If an unrecognized query modifier is being used |
142
|
|
|
*/ |
143
|
|
|
public function prepareQuery() |
144
|
|
|
{ |
145
|
|
|
|
146
|
|
|
$query = clone $this->queryBase; |
147
|
|
|
foreach ($this->getModifiers() as $key => $options) { |
148
|
|
|
switch (strtoupper($options['type'])) { |
149
|
|
|
case 'EQUALS': |
150
|
|
|
$query->addAnd( |
151
|
|
|
$query->expr() |
152
|
|
|
->field($key) |
153
|
|
|
->equals($options['value']) |
154
|
|
|
); |
155
|
|
|
break; |
156
|
|
|
|
157
|
|
|
case 'REFERENCES': |
158
|
|
|
$query->addAnd( |
159
|
|
|
$query->expr() |
160
|
|
|
->field($key) |
161
|
|
|
->references($options['value']) |
162
|
|
|
); |
163
|
|
|
break; |
164
|
|
|
|
165
|
|
|
default: |
166
|
|
|
throw new \Exception('Query modifier for: ' . $key . ' not recognised:' . $options['type']); |
167
|
|
|
} |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
return $query; |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* Attempt to build the actual database operation and return the result |
175
|
|
|
* @return type |
176
|
|
|
* @throws \Exception |
177
|
|
|
*/ |
178
|
|
|
public function run() |
179
|
|
|
{ |
180
|
|
|
|
181
|
|
|
if (!$this->repository) { |
182
|
|
|
throw new \Exception('Set a repository before trying to run query'); |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
// If no base query has been set, create a default one |
186
|
|
|
if (!$this->queryBase) { |
187
|
|
|
$this->queryBase = $this->repository->createQueryBuilder(); |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
$query = $this->prepareQuery(); |
191
|
|
|
|
192
|
|
|
// Empty the modifiers after the query has been run |
193
|
|
|
$this->modifiers = []; |
194
|
|
|
|
195
|
|
|
return $query->getQuery()->execute(); |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* Return the Doctrine repository object |
200
|
|
|
* @return object |
201
|
|
|
*/ |
202
|
|
|
public function getRepository() |
203
|
|
|
{ |
204
|
|
|
return $this->repository; |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
/** |
208
|
|
|
* Return the current set counter |
209
|
|
|
* |
210
|
|
|
* @return integer |
211
|
|
|
*/ |
212
|
|
|
public function getNumberDataSetCurrent() |
213
|
|
|
{ |
214
|
|
|
return $this->currentDataSet; |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* Return the total number of data sets to be run |
219
|
|
|
* |
220
|
|
|
* @return integer |
221
|
|
|
*/ |
222
|
|
|
public function getNumberDataSetCount() |
223
|
|
|
{ |
224
|
|
|
return $this->numberDataSets; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* Return the current modifiers in their array definition format |
229
|
|
|
* |
230
|
|
|
* @return array |
231
|
|
|
*/ |
232
|
|
|
public function getModifiers() |
233
|
|
|
{ |
234
|
|
|
return $this->modifiers; |
235
|
|
|
} |
236
|
|
|
/** |
237
|
|
|
* Display the set heading |
238
|
|
|
* |
239
|
|
|
* @return string |
240
|
|
|
*/ |
241
|
|
|
public function getTitle() |
242
|
|
|
{ |
243
|
|
|
return ''; |
244
|
|
|
} |
245
|
|
|
} |
246
|
|
|
|
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.
If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.