Skip to content

Instantly share code, notes, and snippets.

@Isinlor
Created February 22, 2016 15:24
Show Gist options
  • Save Isinlor/5d901ff80cfc992ec30f to your computer and use it in GitHub Desktop.
Save Isinlor/5d901ff80cfc992ec30f to your computer and use it in GitHub Desktop.
Interface and calsses that allows dynamical nesting of loops
<?php
/**
* Interface DynamicLoops
*
* Allows to dynamically nest loops eg.:
*
* $loops = [
* function (DynamicLoops $nextLoop, array $values = []) {
* foreach (range(0, 1) as $value) {
* $values[] = $value;
* $nextLoop($values);
* }
* },
* function (DynamicLoops $nextLoop, array $values = []) {
* foreach (range(2, 3) as $value) {
* $values[] = $value;
* $nextLoop($values);
* }
* }
* ];
*
* (new BasicDynamicLoops($loops))(); // $values: [[0, 2], [0, 3], [1, 2], [1, 3]]
* (new BasicDynamicLoops(array_reverse($loops)))(); // $values: [[2, 0], [2, 1], [3, 0], [3, 1]]
*
*
*/
interface DynamicLoops
{
/**
* Invokes next loop
*
* @param array ...$args Arguments that will be passed to the loop callable (see example above)
*
* @return mixed
*/
public function __invoke(...$args);
}
class BasicDynamicLoops implements DynamicLoops
{
protected $loops = [];
protected $currentLoop = 0;
public function __construct(array $loops)
{
$this->loops = array_values($loops);
}
public function __invoke(...$args)
{
/* @var $loop callable */
$loop = $this->loops[$this->currentLoop];
$this->currentLoop++;
$loop($this, ...$args);
$this->currentLoop--;
}
}
class ContinueInLoop extends Exception
{
protected $loop;
public function __construct(string $loop)
{
$this->loop = $loop;
}
public function getLoop()
{
return $this->loop;
}
}
class DynamicLoopsWithContinue extends BasicDynamicLoops
{
protected $loopsKeys = [];
public function __construct(array $loops)
{
$this->loopsKeys = array_flip(array_keys($loops));
parent::__construct($loops);
}
public function __invoke(...$args)
{
try {
/* @var $loop callable */
$loop = $this->loops[$this->currentLoop];
$this->currentLoop++;
$continueInLoop = $loop($this, ...$args);
} catch (ContinueInLoop $skipper) {
// -1 because we haven't yet decremented counter, but we exited a loop already with exception
// -1 because we want to stop inside the requested loop
if ($this->currentLoop - 2 != $skipper->getLoop()) {
throw $skipper;
}
} finally {
$this->currentLoop--;
// check if continuing in some loop was requested
// throw an exception to break execution of loops in between the requested loop and the current loop
if (isset($continueInLoop)) {
throw new ContinueInLoop($this->loopsKeys[$continueInLoop]);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment