forked from extern/egroupware
189 lines
4.6 KiB
PHP
189 lines
4.6 KiB
PHP
<?php
|
|
/**
|
|
* EGroupware generalized SQL Storage Object: Iterator for get_rows
|
|
*
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
* @package api
|
|
* @subpackage storage
|
|
* @link http://www.egroupware.org
|
|
* @author Ralf Becker <rb@egroupware.org>
|
|
* @copyright 2020 by Ralf Becker <rb@egroupware.org>
|
|
*/
|
|
|
|
namespace EGroupware\Api\Storage;
|
|
use EGroupware\Api;
|
|
|
|
/**
|
|
* Iterator using a get_rows method and querying it in chunks instead of all rows as once
|
|
*
|
|
* You should use a consisten sorting eg. by id, in case rows change while the iterator is instanciated!
|
|
*
|
|
* Instead of ($query['num_rows']=-1, $query['start']=0):
|
|
*
|
|
* $storage->get_rows($query, $rows, $readonlys);
|
|
*
|
|
* Use:
|
|
*
|
|
* $rows = new RowsIterator($storage, $query);
|
|
*/
|
|
class RowsIterator implements \Iterator
|
|
{
|
|
/**
|
|
* Reference of Storage\Base class or other object with a get_rows method
|
|
*
|
|
* @var Base
|
|
*/
|
|
protected $storage;
|
|
|
|
/**
|
|
* query parameter for get_rows
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $query;
|
|
|
|
/**
|
|
* name of (unique) key in row or null to use $this->start + key returned by get_rows
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $key;
|
|
|
|
/**
|
|
* current chunk
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $rows;
|
|
|
|
/**
|
|
* Start value for callback
|
|
*
|
|
* @var int
|
|
*/
|
|
protected $start=0;
|
|
|
|
/**
|
|
* Number of rows queried from get_rows in one call
|
|
*/
|
|
const CHUNK_SIZE = 500;
|
|
|
|
/**
|
|
* Log calls via error_log()
|
|
*
|
|
* @var boolean
|
|
*/
|
|
public $debug = false;
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param Base $storage only requirement is class to have a get_rows method
|
|
* @param array $query query parameter for get_rows
|
|
* @param string $key =null name of (unique) key in row, default use $this->start + key from get_rows
|
|
*/
|
|
public function __construct($storage, array $query, $key=null)
|
|
{
|
|
if (!is_object($storage) || !method_exists($storage, 'get_rows'))
|
|
{
|
|
throw new Api\Exception\WrongParameter("\$storage parameter needs to be object with a get_rows method!");
|
|
}
|
|
$this->storage = $storage;
|
|
$this->query = $query;
|
|
$this->key = $key;
|
|
}
|
|
|
|
/**
|
|
* Return the current element
|
|
*
|
|
* @return array
|
|
*/
|
|
public function current()
|
|
{
|
|
if ($this->debug) error_log(__METHOD__."() returning ".array2string(current($this->rows)));
|
|
return current($this->rows);
|
|
}
|
|
|
|
/**
|
|
* Return the key of the current element
|
|
*
|
|
* @return int|string
|
|
*/
|
|
public function key()
|
|
{
|
|
$current = current($this->rows);
|
|
|
|
$key = !empty($this->key) ? $current[$this->key] : $this->start + key($this->rows);
|
|
if ($this->debug) error_log(__METHOD__."() returning ".array2string($key));
|
|
return $key;
|
|
}
|
|
|
|
/**
|
|
* Move forward to next element (called after each foreach loop)
|
|
*/
|
|
public function next()
|
|
{
|
|
if (next($this->rows) !== false)
|
|
{
|
|
if ($this->debug) error_log(__METHOD__."() returning TRUE");
|
|
return true;
|
|
}
|
|
// check if previous query gave less then CHUNK_SIZE entries --> we're done
|
|
if ($this->start && count($this->rows) < self::CHUNK_SIZE)
|
|
{
|
|
if ($this->debug) error_log(__METHOD__."() returning FALSE (no more entries)");
|
|
return false;
|
|
}
|
|
// try query further rows via get_rows method and store result in $this->rows
|
|
$readonlys = null;
|
|
$this->query['start'] = $this->start;
|
|
$this->query['num_rows'] = self::CHUNK_SIZE;
|
|
$this->storage->get_rows($this->query, $this->rows, $readonlys);
|
|
|
|
// remove non-rows returned (sel_options and the like, or leading false old eTemplate required)
|
|
foreach($this->rows as $key => $row)
|
|
{
|
|
if (!is_int($key) || !is_array($row))
|
|
{
|
|
unset($this->rows[$key]);
|
|
}
|
|
}
|
|
|
|
if (!is_array($this->rows) || !($entries = count($this->rows)))
|
|
{
|
|
if ($this->debug) error_log(__METHOD__."() returning FALSE (no more entries)");
|
|
return false; // no further entries
|
|
}
|
|
$this->start += self::CHUNK_SIZE;
|
|
reset($this->rows);
|
|
|
|
if ($this->debug) error_log(__METHOD__."() this->start=$this->start, entries=$entries, count(this->files)=".count($this->rows)." returning ".array2string(current($this->rows) !== false));
|
|
|
|
return current($this->rows) !== false;
|
|
}
|
|
|
|
/**
|
|
* Rewind the Iterator to the first element (called at beginning of foreach loop)
|
|
*/
|
|
public function rewind()
|
|
{
|
|
if ($this->debug) error_log(__METHOD__."()");
|
|
|
|
$this->start = 0;
|
|
$this->rows = [];
|
|
if (!$this->rows) $this->next(); // otherwise valid will return false and nothing get returned
|
|
reset($this->rows);
|
|
}
|
|
|
|
/**
|
|
* Checks if current position is valid
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function valid ()
|
|
{
|
|
if ($this->debug) error_log(__METHOD__."() returning ".array2string(current($this->rows) !== false));
|
|
return current($this->rows) !== false;
|
|
}
|
|
}
|