nur-sery/nur_src/mapper/base/Mapper.php

363 lines
11 KiB
PHP
Raw Normal View History

2024-04-04 16:26:22 +04:00
<?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--
}