améliorer Cursor
This commit is contained in:
		
							parent
							
								
									a120143794
								
							
						
					
					
						commit
						0c40769619
					
				| @ -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 | ||||
|    * <code>function(mixed $value, string|int $key, ?array $row)</code> | ||||
|    */ | ||||
|   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 <code>function(Cursor): ?iterable</code> */ | ||||
|   /** une fonction de signature <code>function(mixed $rows, Cursor): ?iterable</code> */ | ||||
|   private ?func $rowsFunc; | ||||
| 
 | ||||
|   /** une fonction de signature <code>function(Cursor): ?array</code> */ | ||||
|   private ?func $colsFunc; | ||||
|   /** une fonction de signature <code>function(?array $row, Cursor): bool</code> */ | ||||
|   private ?func $filterFunc = null; | ||||
| 
 | ||||
|   /** une fonction de signature <code>function(Cursor): ?array</code> */ | ||||
|   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 <code>function(Cursor): bool</code> */ | ||||
|   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 <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 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 <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) { | ||||
| @ -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) { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user