nur-ture/src/php/coll/Cursor.php

255 lines
7.0 KiB
PHP

<?php
namespace nulib\php\coll;
use Iterator;
use IteratorAggregate;
use nulib\cl;
use nulib\php\func;
use nulib\php\iter;
use Traversable;
/**
* Class Cursor: parcours des lignes itérable
*
* XXX si on spécifie $cols ou $colsFunc, il y a une possibilité que les clés de
* $row ne soient pas dans le bon ordre, ou que les clés de $cols ne soient pas
* présentes dans $row. ajouter les paramètres ensure_keys et order_keys
*
* @property-read array|null $value alias pour $row
* @property-read iterable|null $rows la source des lignes
*/
class Cursor implements Iterator {
const PARAMS_SCHEMA = [
"rows" => ["?iterable"],
"rows_func" => ["?callable"],
"filter" => ["?array"],
"filter_func" => ["?callable"],
"map" => ["?array"],
"map_func" => ["?callable"],
"cols" => ["?array"],
"cols_func" => ["?callable"],
];
function __construct(?iterable $rows=null, ?array $params=null) {
if ($rows !== null) $params["rows"] = $rows;
$rows = $params["rows"] ?? null;
$rowsGenerator = null;
$rowsFunc = $params["rows_func"] ?? null;
if ($rowsFunc !== null) {
if ($rowsFunc instanceof Traversable) {
$rowsGenerator = $rowsFunc;
$rowsFunc = null;
} else {
$rowsFunc = func::with($rowsFunc, [$rows, $this]);
}
} elseif ($rows instanceof Traversable) {
$rowsGenerator = $rows;
} else {
$rowsFunc = func::with(function() use ($rows) {
return $rows;
});
}
$this->rowsGenerator = $rowsGenerator;
$this->rowsFunc = $rowsFunc;
$filter = $params["filter"] ?? null;
$filterFunc = $params["filter_func"] ?? null;
if ($filterFunc !== null) $this->setFilterFunc($filterFunc);
elseif ($filter !== null) $this->setFilter($filter);
$map = $params["map"] ?? null;
$mapFunc = $params["map_func"] ?? null;
if ($mapFunc !== null) $this->setMapFunc($mapFunc);
elseif ($map !== null) $this->setMap($map);
$this->cols = $params["cols"] ?? null;
$this->setColsFunc($params["cols_func"] ?? null);
}
/** un générateur de lignes */
private ?Traversable $rowsGenerator;
/** une fonction de signature <code>function(mixed $rows, Cursor): ?iterable</code> */
private ?func $rowsFunc;
/** une fonction de signature <code>function(?array $row, Cursor): bool</code> */
private ?func $filterFunc = null;
function setFilter(array $filter): self {
$this->filterFunc = func::with(function(?array $row) use ($filter) {
return cl::filter($row, $filter);
});
return $this;
}
function setFilterFunc(?callable $func): self {
if ($func === null) $this->filterFunc = null;
else $this->filterFunc = func::with($func)->bind($this);
return $this;
}
/** une fonction de signature <code>function(?array $row, Cursor): ?array</code> */
private ?func $mapFunc = null;
function setMap(array $map): self {
$this->mapFunc = func::with(function(?array $row) use ($map) {
return cl::map($row, $map);
});
return $this;
}
function setMapFunc(?callable $func): self {
if ($func === null) $this->mapFunc = null;
else $this->mapFunc = func::with($func)->bind($this);
return $this;
}
/** une fonction de signature <code>function(?array $row, Cursor): ?array</code> */
private ?func $colsFunc = null;
function setColsFunc(?callable $func): self {
$this->cols = null;
if ($func === null) $this->colsFunc = null;
else $this->colsFunc = func::with($func)->bind($this);
return $this;
}
/** @var iterable|null source des éléments */
protected ?iterable $rows;
/** @var array|null listes des colonnes de chaque enregistrement */
public ?array $cols;
/**
* @var int index de l'enregistrement (en ne comptant pas les éléments filtrés)
*/
public int $index;
/**
* @var int index original de l'enregistrement (en tenant compte des éléments
* filtrés)
*/
public int $origIndex;
/** @var string|int clé de l'enregistrement */
public $key;
/** @var mixed élément original récupéré depuis la source */
public $raw;
/**
* @var array|null enregistrement après conversion en tableau et application
* du mapping
*/
public ?array $row;
function __get($name) {
if ($name === "value") return $this->row;
elseif ($name == "rows") return $this->rows;
trigger_error("Undefined property $name");
return null;
}
protected function convertToRow($raw): ?array {
return cl::withn($raw);
}
protected function filterFunc(?array $row): bool {
return false;
}
protected function mapFunc(?array $row): ?array {
return $this->row;
}
protected function colsFunc(?array $row): ?array {
return $this->row !== null? array_keys($this->row): null;
}
#############################################################################
# Iterator
function rewind() {
$this->cols = null;
$this->index = 0;
$this->origIndex = 0;
$this->key = null;
$this->raw = null;
$this->row = null;
if ($this->rowsGenerator !== null) {
$rows = $this->rowsGenerator;
if ($rows instanceof IteratorAggregate) $rows = $rows->getIterator();
$rows->rewind();
$this->rows = $rows;
} else {
$this->rows = $this->rowsFunc->invoke();
}
}
function valid(): bool {
$colsFunc = $this->colsFunc;
$filterFunc = $this->filterFunc;
$mapFunc = $this->mapFunc;
while ($valid = iter::valid($this->rows)) {
$this->raw = iter::current($this->rows, $this->key);
# conversion en enregistrement
$this->key ??= $this->origIndex;
$this->row = $this->convertToRow($this->raw);
# filtrage
if ($filterFunc === null) $filtered = $this->filterFunc($this->row);
else $filtered = $filterFunc->invoke([$this->row, $this]);
if ($filtered) {
iter::next($this->rows);
$this->origIndex++;
} else {
# l'enregistrement n'as pas été filtré: faire le mapping
if ($mapFunc === null) $this->row = $this->mapFunc($this->row);
else $this->row = $mapFunc->invoke([$this->row, $this]);
# calculer la liste des colonnes le cas échéant
if ($this->cols === null) {
if ($colsFunc === null) $this->cols = $this->colsFunc($this->row);
else $this->cols = $colsFunc->invoke([$this->row, $this]);
}
break;
}
}
if (!$valid) {
iter::close($this->rows);
$this->rows = null;
$this->cols = null;
$this->index = -1;
$this->origIndex = -1;
$this->key = null;
$this->raw = null;
$this->row = null;
}
return $valid;
}
function current(): ?array {
return $this->row;
}
function key() {
return $this->key;
}
function next() {
iter::next($this->rows);
$this->index++;
$this->origIndex++;
}
#############################################################################
function each(callable $func): void {
$func = func::with($func);
$this->rewind();
while ($this->valid()) {
$func->invoke([$this]);
$this->next();
}
}
}