forked from inetprocess/libsugarcrm
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Rémi Sauvat
committed
Mar 1, 2018
1 parent
1e9f15e
commit 493bb10
Showing
4 changed files
with
272 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |