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--
 | 
						|
}
 |