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