Někdy se můžeme setkat s funkcí, která vrací jen omezený počet záznamů s tím, že k dalším se můžeme dostat předáním parametru, který určuje pořadí záznamu, od kterého nás data zajímají. Nejčastěji to budou data z externích zdrojů, může jít ale i o data z databáze, pokud je dat hodně a my jimi nechceme zabrat celou paměť a zároveň nechceme použít funkci mysql_unbuffered_query. Pro ilustraci definuji funkci vracející data jako jednoduchou:
<?php function f($params, $offset = 0) { return ($offset ? array(1000) : range(0, 999)); } ?>
Při nulovém parametru $offset
vrátí funkce 1000 záznamů, jinak vrátí jeden.
Jak lze postupovat, pokud chceme projít všechny záznamy?
<?php for ($offset=0; !$offset || count($f) == 1000; $offset += 1000) { $f = f($params, $offset); foreach ($f as $row) { // kód } } ?>
Tento kód je nepraktický, pokud funkci potřebujeme volat na více místech s různým vnitřním kódem. Potom musíme všude opakovat poměrně netriviální iteraci.
Uvedený problém se dá vyřešit tak, že cyklus uzavřeme do další funkce, které jako parametr předáme funkci, která má zpracovat kód:
<?php function f_callback($params, $callback) { for ($offset=0; !$offset || count($f) == 1000; $offset += 1000) { $f = f($params, $offset); foreach ($f as $row) { $callback($row); } } } f_callback($params, 'zpracovani'); ?>
Tento přístup je nepraktický v tom, že sebejednodušší kód musíme uzavírat do funkce, která navíc pracuje s vlastní sadou proměnných – to může být výhoda, ale obvykle je to spíš nevýhoda.
<?php function f_merge($params) { $return = array(); for ($offset=0; !$offset || count($f) == 1000; $offset += 1000) { $f = f($params, $offset); $return = array_merge($return, $f); } return $return; } foreach (f_merge($params) as $row) { // kód } ?>
Tím, že všechny výsledky volání funkce spojíme do jednoho pole, získáme jednoduchou iteraci, ale zabereme tím hodně paměti. Pokud je výsledků opravdu velké množství, může to být významný problém.
<?php class F { private $f, $params, $offset = 0; function __construct($params) { $this->params = $params; $this->f = f($params); } function each() { $return = each($this->f); if (!$return && count($this->f) == 1000) { $this->offset += 1000; $this->f = f($this->params, $this->offset); $return = each($this->f); } return $return; } } $f = new F($params); while (list(, $row) = $f->each()) { // kód } ?>
Vytvoříme si vlastní iterační funkci, která v případě, že dojde na konec aktuálního pole, sama zvýší ofset a zavolá funkci pro vrácení dat. Nevýhodou je nemožnost používání konstrukce foreach.
<?php class F implements Iterator { private $f, $params, $offset = 0; function __construct($params) { $this->params = $params; } function current() { return current($this->f); } function key() { return key($this->f); } function next() { return next($this->f); } function rewind() { $this->f = f($this->params); } function valid() { if (!is_null(key($this->f))) { return true; } elseif (count($this->f) == 1000) { $this->offset += 1000; $this->f = f($this->params, $this->offset); return !is_null(key($this->f)); } } } foreach (new F($params) as $row) { // kód } ?>
Rozhraní Iterator z rozšíření SPL dovoluje nadefinovat vlastní způsob procházení objektů. Abychom toho dosáhli, musíme ale nadefinovat celkem pět funkcí.
Osobně používám spojení dílčích výsledků do jednoho velkého pole, trápí mě ale paměťové nároky. Proto jsem hledal jiné řešení. Žádné, které bych považoval za dostatečně elegantní, jsem ale bohužel nenašel.
Přijďte si o tomto tématu popovídat na školení Výkonnost webových aplikací.
Diskuse je zrušena z důvodu spamu.