Elgg  Version 4.x
ElggBatch.php
Go to the documentation of this file.
1 <?php
68 class ElggBatch implements \Countable, \Iterator {
69 
75  private $results = [];
76 
82  private $getter = null;
83 
89  private $options = [];
90 
96  private $chunkSize = 25;
97 
103  private $callback = null;
104 
110  private $offset = 0;
111 
117  private $limit = 0;
118 
124  private $retrievedResults = 0;
125 
131  private $resultIndex = 0;
132 
138  private $chunkIndex = 0;
139 
145  private $processedResults = 0;
146 
152  private $validGetter = null;
153 
159  public $callbackResult = null;
160 
166  private $incrementOffset = true;
167 
173  private $reportedFailures = 0;
174 
197  public function __construct(callable $getter, array $options, $callback = null, int $chunk_size = 25, bool $inc_offset = true) {
198 
199  $this->getter = $getter;
200  $this->options = $options;
201  $this->callback = $callback;
202  $this->chunkSize = $chunk_size;
203  $this->setIncrementOffset($inc_offset);
204 
205  if ($this->chunkSize <= 0) {
206  $this->chunkSize = 25;
207  }
208 
209  // store these so we can compare later
210  $this->offset = elgg_extract('offset', $options, 0);
211  $this->limit = elgg_extract('limit', $options, _elgg_services()->config->default_limit);
212 
213  // if passed a callback, create a new \ElggBatch with the same options
214  // and pass each to the callback.
215  if ($callback && is_callable($callback)) {
216  $batch = new \ElggBatch($getter, $options, null, $chunk_size, $inc_offset);
217 
218  $all_results = null;
219 
220  foreach ($batch as $result) {
221  $result = call_user_func($callback, $result, $getter, $options);
222 
223  if (!isset($all_results)) {
224  if ($result === true || $result === false || $result === null) {
225  $all_results = $result;
226  } else {
227  $all_results = [];
228  }
229  }
230 
231  if (($result === true || $result === false || $result === null) && !is_array($all_results)) {
232  $all_results = $result && $all_results;
233  } else {
234  $all_results[] = $result;
235  }
236  }
237 
238  $this->callbackResult = $all_results;
239  }
240  }
241 
247  private function getNextResultsChunk() {
248 
249  // always reset results.
250  $this->results = [];
251 
252  if (!isset($this->validGetter)) {
253  $this->validGetter = is_callable($this->getter);
254  }
255 
256  if (!$this->validGetter) {
257  return false;
258  }
259 
260  $limit = $this->chunkSize;
261 
262  // if someone passed limit = 0 they want everything.
263  if ($this->limit != 0) {
264  if ($this->retrievedResults >= $this->limit) {
265  return false;
266  }
267 
268  // if original limit < chunk size, set limit to original limit
269  // else if the number of results we'll fetch if greater than the original limit
270  if ($this->limit < $this->chunkSize) {
271  $limit = $this->limit;
272  } elseif ($this->retrievedResults + $this->chunkSize > $this->limit) {
273  // set the limit to the number of results remaining in the original limit
274  $limit = $this->limit - $this->retrievedResults;
275  }
276  }
277 
278  if ($this->incrementOffset) {
279  $offset = $this->offset + $this->retrievedResults;
280  } else {
281  $offset = $this->offset + $this->reportedFailures;
282  }
283 
284  $current_options = [
285  'limit' => $limit,
286  'offset' => $offset,
287  '__ElggBatch' => $this,
288  ];
289 
290  $options = array_merge($this->options, $current_options);
291 
292  // batch result sets tend to be large; we don't want to cache these.
293  _elgg_services()->queryCache->disable(false);
294 
295  $this->results = call_user_func($this->getter, $options);
296 
297  $num_results = count($this->results);
298 
299  if ($this->results) {
300  $this->chunkIndex++;
301  $this->resultIndex = 0;
302 
303  $this->retrievedResults += $num_results;
304  if ($num_results === 0) {
305  // This fetch was *all* incompletes! We need to fetch until we can either
306  // offer at least one row to iterate over, or give up.
307  return $this->getNextResultsChunk();
308  }
309  _elgg_services()->queryCache->enable();
310  return true;
311  }
312 
313  // no result
314  _elgg_services()->queryCache->enable();
315  return false;
316  }
317 
325  public function setIncrementOffset(bool $increment = true) {
326  $this->incrementOffset = $increment;
327  }
328 
334  public function setChunkSize(int $size = 25) {
335  $this->chunkSize = $size;
336  }
337 
346  public function reportFailure(int $num = 1) {
347  $this->reportedFailures += $num;
348  }
349 
357  #[\ReturnTypeWillChange]
358  public function rewind() {
359  $this->resultIndex = 0;
360  $this->retrievedResults = 0;
361  $this->processedResults = 0;
362  $this->reportedFailures = 0;
363 
364  // only grab results if we haven't yet or we're crossing chunks
365  if ($this->chunkIndex == 0 || $this->limit > $this->chunkSize) {
366  $this->chunkIndex = 0;
367  $this->getNextResultsChunk();
368  }
369  }
370 
374  #[\ReturnTypeWillChange]
375  public function current() {
376  return current($this->results);
377  }
378 
382  #[\ReturnTypeWillChange]
383  public function key() {
384  return $this->processedResults;
385  }
386 
390  #[\ReturnTypeWillChange]
391  public function next() {
392  // if we'll be at the end.
393  if (($this->processedResults + 1) >= $this->limit && $this->limit > 0) {
394  $this->results = [];
395  return;
396  }
397 
398  // if we'll need new results.
399  if (($this->resultIndex + 1) >= $this->chunkSize) {
400  if (!$this->getNextResultsChunk()) {
401  $this->results = [];
402  return;
403  }
404 
405  $result = current($this->results);
406  } else {
407  // the function above resets the indexes, so only inc if not
408  // getting new set
409  $this->resultIndex++;
410  $result = next($this->results);
411  }
412 
413  $this->processedResults++;
414  return $result;
415  }
416 
420  #[\ReturnTypeWillChange]
421  public function valid() {
422  if (!is_array($this->results)) {
423  return false;
424  }
425  $key = key($this->results);
426  return ($key !== null && $key !== false);
427  }
428 
438  #[\ReturnTypeWillChange]
439  public function count() {
440  if (!is_callable($this->getter)) {
441  $inspector = new \Elgg\Debug\Inspector();
442  throw new RuntimeException("Getter is not callable: " . $inspector->describeCallable($this->getter));
443  }
444 
445  $options = array_merge($this->options, ['count' => true]);
446 
447  return call_user_func($this->getter, $options);
448  }
449 }
__construct(callable $getter, array $options, $callback=null, int $chunk_size=25, bool $inc_offset=true)
Batches operations on any elgg_get_*() or compatible function that supports an options array...
Definition: ElggBatch.php:197
valid()
{}
Definition: ElggBatch.php:421
current()
{}
Definition: ElggBatch.php:375
rewind()
Implements Iterator.
Definition: ElggBatch.php:358
setIncrementOffset(bool $increment=true)
Increment the offset from the original options array? Setting to false is required for callbacks that...
Definition: ElggBatch.php:325
reportFailure(int $num=1)
Report a number of failures during the batch execution, this will increase the internal offset by $nu...
Definition: ElggBatch.php:346
setChunkSize(int $size=25)
Set chunk size.
Definition: ElggBatch.php:334
count()
Count the total results available at this moment.
Definition: ElggBatch.php:439
elgg_extract($key, $array, $default=null, $strict=true)
Checks for $array[$key] and returns its value if it exists, else returns $default.
Definition: elgglib.php:686
next()
{}
Definition: ElggBatch.php:391
$size
Definition: thumb.php:23
if($container instanceof ElggGroup &&$container->guid!=elgg_get_page_owner_guid()) $key
Definition: summary.php:44
if($item instanceof\ElggEntity) elseif($item instanceof\ElggRiverItem) elseif($item instanceof ElggRelationship) elseif(is_callable([$item, 'getType']))
Definition: item.php:48
key()
{}
Definition: ElggBatch.php:383
_elgg_services()
Get the global service provider.
Definition: elgglib.php:777