This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Efficiently run operations on batches of results for any function |
||
4 | * that supports an options array. |
||
5 | * |
||
6 | * This is usually used with elgg_get_entities() and friends, |
||
7 | * elgg_get_annotations(), and elgg_get_metadata(). |
||
8 | * |
||
9 | * If you pass a valid PHP callback, all results will be run through that |
||
10 | * callback. You can still foreach() through the result set after. Valid |
||
11 | * PHP callbacks can be a string, an array, or a closure. |
||
12 | * {@link http://php.net/manual/en/language.pseudo-types.php} |
||
13 | * |
||
14 | * The callback function must accept 3 arguments: an entity, the getter |
||
15 | * used, and the options used. |
||
16 | * |
||
17 | * Results from the callback are stored in callbackResult. If the callback |
||
18 | * returns only booleans, callbackResults will be the combined result of |
||
19 | * all calls. If no entities are processed, callbackResults will be null. |
||
20 | * |
||
21 | * If the callback returns anything else, callbackresult will be an indexed |
||
22 | * array of whatever the callback returns. If returning error handling |
||
23 | * information, you should include enough information to determine which |
||
24 | * result you're referring to. |
||
25 | * |
||
26 | * Don't combine returning bools and returning something else. |
||
27 | * |
||
28 | * Note that returning false will not stop the foreach. |
||
29 | * |
||
30 | * @warning If your callback or foreach loop deletes or disable entities |
||
31 | * you MUST call setIncrementOffset(false) or set that when instantiating. |
||
32 | * This forces the offset to stay what it was in the $options array. |
||
33 | * |
||
34 | * @example |
||
35 | * <code> |
||
36 | * // using foreach |
||
37 | * $batch = new \ElggBatch('elgg_get_entities', array()); |
||
38 | * $batch->setIncrementOffset(false); |
||
39 | * |
||
40 | * foreach ($batch as $entity) { |
||
41 | * $entity->disable(); |
||
42 | * } |
||
43 | * |
||
44 | * // using both a callback |
||
45 | * $callback = function($result, $getter, $options) { |
||
46 | * var_dump("Looking at annotation id: $result->id"); |
||
47 | * return true; |
||
48 | * } |
||
49 | * |
||
50 | * $batch = new \ElggBatch('elgg_get_annotations', array('guid' => 2), $callback); |
||
51 | * </code> |
||
52 | * |
||
53 | * @package Elgg.Core |
||
54 | * @subpackage DataModel |
||
55 | * @since 1.8 |
||
56 | */ |
||
57 | class ElggBatch |
||
58 | implements \Iterator { |
||
59 | |||
60 | /** |
||
61 | * The objects to interator over. |
||
62 | * |
||
63 | * @var array |
||
64 | */ |
||
65 | private $results = array(); |
||
66 | |||
67 | /** |
||
68 | * The function used to get results. |
||
69 | * |
||
70 | * @var mixed A string, array, or closure, or lamda function |
||
71 | */ |
||
72 | private $getter = null; |
||
73 | |||
74 | /** |
||
75 | * The number of results to grab at a time. |
||
76 | * |
||
77 | * @var int |
||
78 | */ |
||
79 | private $chunkSize = 25; |
||
80 | |||
81 | /** |
||
82 | * A callback function to pass results through. |
||
83 | * |
||
84 | * @var mixed A string, array, or closure, or lamda function |
||
85 | */ |
||
86 | private $callback = null; |
||
87 | |||
88 | /** |
||
89 | * Start after this many results. |
||
90 | * |
||
91 | * @var int |
||
92 | */ |
||
93 | private $offset = 0; |
||
94 | |||
95 | /** |
||
96 | * Stop after this many results. |
||
97 | * |
||
98 | * @var int |
||
99 | */ |
||
100 | private $limit = 0; |
||
101 | |||
102 | /** |
||
103 | * Number of processed results. |
||
104 | * |
||
105 | * @var int |
||
106 | */ |
||
107 | private $retrievedResults = 0; |
||
108 | |||
109 | /** |
||
110 | * The index of the current result within the current chunk |
||
111 | * |
||
112 | * @var int |
||
113 | */ |
||
114 | private $resultIndex = 0; |
||
115 | |||
116 | /** |
||
117 | * The index of the current chunk |
||
118 | * |
||
119 | * @var int |
||
120 | */ |
||
121 | private $chunkIndex = 0; |
||
122 | |||
123 | /** |
||
124 | * The number of results iterated through |
||
125 | * |
||
126 | * @var int |
||
127 | */ |
||
128 | private $processedResults = 0; |
||
129 | |||
130 | /** |
||
131 | * Is the getter a valid callback |
||
132 | * |
||
133 | * @var bool |
||
134 | */ |
||
135 | private $validGetter = null; |
||
136 | |||
137 | /** |
||
138 | * The result of running all entities through the callback function. |
||
139 | * |
||
140 | * @var mixed |
||
141 | */ |
||
142 | public $callbackResult = null; |
||
143 | |||
144 | /** |
||
145 | * If false, offset will not be incremented. This is used for callbacks/loops that delete. |
||
146 | * |
||
147 | * @var bool |
||
148 | */ |
||
149 | private $incrementOffset = true; |
||
150 | |||
151 | /** |
||
152 | * Entities that could not be instantiated during a fetch |
||
153 | * |
||
154 | * @var \stdClass[] |
||
155 | */ |
||
156 | private $incompleteEntities = array(); |
||
157 | |||
158 | /** |
||
159 | * Total number of incomplete entities fetched |
||
160 | * |
||
161 | * @var int |
||
162 | */ |
||
163 | private $totalIncompletes = 0; |
||
164 | |||
165 | /** |
||
166 | * Batches operations on any elgg_get_*() or compatible function that supports |
||
167 | * an options array. |
||
168 | * |
||
169 | * Instead of returning all objects in memory, it goes through $chunk_size |
||
170 | * objects, then requests more from the server. This avoids OOM errors. |
||
171 | * |
||
172 | * @param string $getter The function used to get objects. Usually |
||
173 | * an elgg_get_*() function, but can be any valid PHP callback. |
||
174 | * @param array $options The options array to pass to the getter function. If limit is |
||
175 | * not set, 10 is used as the default. In most cases that is not |
||
176 | * what you want. |
||
177 | * @param mixed $callback An optional callback function that all results will be passed |
||
178 | * to upon load. The callback needs to accept $result, $getter, |
||
179 | * $options. |
||
180 | * @param int $chunk_size The number of entities to pull in before requesting more. |
||
181 | * You have to balance this between running out of memory in PHP |
||
182 | * and hitting the db server too often. |
||
183 | * @param bool $inc_offset Increment the offset on each fetch. This must be false for |
||
184 | * callbacks that delete rows. You can set this after the |
||
185 | * object is created with {@link \ElggBatch::setIncrementOffset()}. |
||
186 | */ |
||
187 | public function __construct($getter, $options, $callback = null, $chunk_size = 25, |
||
188 | $inc_offset = true) { |
||
189 | |||
190 | $this->getter = $getter; |
||
191 | $this->options = $options; |
||
0 ignored issues
–
show
|
|||
192 | $this->callback = $callback; |
||
193 | $this->chunkSize = $chunk_size; |
||
194 | $this->setIncrementOffset($inc_offset); |
||
195 | |||
196 | if ($this->chunkSize <= 0) { |
||
197 | $this->chunkSize = 25; |
||
198 | } |
||
199 | |||
200 | // store these so we can compare later |
||
201 | $this->offset = elgg_extract('offset', $options, 0); |
||
202 | $this->limit = elgg_extract('limit', $options, elgg_get_config('default_limit')); |
||
203 | |||
204 | // if passed a callback, create a new \ElggBatch with the same options |
||
205 | // and pass each to the callback. |
||
206 | if ($callback && is_callable($callback)) { |
||
207 | $batch = new \ElggBatch($getter, $options, null, $chunk_size, $inc_offset); |
||
208 | |||
209 | $all_results = null; |
||
210 | |||
211 | foreach ($batch as $result) { |
||
212 | $result = call_user_func($callback, $result, $getter, $options); |
||
213 | |||
214 | if (!isset($all_results)) { |
||
215 | if ($result === true || $result === false || $result === null) { |
||
216 | $all_results = $result; |
||
217 | } else { |
||
218 | $all_results = array(); |
||
219 | } |
||
220 | } |
||
221 | |||
222 | if (($result === true || $result === false || $result === null) && !is_array($all_results)) { |
||
223 | $all_results = $result && $all_results; |
||
224 | } else { |
||
225 | $all_results[] = $result; |
||
226 | } |
||
227 | } |
||
228 | |||
229 | $this->callbackResult = $all_results; |
||
230 | } |
||
231 | } |
||
232 | |||
233 | /** |
||
234 | * Tell the process that an entity was incomplete during a fetch |
||
235 | * |
||
236 | * @param \stdClass $row |
||
237 | * |
||
238 | * @access private |
||
239 | */ |
||
240 | public function reportIncompleteEntity(\stdClass $row) { |
||
241 | $this->incompleteEntities[] = $row; |
||
242 | } |
||
243 | |||
244 | /** |
||
245 | * Fetches the next chunk of results |
||
246 | * |
||
247 | * @return bool |
||
248 | */ |
||
249 | private function getNextResultsChunk() { |
||
250 | |||
251 | // always reset results. |
||
252 | $this->results = array(); |
||
253 | |||
254 | if (!isset($this->validGetter)) { |
||
255 | $this->validGetter = is_callable($this->getter); |
||
256 | } |
||
257 | |||
258 | if (!$this->validGetter) { |
||
259 | return false; |
||
260 | } |
||
261 | |||
262 | $limit = $this->chunkSize; |
||
263 | |||
264 | // if someone passed limit = 0 they want everything. |
||
265 | if ($this->limit != 0) { |
||
266 | if ($this->retrievedResults >= $this->limit) { |
||
267 | return false; |
||
268 | } |
||
269 | |||
270 | // if original limit < chunk size, set limit to original limit |
||
271 | // else if the number of results we'll fetch if greater than the original limit |
||
272 | if ($this->limit < $this->chunkSize) { |
||
273 | $limit = $this->limit; |
||
274 | } elseif ($this->retrievedResults + $this->chunkSize > $this->limit) { |
||
275 | // set the limit to the number of results remaining in the original limit |
||
276 | $limit = $this->limit - $this->retrievedResults; |
||
277 | } |
||
278 | } |
||
279 | |||
280 | if ($this->incrementOffset) { |
||
281 | $offset = $this->offset + $this->retrievedResults; |
||
282 | } else { |
||
283 | $offset = $this->offset + $this->totalIncompletes; |
||
284 | } |
||
285 | |||
286 | $current_options = array( |
||
287 | 'limit' => $limit, |
||
288 | 'offset' => $offset, |
||
289 | '__ElggBatch' => $this, |
||
290 | ); |
||
291 | |||
292 | $options = array_merge($this->options, $current_options); |
||
293 | |||
294 | $this->incompleteEntities = array(); |
||
295 | $this->results = call_user_func($this->getter, $options); |
||
0 ignored issues
–
show
It seems like
call_user_func($this->getter, $options) of type * is incompatible with the declared type array of property $results .
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.. ![]() |
|||
296 | |||
297 | // batch result sets tend to be large; we don't want to cache these. |
||
298 | _elgg_services()->db->disableQueryCache(); |
||
299 | |||
300 | $num_results = count($this->results); |
||
301 | $num_incomplete = count($this->incompleteEntities); |
||
302 | |||
303 | $this->totalIncompletes += $num_incomplete; |
||
304 | |||
305 | if ($this->incompleteEntities) { |
||
0 ignored issues
–
show
The expression
$this->incompleteEntities 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 ![]() |
|||
306 | // pad the front of the results with nulls representing the incompletes |
||
307 | array_splice($this->results, 0, 0, array_pad(array(), $num_incomplete, null)); |
||
308 | // ...and skip past them |
||
309 | reset($this->results); |
||
310 | for ($i = 0; $i < $num_incomplete; $i++) { |
||
311 | next($this->results); |
||
312 | } |
||
313 | } |
||
314 | |||
315 | if ($this->results) { |
||
316 | $this->chunkIndex++; |
||
317 | |||
318 | // let the system know we've jumped past the nulls |
||
319 | $this->resultIndex = $num_incomplete; |
||
320 | |||
321 | $this->retrievedResults += ($num_results + $num_incomplete); |
||
322 | if ($num_results == 0) { |
||
323 | // This fetch was *all* incompletes! We need to fetch until we can either |
||
324 | // offer at least one row to iterate over, or give up. |
||
325 | return $this->getNextResultsChunk(); |
||
326 | } |
||
327 | _elgg_services()->db->enableQueryCache(); |
||
328 | return true; |
||
329 | } else { |
||
330 | _elgg_services()->db->enableQueryCache(); |
||
331 | return false; |
||
332 | } |
||
333 | } |
||
334 | |||
335 | /** |
||
336 | * Increment the offset from the original options array? Setting to |
||
337 | * false is required for callbacks that delete rows. |
||
338 | * |
||
339 | * @param bool $increment Set to false when deleting data |
||
340 | * @return void |
||
341 | */ |
||
342 | public function setIncrementOffset($increment = true) { |
||
343 | $this->incrementOffset = (bool) $increment; |
||
344 | } |
||
345 | |||
346 | /** |
||
347 | * Implements Iterator |
||
348 | */ |
||
349 | |||
350 | /** |
||
351 | * PHP Iterator Interface |
||
352 | * |
||
353 | * @see Iterator::rewind() |
||
354 | * @return void |
||
355 | */ |
||
356 | public function rewind() { |
||
357 | $this->resultIndex = 0; |
||
358 | $this->retrievedResults = 0; |
||
359 | $this->processedResults = 0; |
||
360 | |||
361 | // only grab results if we haven't yet or we're crossing chunks |
||
362 | if ($this->chunkIndex == 0 || $this->limit > $this->chunkSize) { |
||
363 | $this->chunkIndex = 0; |
||
364 | $this->getNextResultsChunk(); |
||
365 | } |
||
366 | } |
||
367 | |||
368 | /** |
||
369 | * PHP Iterator Interface |
||
370 | * |
||
371 | * @see Iterator::current() |
||
372 | * @return mixed |
||
373 | */ |
||
374 | public function current() { |
||
375 | return current($this->results); |
||
376 | } |
||
377 | |||
378 | /** |
||
379 | * PHP Iterator Interface |
||
380 | * |
||
381 | * @see Iterator::key() |
||
382 | * @return int |
||
383 | */ |
||
384 | public function key() { |
||
385 | return $this->processedResults; |
||
386 | } |
||
387 | |||
388 | /** |
||
389 | * PHP Iterator Interface |
||
390 | * |
||
391 | * @see Iterator::next() |
||
392 | * @return mixed |
||
393 | */ |
||
394 | public function next() { |
||
395 | // if we'll be at the end. |
||
396 | if (($this->processedResults + 1) >= $this->limit && $this->limit > 0) { |
||
397 | $this->results = array(); |
||
398 | return false; |
||
399 | } |
||
400 | |||
401 | // if we'll need new results. |
||
402 | if (($this->resultIndex + 1) >= $this->chunkSize) { |
||
403 | if (!$this->getNextResultsChunk()) { |
||
404 | $this->results = array(); |
||
405 | return false; |
||
406 | } |
||
407 | |||
408 | $result = current($this->results); |
||
409 | } else { |
||
410 | // the function above resets the indexes, so only inc if not |
||
411 | // getting new set |
||
412 | $this->resultIndex++; |
||
413 | $result = next($this->results); |
||
414 | } |
||
415 | |||
416 | $this->processedResults++; |
||
417 | return $result; |
||
418 | } |
||
419 | |||
420 | /** |
||
421 | * PHP Iterator Interface |
||
422 | * |
||
423 | * @see Iterator::valid() |
||
424 | * @return bool |
||
425 | */ |
||
426 | public function valid() { |
||
427 | if (!is_array($this->results)) { |
||
428 | return false; |
||
429 | } |
||
430 | $key = key($this->results); |
||
431 | return ($key !== null && $key !== false); |
||
432 | } |
||
433 | } |
||
434 |
In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:
Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion: