From 0c4076961952d9c6619ebeb26559857229363dab Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 7 May 2025 23:06:49 +0400 Subject: [PATCH] =?UTF-8?q?am=C3=A9liorer=20Cursor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/php/coll/Cursor.php | 233 ++++++++++++++++++++++++---------------- 1 file changed, 142 insertions(+), 91 deletions(-) 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) {