363 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			363 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| namespace nur\mapper\base;
 | |
| 
 | |
| use IteratorAggregate;
 | |
| use nur\b\ICloseable;
 | |
| use nur\b\params\IParametrable;
 | |
| use nur\b\params\Parametrable;
 | |
| use nur\b\params\Tparametrable;
 | |
| use nur\func;
 | |
| use nur\mapper\base\oobd\IOobdManager;
 | |
| use nur\mapper\base\oobd\TOobdManager;
 | |
| 
 | |
| /**
 | |
|  * Class Mapper: un mappeur / filtre de données
 | |
|  *
 | |
|  * La méthode {@link mapper()} permet d'associer un élément à zéro, un ou
 | |
|  * plusieurs autres éléments. il faut:
 | |
|  * - soit retourner une valeur correspondante. si la valeur retournée est null,
 | |
|  * alors cela revient à ignorer la valeur en entrée
 | |
|  * - soit appeler l'une des méthodes {@link mapTo()}. il est possible de mapper
 | |
|  * vers une valeur, un tableau de valeurs, ou un iterateur/générateur
 | |
|  *
 | |
|  * Pour pouvoir être utilisé avec {@link Consumer}, les constructeurs des
 | |
|  * classes dérivées doivent par convention mentionner $source comme dernier
 | |
|  * argument.
 | |
|  *
 | |
|  * --autogen-properties-and-methods--
 | |
|  * @method iterable getSource()
 | |
|  * @method iterable setSource(iterable $value)
 | |
|  */
 | |
| abstract class Mapper extends Parametrable implements IteratorAggregate, ICloseable, IParametrable, IOobdManager {
 | |
|   use Tparametrable, TOobdManager;
 | |
| 
 | |
|   /**
 | |
|    * @return bool indiquer s'il faut appeler {@link mapper()} une première fois
 | |
|    * au début de l'itération avec {null => null}. cela permet d'implémenter des
 | |
|    * méthodes de mapping plus complexes.
 | |
|    */
 | |
|   protected function MAP_SOF(): bool {
 | |
|     return static::MAP_SOF;
 | |
|   } const MAP_SOF = false;
 | |
| 
 | |
|   /**
 | |
|    * @return bool indiquer s'il faut appeler {@link mapper()} une dernière fois
 | |
|    * à la fin de l'itération avec {null => null}. cela permet d'implémenter des
 | |
|    * méthodes de mapping plus complexes.
 | |
|    */
 | |
|   protected function MAP_EOF(): bool {
 | |
|     return static::MAP_EOF;
 | |
|   } const MAP_EOF = false;
 | |
| 
 | |
|   /**
 | |
|    * @param iterable|null $source dans les classes dérivées, cet argument doit
 | |
|    * par convention être le dernier de la liste
 | |
|    */
 | |
|   function __construct(?iterable $source=null) {
 | |
|     $params = null;
 | |
|     if ($source !== null) $params["source"] = $source;
 | |
|     parent::__construct($params);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * @var array liste de paramètres pour la configuration générique de cet objet
 | |
|    */
 | |
|   const PARAMETRABLE_PARAMS_SCHEMA = [
 | |
|     "source" => ["iterable", null, "source de données"],
 | |
|   ];
 | |
| 
 | |
|   /** @var iterable */
 | |
|   protected $ppSource;
 | |
| 
 | |
|   function hasOvalue(string $name): bool {
 | |
|     if ($this->_hasOobdValue($name)) {
 | |
|       return true;
 | |
|     } elseif ($this->sharedOobdManager !== null
 | |
|       && $this->sharedOobdManager->hasOvalue($name)) {
 | |
|       return true;
 | |
|     } elseif ($this->ppSource instanceof IOobdManager) {
 | |
|       return $this->ppSource->hasOvalue($name);
 | |
|     }
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   function getOvalue(string $name, $default=null) {
 | |
|     if ($this->_hasOobdValue($name)) {
 | |
|       return $this->_getOobdValue($name, $default);
 | |
|     } elseif ($this->sharedOobdManager !== null
 | |
|       && $this->sharedOobdManager->hasOvalue($name)) {
 | |
|       return $this->sharedOobdManager->getOvalue($name, $default);
 | |
|     } elseif ($this->ppSource instanceof IOobdManager) {
 | |
|       return $this->ppSource->getOvalue($name, $default);
 | |
|     }
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   /** @var bool */
 | |
|   private $setup = false;
 | |
| 
 | |
|   function ensureSetup(): void {
 | |
|     if (!$this->setup) {
 | |
|       $this->setup();
 | |
|       $this->setup = true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * initialiser l'itérateur. cette méthode est appelée avant de lancer
 | |
|    * l'itération
 | |
|    */
 | |
|   protected function setup(): void {
 | |
|   }
 | |
| 
 | |
|   function getIterator(): iterable {
 | |
|     $this->ensureSetup();
 | |
|     $this->sof = false;
 | |
|     $this->eof = false;
 | |
|     try {
 | |
|       $mapFunc = func::_prepare([$this, "mapper"]);
 | |
|       $outIndex = 0;
 | |
|       $srcIndex = 0;
 | |
|       if ($this->MAP_SOF()) {
 | |
|         $this->sof = true;
 | |
|         while (true) {
 | |
|           $this->action = self::USE_MAPPED_VALUE_ACTION;
 | |
|           $this->mappedIterables = null;
 | |
|           $mappedValue = func::_call($mapFunc, [null, null]);
 | |
|           if ($this->action !== self::RETRY_ACTION) break;
 | |
|         }
 | |
|         switch ($this->action) {
 | |
|         case self::USE_MAPPED_VALUE_ACTION:
 | |
|           if ($mappedValue !== null) {
 | |
|             yield $outIndex => $mappedValue;
 | |
|             $outIndex++;
 | |
|           }
 | |
|           break;
 | |
|         case self::USE_MAPPED_ITERABLES_ACTION:
 | |
|           foreach ($this->mappedIterables as $mappedIterable) {
 | |
|             $seqIndex = 0;
 | |
|             foreach ($mappedIterable as $mappedKey => $mappedValue) {
 | |
|               if ($mappedKey === $seqIndex) {
 | |
|                 # seq
 | |
|                 yield $outIndex => $mappedValue;
 | |
|                 $outIndex++;
 | |
|                 $seqIndex++;
 | |
|               } else {
 | |
|                 # assoc
 | |
|                 yield $mappedKey => $mappedValue;
 | |
|               }
 | |
|             }
 | |
|           }
 | |
|           break;
 | |
|         }
 | |
|         $this->sof = false;
 | |
|       }
 | |
|       if ($this->ppSource !== null) {
 | |
|         foreach ($this->ppSource as $key => $value) {
 | |
|           while (true) {
 | |
|             $this->mappedIterables = null;
 | |
|             $this->action = self::USE_MAPPED_VALUE_ACTION;
 | |
|             $mappedValue = func::_call($mapFunc, [$value, $key]);
 | |
|             if ($this->action !== self::RETRY_ACTION) break;
 | |
|           }
 | |
|           switch ($this->action) {
 | |
|           case self::USE_MAPPED_VALUE_ACTION:
 | |
|             if ($mappedValue !== null) {
 | |
|               if ($key === $srcIndex) {
 | |
|                 yield $outIndex => $mappedValue;
 | |
|                 $outIndex++;
 | |
|               } else {
 | |
|                 yield $key => $mappedValue;
 | |
|               }
 | |
|             }
 | |
|             break;
 | |
|           case self::USE_MAPPED_ITERABLES_ACTION:
 | |
|             foreach ($this->mappedIterables as $mappedIterable) {
 | |
|               $seqIndex = 0;
 | |
|               foreach ($mappedIterable as $mappedKey => $mappedValue) {
 | |
|                 if ($mappedKey === $seqIndex) {
 | |
|                   # seq
 | |
|                   yield $outIndex => $mappedValue;
 | |
|                   $outIndex++;
 | |
|                   $seqIndex++;
 | |
|                 } else {
 | |
|                   # assoc
 | |
|                   yield $mappedKey => $mappedValue;
 | |
|                 }
 | |
|               }
 | |
|             }
 | |
|             break;
 | |
|           }
 | |
|           if ($key === $srcIndex) $srcIndex++;
 | |
|         }
 | |
|       }
 | |
|       if ($this->MAP_EOF()) {
 | |
|         $this->eof = true;
 | |
|         while (true) {
 | |
|           $this->action = self::USE_MAPPED_VALUE_ACTION;
 | |
|           $this->mappedIterables = null;
 | |
|           $mappedValue = func::_call($mapFunc, [null, null]);
 | |
|           if ($this->action !== self::RETRY_ACTION) break;
 | |
|         }
 | |
|         switch ($this->action) {
 | |
|         case self::USE_MAPPED_VALUE_ACTION:
 | |
|           if ($mappedValue !== null) {
 | |
|             yield $outIndex => $mappedValue;
 | |
|           }
 | |
|           break;
 | |
|         case self::USE_MAPPED_ITERABLES_ACTION:
 | |
|           foreach ($this->mappedIterables as $mappedIterable) {
 | |
|             $seqIndex = 0;
 | |
|             foreach ($mappedIterable as $mappedKey => $mappedValue) {
 | |
|               if ($mappedKey === $seqIndex) {
 | |
|                 # seq
 | |
|                 yield $outIndex => $mappedValue;
 | |
|                 $outIndex++;
 | |
|                 $seqIndex++;
 | |
|               } else {
 | |
|                 # assoc
 | |
|                 yield $mappedKey => $mappedValue;
 | |
|               }
 | |
|             }
 | |
|           }
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|     } finally {
 | |
|       $this->close();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /** terminer l'itération. cette méthode est appelée à la fin de l'itération */
 | |
|   protected function teardown(): void {
 | |
|     if ($this->ppSource instanceof ICloseable) $this->ppSource->close();
 | |
|   }
 | |
| 
 | |
|   function close(): void {
 | |
|     if ($this->setup) {
 | |
|       $this->teardown();
 | |
|       $this->setup = false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * @var bool indique que l'on est au début de l'itération si et seulement si
 | |
|    * {@link MAP_SOF()} retourne true
 | |
|    */
 | |
|   protected $sof;
 | |
| 
 | |
|   /**
 | |
|    * @var bool indique que l'on est à la fin de l'itération si et seulement si
 | |
|    * {@link MAP_EOF()} retourne true
 | |
|    */
 | |
|   protected $eof;
 | |
| 
 | |
|   const USE_MAPPED_VALUE_ACTION = 0;
 | |
|   const USE_MAPPED_ITERABLES_ACTION = 1;
 | |
|   const RETRY_ACTION = 2;
 | |
| 
 | |
|   /** @var bool action à effectuer au retour de la fonction {@link mapper()} */
 | |
|   private $action;
 | |
| 
 | |
|   /** @var array valeurs générées par les méthodes {@link mapTo()} */
 | |
|   private $mappedIterables;
 | |
| 
 | |
|   /**
 | |
|    * indiquer que la valeur courante doit être mappée vers $values. si $values
 | |
|    * est null, la valeur courante n'est pas mappée.
 | |
|    * Cette méthode doit être appelée depuis le corps de {@link mapper()}
 | |
|    */
 | |
|   function mapTo(?iterable $values) {
 | |
|     if ($this->action !== self::USE_MAPPED_ITERABLES_ACTION) {
 | |
|       $this->action = self::USE_MAPPED_ITERABLES_ACTION;
 | |
|       $this->mappedIterables = [];
 | |
|     }
 | |
|     if ($values !== null) {
 | |
|       $this->mappedIterables[] = $values;
 | |
|     }
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * indiquer que la valeur courante doit être mappée vers $value avec
 | |
|    * éventuellement la clé $key. $value est fournie telle quelle: notamment,
 | |
|    * cette méthode peut être utilisée pour mapper la valeur courante vers null.
 | |
|    * Cette méthode doit être appelée depuis le corps de {@link mapper()}
 | |
|    */
 | |
|   function mapToValue($value, $key=null) {
 | |
|     if ($key !== null) return $this->mapTo([$key => $value]);
 | |
|     else return $this->mapTo([$value]);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * indiquer que la valeur courante n'est pas mappée.
 | |
|    * Cette méthode doit être appelée depuis le corps de {@link mapper()}
 | |
|    */
 | |
|   function mapToNone() {
 | |
|     return $this->mapTo(null);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * indiquer que la valeur courante ne peut pas être traitée pour le moment et
 | |
|    * qu'elle doit de nouveau être présentée à la fonction {@link mapper()} la
 | |
|    * fois suivante. cela permet d'implémenter des méthodes de mapping plus
 | |
|    * complexes. attention tout de même à ne pas créer de boucles infinies.
 | |
|    * Cette méthode doit être appelée depuis le corps de {@link mapper()}
 | |
|    */
 | |
|   function retry() {
 | |
|     $this->action = self::RETRY_ACTION;
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * mapper la valeur courante $item. il faut
 | |
|    * - soit retourner la valeur mappée. si on retourne null, la valeur n'est pas
 | |
|    * mappée (il n'y a pas de valeur correspondante, elle est donc ignorée).
 | |
|    * avec cette façon de faire, les clés originales sont conservées
 | |
|    * - soit appeler l'une des méthodes {@link mapTo()}, {@link mapToValue()},
 | |
|    * {@link mapToNone()} pour indiquer la ou les valeurs correspondantes (ainsi
 | |
|    * que les clés correspondantes le cas échéant). avec cette façon de faire,
 | |
|    * les clés originales sont perdues
 | |
|    *
 | |
|    * Dans les classes dérivées, il est possible de rajouter "$key=null" à la
 | |
|    * signature de cette fonction pour avoir aussi la clé
 | |
|    */
 | |
|   abstract function mapper($item);
 | |
| 
 | |
|   #############################################################################
 | |
|   const _AUTOGEN_CONSTS = [
 | |
|     "" => [self::class, "_autogen_consts"],
 | |
|   ];
 | |
|   const _AUTOGEN_LITERALS = /*autogen*/[
 | |
|     [
 | |
|       \nur\b\params\parametrable_utils::class,
 | |
|       '\\nur\\b\\params\\parametrable_utils::class',
 | |
|     ],
 | |
|     [
 | |
|       self::PARAMETRABLE_PARAMS_SCHEMA,
 | |
|       'self::PARAMETRABLE_PARAMS_SCHEMA',
 | |
|     ],
 | |
|   ];
 | |
|   const _AUTOGEN_METHODS = /*autogen*/[
 | |
|     [
 | |
|       \nur\b\params\parametrable_utils::class,
 | |
|       '_autogen_methods_getters',
 | |
|       self::PARAMETRABLE_PARAMS_SCHEMA,
 | |
|       null,
 | |
|     ],
 | |
|     [
 | |
|       \nur\b\params\parametrable_utils::class,
 | |
|       '_autogen_methods_setters',
 | |
|       self::PARAMETRABLE_PARAMS_SCHEMA,
 | |
|       null,
 | |
|     ],
 | |
|   ];
 | |
|   const _AUTO_GETTERS = /*autogen*/[
 | |
|     'getSource' => 'source',
 | |
|   ];
 | |
|   const _AUTO_SETTERS = /*autogen*/[
 | |
|     'setSource' => 'source',
 | |
|   ];
 | |
|   #--autogen-dynamic--
 | |
| }
 |