schema = $schema; $this->verifix = $verifix; $this->throw = $throw ?? false; $this->result = new ScalarResult(); $this->reset($value, $valueKey); $this->throw = $throw ?? true; } function isScalar(?ScalarWrapper &$scalar=null): bool { $scalar = $this; return true; } /** 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; protected bool $verifix; protected bool $throw; /** 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->analyzeExtractParse(); if ($verifix ?? $this->verifix) $this->verifix(); return $this; } function getKeys(): array { return [null]; } function getWrapper($key=null): ScalarWrapper { if ($key === null) return $this; throw ValueException::invalid_key($key); } /** analyser la valeur et résoudre son type */ function _analyze(AnalyzerContext $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++; $params = null; } else { $params = $name; $name = $key; } $type = types::get($schema->nullable, $name, $params, $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); } } function analyzeExtractParse(): int { $schema = $this->schema; $input = $this->input; $valueKey = $this->valueKey; $result = $this->result; $result->reset(); $context = new AnalyzerContext($schema, $this, $input, $valueKey, $result); /** @var func $analyzerFunc */ $analyzerFunc = $schema->analyzerFunc; if ($analyzerFunc !== null) $what = $analyzerFunc->invoke([$context]); else $what = $this->_analyze($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 AnalyzerContext($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->analyzeExtractParse(); if ($verifix ?? $this->verifix) $this->verifix(); return $this; } function unset(?bool $verifix=null): ScalarWrapper { $this->input->unset($this->valueKey); $this->analyzeExtractParse(); 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); } } }