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

275 lines
7.5 KiB
PHP

<?php
namespace nur\sery\wip\php\coll;
use Iterator;
use IteratorAggregate;
use nulib\cl;
use nulib\php\func;
use nur\sery\wip\php\iter;
use Traversable;
/**
* Class Cursor: parcours des lignes itérable
*
* @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"],
"cols" => ["?array"],
"cols_func" => ["?callable"],
"map" => ["?array"],
"map_func" => ["?callable"],
"filter" => ["?array"],
"filter_func" => ["?callable"],
];
/**
* mapper le tableau source $row selon les règles suivantes illustrées dans
* l'exemple suivant:
* si
* $map = ["a", "b" => "x", "c" => function() { return "y"; }, "d" => null]
* alors retourner le tableau
* ["a" => $row["a"], "b" => $row["x"], "c" => "y", "d" => null]
*/
protected static function map_row(array $row, ?array $map): array {
if ($map === null) return $row;
$index = 0;
$mapped = [];
foreach ($map as $key => $value) {
if ($key === $index) {
$index++;
if ($value === null) $mapped[] = null;
else $mapped[$value] = cl::get($row, $value);
} elseif (is_callable($value)) {
$func = func::with($value);
$value = cl::get($row, $key);
$mapped[$key] = $func->invoke([$value, $key, $row]);
} else {
if ($value === null) $mapped[$key] = null;
else $mapped[$key] = cl::get($row, $value);
}
}
return $mapped;
}
/**
* tester si $row satisfait les conditions de $filter
* - $filter est un scalaire, le transformer en [$filter]
* - sinon $filter doit être un tableau de scalaires
*
* les règles des conditions sont les suivantes:
* - une valeur séquentielle $key est équivalente à la valeur associative
* $key => true
* - une valeur associative $key => bool indique que la clé correspondante ne
* doit pas (resp. doit) exister selon que bool vaut false (resp. true)
* - une valeur associative $key => $value indique que la clé correspondante
* doit exiter avec la valeur spécifiée
*/
protected static function filter_row(array $row, $filter): bool {
if ($filter === null) return false;
if (!is_array($filter)) $filter = [$filter];
if (!$filter) return false;
$index = 0;
foreach ($filter as $key => $value) {
if ($key === $index) {
$index++;
if (!array_key_exists($value, $row)) return false;
} elseif (is_bool($value)) {
if ($value) {
if (!array_key_exists($key, $row)) return false;
} else {
if (array_key_exists($key, $row)) return false;
}
} else {
if (!array_key_exists($key, $row)) return false;
if ($row[$key] !== $value) return false;
}
}
return true;
}
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]);
}
} elseif ($rows instanceof Traversable) {
$rowsGenerator = $rows;
} else {
$rowsFunc = func::with(function() use ($rows) {
return $rows;
});
}
$this->rowsGenerator = $rowsGenerator;
$this->rowsFunc = $rowsFunc;
$this->cols = $params["cols"] ?? null;
$colsFunc = $params["cols_func"] ?? null;
if ($colsFunc !== null) $colsFunc = func::with($colsFunc);
$this->colsFunc = $colsFunc;
$map = $params["map"] ?? null;
$mapFunc = $params["map_func"] ?? null;
if ($mapFunc !== null) {
$mapFunc = func::with($mapFunc);
} elseif ($map !== null) {
$mapFunc = func::with(function(array $row) use ($map) {
return self::map_row($row, $map);
});
}
$this->mapFunc = $mapFunc;
$filter = $params["filter"] ?? null;
$filterFunc = $params["filter_func"] ?? null;
if ($filterFunc !== null) {
$filterFunc = func::with($filterFunc);
} elseif ($filter !== null) {
$filterFunc = func::with(function(array $row) use ($filter) {
return self::filter_row($row, $filter);
});
}
$this->filterFunc = $filterFunc;
}
/** un générateur de lignes */
private ?Traversable $rowsGenerator;
/** une fonction de signature <code>function(Cursor): ?iterable</code> */
private ?func $rowsFunc;
/** une fonction de signature <code>function(Cursor): ?array</code> */
private ?func $colsFunc;
/** une fonction de signature <code>function(Cursor): ?array</code> */
private ?func $mapFunc;
/** une fonction de signature <code>function(Cursor): bool</code> */
private ?func $filterFunc;
protected ?iterable $rows;
public ?array $cols;
public int $index;
public int $origIndex;
public $key;
public $raw;
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 cols(): ?array {
return $this->row !== null? array_keys($this->row): null;
}
protected function filter(): bool {
return false;
}
protected function map(): ?array {
return $this->row;
}
#############################################################################
# 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 {
$cols = $this->colsFunc;
$filter = $this->filterFunc;
$map = $this->mapFunc;
while ($valid = iter::valid($this->rows)) {
$this->raw = iter::current($this->rows, $this->key);
$this->key ??= $this->origIndex;
$this->row = cl::withn($this->raw);
if ($filter === null) $filtered = $this->filter();
else $filtered = $filter->invoke([$this]);
if (!$filtered) {
if ($map === null) $this->row = $this->map();
else $this->row = $map->invoke([$this]);
if ($this->cols === null) {
if ($cols === null) $this->cols = $this->cols();
else $this->cols = $cols->invoke([$this]);
}
break;
} else {
iter::next($this->rows);
$this->origIndex++;
}
}
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();
}
}
}