318 lines
9.4 KiB
PHP
318 lines
9.4 KiB
PHP
<?php
|
|
namespace nur\sery\wip\schema\_scalar;
|
|
|
|
use nulib\php\func;
|
|
use nulib\ref\schema\ref_analyze;
|
|
use nulib\ValueException;
|
|
use nur\sery\wip\schema\WrapperContext;
|
|
use nur\sery\wip\schema\input\Input;
|
|
use nur\sery\wip\schema\types;
|
|
use nur\sery\wip\schema\types\IType;
|
|
use nur\sery\wip\schema\Wrapper;
|
|
|
|
class ScalarWrapper extends Wrapper {
|
|
function __construct(ScalarSchema $schema, &$value=null, $valueKey=null, ?array $params=null) {
|
|
$verifix = $params["verifix"] ?? true;
|
|
$throw = $params["throw"] ?? null;
|
|
if ($value !== null && $throw === null) {
|
|
# Si $value est null, ne pas lancer d'exception, parce qu'on considère que
|
|
# c'est une initialisation sans conséquences
|
|
$throw = true;
|
|
}
|
|
$this->verifix = $verifix;
|
|
$this->throw = $throw ?? false;
|
|
$this->schema = $schema;
|
|
$this->result = new ScalarResult();
|
|
$this->reset($value, $valueKey);
|
|
$this->throw = $throw ?? true;
|
|
}
|
|
|
|
function isScalar(?ScalarWrapper &$wrapper=null): bool { $wrapper = $this; return true; }
|
|
|
|
protected bool $verifix;
|
|
|
|
protected bool $throw;
|
|
|
|
/** schéma de cette valeur */
|
|
protected ScalarSchema $schema;
|
|
|
|
/** source et destination de la valeur */
|
|
protected Input $input;
|
|
|
|
/** @var string|int|null clé de la valeur dans le tableau destination */
|
|
protected $valueKey;
|
|
|
|
/** type de la valeur après analyse */
|
|
protected ?IType $type;
|
|
|
|
/** résultat de l'analyse de la valeur */
|
|
protected ScalarResult $result;
|
|
|
|
protected function newInput(&$value): Input {
|
|
return new Input($value);
|
|
}
|
|
|
|
function reset(&$value, $valueKey=null, ?bool $verifix=null): Wrapper {
|
|
if ($value instanceof Input) $input = $value;
|
|
else $input = $this->newInput($value);
|
|
$this->input = $input;
|
|
$this->valueKey = $valueKey;
|
|
$this->type = null;
|
|
$this->analyze();
|
|
if ($verifix ?? $this->verifix) $this->verifix();
|
|
return $this;
|
|
}
|
|
|
|
function getKeys(): array {
|
|
return [null];
|
|
}
|
|
|
|
/** @param string|int|null $key */
|
|
function select($key): ScalarWrapper {
|
|
if ($key !== null) throw ValueException::invalid_key($key);
|
|
return $this;
|
|
}
|
|
|
|
/** analyser la valeur et résoudre son type */
|
|
protected function analyze0(WrapperContext $context): int {
|
|
/** @var ScalarSchema $schema */
|
|
$schema = $context->schema;
|
|
$input = $context->input;
|
|
$valueKey = $context->valueKey;
|
|
/** @var ScalarResult $result */
|
|
$result = $context->result;
|
|
|
|
$default = $schema->default;
|
|
if (!$input->isPresent($valueKey)) {
|
|
if ($default !== null) {
|
|
$input->set($default, $valueKey);
|
|
return $result->setNormalized();
|
|
} else {
|
|
return $result->setMissing($schema);
|
|
}
|
|
}
|
|
|
|
$schemaTypes = $schema->type;
|
|
if ($schemaTypes instanceof IType) {
|
|
$type = $schemaTypes;
|
|
} else {
|
|
# type union
|
|
$haveType = false;
|
|
$types = [];
|
|
$type = $firstType = null;
|
|
$value = null;
|
|
# d'abord chercher un type pour lequel c'est une valeur normalisée
|
|
$index = 0;
|
|
$haveValue = false;
|
|
foreach ($schemaTypes as $key => $name) {
|
|
if ($key === $index) {
|
|
$index++;
|
|
$args = null;
|
|
} else {
|
|
$args = $name;
|
|
$name = $key;
|
|
}
|
|
$type = types::get($schema->nullable, $name, $args, $this->schema->getDefinition());
|
|
if ($firstType === null) $firstType = $type;
|
|
$types[] = $type;
|
|
if ($type->isAvailable($input, $valueKey)) {
|
|
if (!$haveValue) {
|
|
$value = $input->get($valueKey);
|
|
$haveValue = true;
|
|
}
|
|
if ($type->isValid($value, $normalized) && $normalized) {
|
|
$haveType = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
# ensuite chercher un type pour lequel la valeur est valide
|
|
if (!$haveType) {
|
|
foreach ($types as $type) {
|
|
if ($type->isAvailable($input, $valueKey) && $type->isValid($value)) {
|
|
$haveType = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
# sinon prendre le premier type
|
|
if (!$haveType) {
|
|
$type = $firstType;
|
|
}
|
|
}
|
|
$context->type = $this->type = $type;
|
|
|
|
if (!$type->isAvailable($input, $valueKey)) {
|
|
if ($default !== null) {
|
|
$input->set($default, $valueKey);
|
|
return $result->setNormalized();
|
|
} else {
|
|
return $result->setUnavailable($schema);
|
|
}
|
|
}
|
|
|
|
$value = $input->get($valueKey);
|
|
$context->origValue = $context->value = $value;
|
|
if ($type->isNull($value)) {
|
|
return $result->setNull($schema);
|
|
} elseif (is_string($value)) {
|
|
return ref_analyze::STRING;
|
|
} elseif ($type->isValid($value, $normalized)) {
|
|
if ($normalized) return $result->setNormalized();
|
|
else return $result->setValid();
|
|
} else {
|
|
return $result->setInvalid($value, $schema);
|
|
}
|
|
}
|
|
|
|
protected function analyze(): int {
|
|
$schema = $this->schema;
|
|
$input = $this->input;
|
|
$valueKey = $this->valueKey;
|
|
$result = $this->result;
|
|
$result->reset();
|
|
$context = new WrapperContext($schema, $this, $input, $valueKey, $result);
|
|
|
|
/** @var func $analyzerFunc */
|
|
$analyzerFunc = $schema->analyzerFunc;
|
|
if ($analyzerFunc !== null) $what = $analyzerFunc->invoke([$context]);
|
|
else $what = $this->analyze0($context);
|
|
if ($what !== ref_analyze::STRING) return $what;
|
|
|
|
$value = $context->value;
|
|
try {
|
|
/** @var func $extractorFunc */
|
|
$extractorFunc = $schema->extractorFunc;
|
|
if ($extractorFunc !== null) $extracted = $extractorFunc->invoke([$value, $context]);
|
|
else $extracted = $context->type->extract($value);
|
|
$context->value = $extracted;
|
|
} catch (ValueException $e) {
|
|
return $result->setInvalid($context->origValue, $schema, $e);
|
|
}
|
|
if ($context->type->isNull($extracted)) return $result->setNull($schema);
|
|
|
|
try {
|
|
/** @var func $parserFunc */
|
|
$parserFunc = $schema->parserFunc;
|
|
if ($parserFunc !== null) $parsed = $parserFunc->invoke([$extracted, $context]);
|
|
else $parsed = $context->type->parse($extracted);
|
|
$context->value = $parsed;
|
|
} catch (ValueException $e) {
|
|
return $result->setInvalid($context->origValue, $schema, $e);
|
|
}
|
|
|
|
$normalized = $parsed === $context->origValue;
|
|
if ($normalized) {
|
|
$input->set($parsed, $valueKey);
|
|
return $result->setNormalized();
|
|
} else {
|
|
$input->set($extracted, $valueKey);
|
|
return $result->setValid($parsed);
|
|
}
|
|
}
|
|
|
|
function verifix(?bool $throw=null): bool {
|
|
$result = $this->result;
|
|
$valueKey = $this->valueKey;
|
|
$verifix = false;
|
|
$modified = false;
|
|
if ($result->resultAvailable) {
|
|
if ($result->null) {
|
|
# forcer la valeur null, parce que la valeur actuelle est peut-être une
|
|
# valeur assimilée à null
|
|
$this->input->set(null, $valueKey);
|
|
} elseif ($result->valid && !$result->normalized) {
|
|
$normalizedValue = $result->normalizedValue;
|
|
if ($normalizedValue !== null) {
|
|
# la valeur normalisée est disponible
|
|
$this->input->set($normalizedValue);
|
|
$result->normalizedValue = null;
|
|
$modified = true;
|
|
} else {
|
|
# normaliser la valeur
|
|
$verifix = true;
|
|
}
|
|
}
|
|
} else {
|
|
$verifix = true;
|
|
}
|
|
|
|
if ($verifix) {
|
|
$value = $this->input->get($valueKey);
|
|
$schema = $this->schema;
|
|
/** @var func $normalizerFunc */
|
|
$normalizerFunc = $schema->normalizerFunc;
|
|
if ($normalizerFunc !== null) {
|
|
$context = new WrapperContext($schema, $this, $this->input, $valueKey, $result);
|
|
$orig = $value;
|
|
$value = $normalizerFunc->invoke([$orig, $context]);
|
|
$modified = $value !== $orig;
|
|
} else {
|
|
$modified = $this->type->verifix($value, $result, $this->schema);
|
|
}
|
|
if ($result->valid) $this->input->set($value, $valueKey);
|
|
}
|
|
if (!$result->valid) $result->throw($throw ?? $this->throw);
|
|
return $modified;
|
|
}
|
|
|
|
function getResult(): ScalarResult {
|
|
return $this->result;
|
|
}
|
|
|
|
function isPresent(): bool {
|
|
return $this->result->present;
|
|
}
|
|
|
|
function getType(): IType {
|
|
return $this->type;
|
|
}
|
|
|
|
function isAvailable(): bool {
|
|
return $this->result->available;
|
|
}
|
|
|
|
function isValid(): bool {
|
|
return $this->result->valid;
|
|
}
|
|
|
|
function isNormalized(): bool {
|
|
return $this->result->normalized;
|
|
}
|
|
|
|
function get($default=null) {
|
|
if ($this->result->available) return $this->input->get($this->valueKey);
|
|
else return $default;
|
|
}
|
|
|
|
function set($value, ?bool $verifix=null): ScalarWrapper {
|
|
$this->input->set($value, $this->valueKey);
|
|
$this->analyze();
|
|
if ($verifix ?? $this->verifix) $this->verifix();
|
|
return $this;
|
|
}
|
|
|
|
function unset(?bool $verifix=null): ScalarWrapper {
|
|
$this->input->unset($this->valueKey);
|
|
$this->analyze();
|
|
if ($verifix ?? $this->verifix) $this->verifix();
|
|
return $this;
|
|
}
|
|
|
|
function format($format=null): string {
|
|
$value = $this->input->get($this->valueKey);
|
|
/** @var func $formatterFunc */
|
|
$formatterFunc = $this->schema->formatterFunc;
|
|
if ($formatterFunc !== null) {
|
|
# la fonction formatter n'a pas forcément accès au format de la définition
|
|
# le lui fournir ici
|
|
$format ??= $this->schema->format;
|
|
return $formatterFunc->invoke([$value, $format]);
|
|
} else {
|
|
# on assume que le type a été initialisé avec le format de la définition
|
|
# le cas échéant
|
|
return $this->type->format($value, $format);
|
|
}
|
|
}
|
|
}
|