diff options
| -rw-r--r-- | engine/classes/ElggBatch.php | 75 | ||||
| -rw-r--r-- | engine/tests/api/helpers.php | 83 | 
2 files changed, 137 insertions, 21 deletions
diff --git a/engine/classes/ElggBatch.php b/engine/classes/ElggBatch.php index 3d01133fa..0cb13eb32 100644 --- a/engine/classes/ElggBatch.php +++ b/engine/classes/ElggBatch.php @@ -3,47 +3,51 @@   * Efficiently run operations on batches of results for any function   * that supports an options array.   * - * This is usually used with elgg_get_entities() and friends, elgg_get_annotations() - * and elgg_get_metadata(). + * This is usually used with elgg_get_entities() and friends, + * elgg_get_annotations(), and elgg_get_metadata().   * - * If you pass a valid PHP callback, all results will be run through that callback. - * You can still foreach() through the result set after.  Valid PHP callbacks - * can be a string, an array, or a closure. + * If you pass a valid PHP callback, all results will be run through that + * callback. You can still foreach() through the result set after.  Valid + * PHP callbacks can be a string, an array, or a closure.   * {@link http://php.net/manual/en/language.pseudo-types.php}   * - * The callback function must accept 3 arguments: an entity, the getter used, and the options used. + * The callback function must accept 3 arguments: an entity, the getter + * used, and the options used.   * - * Results from the callback are stored in callbackResult. - * If the callback returns only booleans, callbackResults will be the combined - * result of all calls. + * Results from the callback are stored in callbackResult. If the callback + * returns only booleans, callbackResults will be the combined result of + * all calls.   * - * If the callback returns anything else, callbackresult will be an indexed array - * of whatever the callback returns.  If returning error handling information, - * you should include enough information to determine which result you're referring - * to. + * If the callback returns anything else, callbackresult will be an indexed + * array of whatever the callback returns.  If returning error handling + * information, you should include enough information to determine which + * result you're referring to.   *   * Don't combine returning bools and returning something else.   *   * Note that returning false will not stop the foreach.   * + * @warning If your callback or foreach loop deletes or disable entities + * you MUST call setIncrementOffset(false) or set that when instantiating. + * This forces the offset to stay what it was in the $options array. + *   * @example   * <code> + * // using foreach   * $batch = new ElggBatch('elgg_get_entities', array()); + * $batch->setIncrementOffset(false);   *   * foreach ($batch as $entity) {   * 	$entity->disable();   * }   * + * // using both a callback   * $callback = function($result, $getter, $options) { - * 	var_dump("Going to delete annotation id: $result->id"); + * 	var_dump("Looking at annotation id: $result->id");   *  return true;   * }   *   * $batch = new ElggBatch('elgg_get_annotations', array('guid' => 2), $callback); - * - * foreach ($batch as $annotation) { - * 	$annotation->delete(); - * }   * </code>   *   * @package    Elgg.Core @@ -139,6 +143,13 @@ class ElggBatch  	public $callbackResult = null;  	/** +	 * If false, offset will not be incremented. This is used for callbacks/loops that delete. +	 * +	 * @var bool +	 */ +	private $incrementOffset = true; + +	/**  	 * Batches operations on any elgg_get_*() or compatible function that supports  	 * an options array.  	 * @@ -156,12 +167,18 @@ class ElggBatch  	 * @param int    $chunk_size The number of entities to pull in before requesting more.  	 *                           You have to balance this between running out of memory in PHP  	 *                           and hitting the db server too often. +	 * @param bool   $inc_offset Increment the offset on each fetch. This must be false for +	 *                           callbacks that delete rows. You can set this after the +	 *                           object is created with {@see ElggBatch::setIncrementOffset()}.  	 */ -	public function __construct($getter, $options, $callback = null, $chunk_size = 25) { +	public function __construct($getter, $options, $callback = null, $chunk_size = 25, +			$inc_offset = true) { +		  		$this->getter = $getter;  		$this->options = $options;  		$this->callback = $callback;  		$this->chunkSize = $chunk_size; +		$this->setIncrementOffset($inc_offset);  		if ($this->chunkSize <= 0) {  			$this->chunkSize = 25; @@ -174,7 +191,7 @@ class ElggBatch  		// if passed a callback, create a new ElggBatch with the same options  		// and pass each to the callback.  		if ($callback && is_callable($callback)) { -			$batch = new ElggBatch($getter, $options, null, $chunk_size); +			$batch = new ElggBatch($getter, $options, null, $chunk_size, $inc_offset);  			$all_results = null; @@ -245,9 +262,15 @@ class ElggBatch  			}  		} +		if ($this->incrementOffset) { +			$offset = $this->offset + $this->retrievedResults; +		} else { +			$offset = $this->offset; +		} +  		$current_options = array(  			'limit' => $limit, -			'offset' => $this->offset + $this->retrievedResults +			'offset' => $offset  		);  		$options = array_merge($this->options, $current_options); @@ -270,6 +293,16 @@ class ElggBatch  	}  	/** +	 * Increment the offset from the original options array? Setting to +	 * false is required for callbacks that delete rows. +	 * +	 * @param bool $increment +	 */ +	public function setIncrementOffset($increment = true) { +		$this->incrementOffset = (bool) $increment; +	} + +	/**  	 * Implements Iterator  	 */ diff --git a/engine/tests/api/helpers.php b/engine/tests/api/helpers.php index 77205138d..a615be0c0 100644 --- a/engine/tests/api/helpers.php +++ b/engine/tests/api/helpers.php @@ -518,4 +518,87 @@ class ElggCoreHelpersTest extends ElggCoreUnitTest {  		$this->assertIdentical($elements_sorted_string, $test_elements);  	} + +	// see http://trac.elgg.org/ticket/4288 +	public function testElggBatchIncOffset() { +		// normal increment +		$options = array( +			'offset' => 0, +			'limit' => 11 +		); +		$batch = new ElggBatch(array('ElggCoreHelpersTest', 'test_elgg_batch_callback'), $options, +				null, 5); +		$j = 0; +		foreach ($batch as $e) { +			$offset = floor($j / 5) * 5; +			$this->assertEqual($offset, $e['offset']); +			$this->assertEqual($j + 1, $e['index']); +			$j++; +		} + +		$this->assertEqual(11, $j); + +		// no increment, 0 start +		ElggCoreHelpersTest::test_elgg_batch_callback(array(), true); +		$options = array( +			'offset' => 0, +			'limit' => 11 +		); +		$batch = new ElggBatch(array('ElggCoreHelpersTest', 'test_elgg_batch_callback'), $options, +				null, 5); +		$batch->setIncrementOffset(false); + +		$j = 0; +		foreach ($batch as $e) { +			$this->assertEqual(0, $e['offset']); +			// should always be the same 5 +			$this->assertEqual($e['index'], $j + 1 - (floor($j / 5) * 5)); +			$j++; +		} +		$this->assertEqual(11, $j); + +		// no increment, 3 start +		ElggCoreHelpersTest::test_elgg_batch_callback(array(), true); +		$options = array( +			'offset' => 3, +			'limit' => 11 +		); +		$batch = new ElggBatch(array('ElggCoreHelpersTest', 'test_elgg_batch_callback'), $options, +				null, 5); +		$batch->setIncrementOffset(false); + +		$j = 0; +		foreach ($batch as $e) { +			$this->assertEqual(3, $e['offset']); +			// same 5 results +			$this->assertEqual($e['index'], $j + 4 - (floor($j / 5) * 5)); +			$j++; +		} + +		$this->assertEqual(11, $j); +	} + +	static function test_elgg_batch_callback($options, $reset = false) { +		static $count = 1; + +		if ($reset) { +			$count = 1; +			return true; +		} + +		if ($count > 20) { +			return false; +		} + +		for ($j = 0; ($options['limit'] < 5) ? $j < $options['limit'] : $j < 5; $j++) { +			$return[] = array( +				'offset' => $options['offset'], +				'limit' => $options['limit'], +				'count' => $count++, +				'index' => 1 + $options['offset'] + $j +			); +		} + +		return $return; +	}  }
\ No newline at end of file  | 
