diff --git a/src/php/coll/Cursor.php b/src/php/coll/Cursor.php
index 4834b50..ec09831 100644
--- a/src/php/coll/Cursor.php
+++ b/src/php/coll/Cursor.php
@@ -11,6 +11,10 @@ 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
*/
@@ -18,14 +22,53 @@ 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"],
+ "map" => ["?array"],
+ "map_func" => ["?callable"],
+ "cols" => ["?array"],
+ "cols_func" => ["?callable"],
];
+ /**
+ * 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
+ */
+ 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 ($row === null) return false;
+ if (!array_key_exists($value, $row)) return false;
+ } elseif (is_bool($value)) {
+ if ($value) {
+ if ($row === null || !array_key_exists($key, $row)) return false;
+ } else {
+ if ($row !== null && array_key_exists($key, $row)) return false;
+ }
+ } else {
+ if ($row === null) return false;
+ if (!array_key_exists($key, $row)) return false;
+ if ($row[$key] !== $value) return false;
+ }
+ }
+ return true;
+ }
+
/**
* mapper le tableau source $row selon les règles suivantes illustrées dans
* l'exemple suivant:
@@ -33,8 +76,11 @@ class Cursor implements Iterator {
* $map = ["a", "b" => "x", "c" => function() { return "y"; }, "d" => null]
* alors retourner le tableau
* ["a" => $row["a"], "b" => $row["x"], "c" => "y", "d" => null]
+ *
+ * si une fonction est utilisée, sa signature est
+ * function(mixed $value, string|int $key, ?array $row)
*/
- protected static function map_row(array $row, ?array $map): array {
+ static function map_row(?array $row, ?array $map): array {
if ($map === null) return $row;
$index = 0;
$mapped = [];
@@ -55,43 +101,6 @@ class Cursor implements Iterator {
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;
@@ -103,7 +112,7 @@ class Cursor implements Iterator {
$rowsGenerator = $rowsFunc;
$rowsFunc = null;
} else {
- $rowsFunc = func::with($rowsFunc, [$rows]);
+ $rowsFunc = func::with($rowsFunc, [$rows, $this]);
}
} elseif ($rows instanceof Traversable) {
$rowsGenerator = $rows;
@@ -115,61 +124,95 @@ class Cursor implements Iterator {
$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;
+ $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) {
- $mapFunc = func::with($mapFunc);
- } elseif ($map !== null) {
- $mapFunc = func::with(function(array $row) use ($map) {
- return self::map_row($row, $map);
- });
- }
- $this->mapFunc = $mapFunc;
+ if ($mapFunc !== null) $this->setMapFunc($mapFunc);
+ elseif ($map !== null) $this->setMap($map);
- $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;
+ $this->cols = $params["cols"] ?? null;
+ $this->setColsFunc($params["cols_func"] ?? null);
}
/** un générateur de lignes */
private ?Traversable $rowsGenerator;
- /** une fonction de signature function(Cursor): ?iterable
*/
+ /** une fonction de signature function(mixed $rows, Cursor): ?iterable
*/
private ?func $rowsFunc;
- /** une fonction de signature function(Cursor): ?array
*/
- private ?func $colsFunc;
+ /** une fonction de signature function(?array $row, Cursor): bool
*/
+ private ?func $filterFunc = null;
- /** une fonction de signature function(Cursor): ?array
*/
- private ?func $mapFunc;
+ function setFilter(array $filter): self {
+ $this->filterFunc = func::with(function(?array $row) use ($filter) {
+ return self::filter_row($row, $filter);
+ });
+ return $this;
+ }
- /** une fonction de signature function(Cursor): bool
*/
- private ?func $filterFunc;
+ 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 function(?array $row, Cursor): ?array
*/
+ private ?func $mapFunc = null;
+
+ function setMap(array $map): self {
+ $this->mapFunc = func::with(function(?array $row) use ($map) {
+ return self::map_row($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 function(?array $row, Cursor): ?array
*/
+ 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) {
@@ -179,18 +222,22 @@ class Cursor implements Iterator {
return null;
}
- protected function cols(): ?array {
- return $this->row !== null? array_keys($this->row): null;
+ protected function convertToRow($raw): ?array {
+ return cl::withn($raw);
}
- protected function filter(): bool {
+ protected function filterFunc(?array $row): bool {
return false;
}
- protected function map(): ?array {
+ 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
@@ -212,26 +259,30 @@ class Cursor implements Iterator {
}
function valid(): bool {
- $cols = $this->colsFunc;
- $filter = $this->filterFunc;
- $map = $this->mapFunc;
+ $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 = 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 {
+ $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) {