Last active
November 10, 2023 14:52
-
-
Save andrerom/a8573d45547875f6562b881b43bd1864 to your computer and use it in GitHub Desktop.
Lazy Collection using Generator in PHP (Example, script in bottom of file to test it)
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
<?php | |
/** | |
* Class GeneratorCollection, allows for lazy loaded arrays using Generators withouth it's limitations. | |
* | |
* This collection class takes Generator as argument, and allows user to threat it like any kind of array. It will take | |
* care about storing the values returned from generator so user can iterate over it several times. | |
* | |
* NOTE: In current form only works with generators that uses integers as keys (lists). | |
*/ | |
class GeneratorCollection implements Iterator, ArrayAccess, Countable | |
{ | |
private $position = 0; | |
private $array = []; | |
private $generator; | |
public function __construct(Generator $generator) | |
{ | |
$this->generator = $generator; | |
} | |
public function rewind() | |
{ | |
if (isset($this->generator)) { | |
// If position has already been increased a non complete loop has been made, so we need to load remaining | |
if ($this->position) { | |
$this->loadAll(); | |
} else { | |
$this->generator->rewind(); | |
} | |
} | |
$this->position = 0; | |
} | |
public function current() | |
{ | |
if (isset($this->generator)) { | |
// As the generator is iterated, set the values on array here so we can also iterate on it later | |
return $this->array[$this->generator->key()] = $this->generator->current(); | |
} | |
return $this->array[$this->position]; | |
} | |
public function key() | |
{ | |
if (isset($this->generator)) { | |
return $this->generator->key(); | |
} | |
return $this->position; | |
} | |
public function next() | |
{ | |
if (isset($this->generator)) { | |
$this->generator->next(); | |
} | |
// Also incrememnt our own position even if generator is still set for use in rewind() | |
++$this->position; | |
} | |
public function valid() | |
{ | |
if (isset($this->generator)) { | |
if ($this->generator->valid()) { | |
return true; | |
} | |
// If not valid we are at the end of the array, for future iteration we'll unset this and use array | |
unset($this->generator); | |
} | |
return isset($this->array[$this->position]); | |
} | |
public function offsetExists($offset) | |
{ | |
if (isset($this->generator)) { | |
$this->loadAll(); | |
} | |
return isset($this->array[$offset]); | |
} | |
public function offsetGet($offset) | |
{ | |
if (isset($this->generator)) { | |
$this->loadAll(); | |
} | |
return $this->array[$offset]; | |
} | |
public function offsetSet($offset, $value) | |
{ | |
// Read only collection, so not handled here (for making it writable it would have to loadAll first if needed) | |
} | |
public function offsetUnset($offset) | |
{ | |
// Read only collection, so not handled here (for making it writable it would have to loadAll first if needed) | |
} | |
public function count() | |
{ | |
if (isset($this->generator)) { | |
$this->loadAll(); | |
} | |
return count($this->array); | |
} | |
private function loadAll() | |
{ | |
// As we might have loaded some elements already we use array union to retain everything | |
$this->array = iterator_to_array($this->generator) + $this->array; | |
unset($this->generator); | |
} | |
} | |
function gen() | |
{ | |
echo "(Generator started)".PHP_EOL; | |
yield from [1, 2, 3]; | |
return 4; | |
} | |
$iterator = new GeneratorCollection(gen()); | |
// uncomment this to see how generators behaves normally | |
//$iterator = gen(); | |
echo "Generator should start after this line even if function is called above (aka should be lazy loaded)!\n"; | |
foreach($iterator as $value) { | |
echo $value; | |
if ($value === 2) break; | |
} | |
echo "\n"; | |
echo $iterator[2]; | |
echo "\n"; | |
foreach($iterator as $value) { | |
echo $value; | |
} | |
echo "\n"; | |
foreach($iterator as $value) { | |
echo $value; | |
} | |
echo "\n"; | |
echo "Count:" .count($iterator). "\n\n"; | |
/* Expected output is: | |
Generator should start after this line even if function is called above (aka should be lazy loaded)! | |
Generator started! | |
12 | |
3 | |
123 | |
123 | |
Count:3 | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment