275 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			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();
 | |
|     }
 | |
|   }
 | |
| }
 |