Skip to content

Commit

Permalink
Add new class SugarQueryIterator
Browse files Browse the repository at this point in the history
  • Loading branch information
Rémi Sauvat committed Mar 1, 2018
1 parent 1e9f15e commit 493bb10
Show file tree
Hide file tree
Showing 4 changed files with 272 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
Changelog
=========

1.2.11
----
* Add new class `SugarQueryIterator`

1.2.10
----
* Make PDO parameters available publicly
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,23 @@ $inetSugarUtils = new Utils(EntryPoint::getInstance());
$convertedArray = $inetSugarUtils->arrayToMultiselect(array('test' => 'inet'));
echo $convertedArray;
```

## Inet\SugarCRM\SugarQueryIterator
Iterator class to iterate in a memory safe way SugarQuery results.

Usage Example:
Loop records 100 to 300
```php
<?php
$query = new \SugarQuery();
// setup $query

$iter = new SugarQueryIterator($query);
$iter->setStartOffset(100);
foreach ($iter as $id => $bean) {
// Do something with $bean
if ($iter->getIterationCounter() >= 200) {
break;
}
}
```
163 changes: 163 additions & 0 deletions src/SugarQueryIterator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<?php
/**
* SugarCRM Tools
*
* PHP Version 5.3 -> 5.6
* SugarCRM Versions 6.5 - 7.6
*
* @author Emmanuel Dyan
* @copyright 2005-2018 iNet Process
*
* @package inetprocess/sugarcrm
*
* @license Apache License 2.0
*
* @link http://www.inetprocess.com
*/

namespace Inet\SugarCRM;

/**
* Iterator class to iterate in a memory safe way SugarQuery results.
* Usage:
* $query = new \SugarQuery();
* // setup $query
*
* foreach (new SugarQueryIterator($query) as $id => $bean) {
* // Do something with $bean
* }
*/
class SugarQueryIterator implements \Iterator
{
protected $retrieve_params;
protected $query;
protected $use_fixed_offset;
protected $start_offset = 0;
protected $clean_memory_on_fetch = true;

protected $query_offset = 0;

protected $results_cache = array();
protected $cache_current_index = 0;

protected $iteration_counter = 0;

/**
* $query A sugarCRM query to fetch record ids.
* $retrieve_params Parameters passed to \BeanFactory::retrieveBean on each iteration
*/
public function __construct(\SugarQuery $query, $retrieve_params = array())
{
$this->query = $query;
$this->retrieve_params = $retrieve_params;
// Set query parameters
$this->query->select(array('id'));
$this->setPaginationSize(100);
}

/**
* Start the iteration at $offset
*/
public function setStartOffset($offset)
{
$this->start_offset = $offset;
}

public function getStartOffset()
{
return $this->start_offset;
}

/**
* Fetch only $size records at a time from the database
*/
public function setPaginationSize($size)
{
$this->query->limit($size);
}

/**
* Set to true if the results are modified during the iteration
* in a way that they are not return on the next query call.
* This way the iterator keep quering the same first records hoping
* eventually the query returns no results.
* Be carefull when setting to true as infinite loop are really easy to create.
*/
public function useFixedOffset($fixed_offset)
{
$this->use_fixed_offset = $fixed_offset;
}

/**
* Should the iterator try to free some memory
* before fetching new results.
*/
public function setCleanMemoryOnFetch($value)
{
$this->clean_memory_on_fetch = $value;
}

/**
* Return the number of iteration. Starts at 1.
*/
public function getIterationCounter()
{
return $this->iteration_counter;
}

/**
* Iterator interface.
*/
public function current()
{
$module = $this->query->getFromBean()->module_name;
return \BeanFactory::retrieveBean($module, $this->key(), $this->retrieve_params);
}

public function key()
{
return $this->results_cache[$this->cache_current_index]['id'];
}

public function next()
{
$this->cache_current_index++;
$this->query_offset++;
$this->iteration_counter++;
if ($this->cache_current_index > (count($this->results_cache) - 1)) {
$this->fetchNextRecords();
}
}

public function rewind()
{
$this->cache_current_index = 0;
$this->results_cache = array();
$this->query_offset = $this->getStartOffset();
$this->iteration_counter = 1;
$this->fetchNextRecords();
}

public function valid()
{
return !empty($this->results_cache);
}

/**
* Fetch the next page of ids from the database
*/
protected function fetchNextRecords()
{
if (!$this->use_fixed_offset) {
$this->query->offset($this->query_offset);
}
if ($this->clean_memory_on_fetch) {
// Attempt to clean php memory
$this->results_cache = null;
BeanFactoryCache::clearCache();
gc_collect_cycles();
}
$this->results_cache = $this->query->execute();
$this->cache_current_index = 0;
}
}
85 changes: 85 additions & 0 deletions tests/SugarQueryIteratorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php

namespace Inet\SugarCRM\Tests;

use Inet\SugarCRM\SugarQueryIterator;

class SugarQueryIteratorTest extends SugarTestCase
{
protected $query;

public function setUp()
{
// Load sugar clases
$this->getEntryPointInstance()->setCurrentUser('1');

$this->query = new \SugarQuery();
$this->query->from(\BeanFactory::newBean('Accounts'));
}

public function testRewindValid()
{
$iter = new SugarQueryIterator($this->query);
$iter->rewind();
$this->assertTrue($iter->valid());
}

public function testFetchAllAccounts()
{
$this->query->limit(10);
$results = $this->query->execute();

$iter = new SugarQueryIterator($this->query);
$iter->setPaginationSize(5);
$i = 0;
foreach ($iter as $id => $bean) {
$i++;
$this->assertEquals($i, $iter->getIterationCounter());
$this->assertInternalType('string', $id);
$this->assertInstanceOf('Account', $bean);
$this->assertEquals($id, $bean->id);
if ($iter->getIterationCounter() >= 10) {
break;
}
}
$this->assertGreaterThan(0, $i);
$this->assertEquals(count($results), $i);
}

public function testStartOffset()
{
$this->query->limit(10);
$this->query->select(array('id'));
$results = $this->query->execute();

$iter = new SugarQueryIterator($this->query);
$iter->setPaginationSize(5);
$iter->setStartOffset(5);

$iter_results = array();
foreach ($iter as $key => $bean) {
$iter_results[] = array('id' => $key);
if ($iter->getIterationCounter() >= 5) {
break;
}
}
$this->assertEquals(array_slice($results, 5), $iter_results);
}

public function testFixedOffset()
{
$iter = new SugarQueryIterator($this->query);
$iter->useFixedOffset(true);
$iter->setCleanMemoryOnFetch(false);
$iter->setPaginationSize(1);

$iter->rewind();
$this->assertTrue($iter->valid());
$bean_id = $iter->key();
$iter->next();
$this->assertTrue($iter->valid());
$bean2_id = $iter->key();

$this->assertEquals($bean_id, $bean2_id);
}
}

0 comments on commit 493bb10

Please sign in to comment.