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