nur-sery/nur_src/b/coll/AbstractIterator.php

190 lines
4.7 KiB
PHP

<?php
namespace nur\b\coll;
use AppendIterator;
use ArrayIterator;
use EmptyIterator;
use Exception;
use Iterator;
use NoRewindIterator;
use nur\b\io\EOFException;
/**
* Class AbstractIterator: implémentation de base d'un itérateur
*
* les classes dérivées *doivent* implémenter les méthodes key() et current()
* qui peuvent simplement être implémentée comme des méthodes déléguées de
* respectivement {@link _key()} et {@link _current()}.
*
* Ce mode opératoire permet de créer ces méthodes avec une signature appropriée,
* permettant à un IDE de découvrir le type des données qui sont accédées
*/
abstract class AbstractIterator implements Iterator {
const AUTO_REWIND = false;
private $setup = false;
private $valid = false;
private $toredown = true;
private $index = 0;
protected $key;
protected $item = null;
/**
* initialiser les ressources nécessaires à l'itération.
* les exceptions lancées par cette méthode sont ignorées.
*/
protected function _setup() {}
/**
* retourner le prochain élément. retourner false ou lancer l'exception
* EOFException pour indiquer que plus aucun élément n'est disponible
*
* le cas échéant, initialiser $key
*
* @throws EOFException
*/
abstract protected function _next(&$key);
/**
* libérer les ressources allouées.
* les exceptions lancées par cette méthode sont ignorées.
*/
protected function _teardown() {}
/**
* lancer un traitement avant de commencer l'itération.
*
* cette méthode est appelée après _setup() et l'objet est garanti d'être dans
* un état valide. elle est prévue pour être surchargée par l'utilisateur
*/
protected function beforeIter() {}
/**
* modifier un élément avant de le retourner.
*
* cette méthode est prévue pour être surchargée par l'utilisateur.
*/
protected function cook(&$item) {}
function _key() {
return $this->key;
}
function _current() {
return $this->item;
}
function next() {
if ($this->toredown) return;
try {
$item = $this->_next($key);
} catch (EOFException $e) {
$item = false;
}
$this->valid = false;
if ($item !== false) {
$this->cook($item);
$this->item = $item;
if ($key !== null) {
$this->key = $key;
} else {
$this->index++;
$this->key = $this->index;
}
$this->valid = true;
} else {
try {
$this->_teardown();
} catch (Exception $e) {
}
$this->toredown = true;
}
}
function rewind() {
if ($this->setup) {
if (!$this->toredown) {
try {
$this->_teardown();
} catch (Exception $e) {
}
}
$this->setup = false;
$this->valid = false;
$this->toredown = true;
$this->index = 0;
$this->key = null;
$this->item = null;
}
}
function valid() {
if (!$this->setup) {
try {
$this->_setup();
} catch (Exception $e) {
}
$this->setup = true;
$this->toredown = false;
$this->beforeIter();
$this->next();
}
return $this->valid;
}
/**
* retourner la première valeur de la liste ou $default si aucun élément n'est
* trouvé.
*
* si static::AUTO_REWIND est true, appeler rewind() à la fin pour s'assurer
* que l'itérateur est fermé correctement.
*/
function get($default=null) {
# obtenir le premier élément de la liste
$this->rewind();
$value = $this->valid()? $this->current(): $default;
if (static::AUTO_REWIND) $this->rewind();
return $value;
}
/**
* retourner la première valeur de la liste, ou $default si aucun élément
* n'est trouvé.
*
* si $rewind est true, appeler rewind() à la fin pour s'assurer que
* l'itérateur est fermé correctement.
*
* retourner un tableau [$value, $have_next, $it_nexts]
* - $have_next vaut true s'il y a encore des données qui suivent
* - si $rewind==false, $it_nexts est un itérateur qui permet d'accéder aux
* données suivantes
*/
function one($default=null, ?bool $rewind=null): array {
if ($rewind === null) $rewind = static::AUTO_REWIND;
$value = $default;
$have_next = false;
$it_nexts = new EmptyIterator();
$this->rewind();
if ($this->valid()) {
$value = $this->current();
$this->next();
$have_next = $this->valid();
if ($have_next) {
$next = $this->current();
$this->next();
if (!$rewind) {
$it_nexts = new AppendIterator();
$it_nexts->append(new ArrayIterator([$next]));
$it_nexts->append(new NoRewindIterator($this));
}
}
}
if ($rewind) $this->rewind();
return [$value, $have_next, $it_nexts];
}
}