diff --git a/.pman.conf b/.pman.conf index 483215c..db579e6 100644 --- a/.pman.conf +++ b/.pman.conf @@ -12,12 +12,16 @@ DIST= NOAUTO= AFTER_CREATE_RELEASE=' +set -x pman --composer-select-profile dist -composer u +composer u || exit 1 git commit -am "deps de dist" +true ' AFTER_MERGE_RELEASE=' +set -x pman --composer-select-profile dev -composer u +composer u || exit 1 git commit -am "deps de dev" +true ' diff --git a/src/schema/AnalyzerContext.php b/src/schema/AnalyzerContext.php new file mode 100644 index 0000000..3e540c9 --- /dev/null +++ b/src/schema/AnalyzerContext.php @@ -0,0 +1,25 @@ +schema = $schema; + $this->input = $input; + $this->destKey = $destKey; + $this->result = $result; + $this->type = null; + $this->value = null; + } + + public Schema $schema; + public Input $input; + /** @var int|string|null */ + public $destKey; + public Result $result; + public ?IType $type; + /** @var mixed */ + public $value; +} diff --git a/src/schema/Schema.php b/src/schema/Schema.php index bd5d11b..0e67c9d 100644 --- a/src/schema/Schema.php +++ b/src/schema/Schema.php @@ -57,8 +57,11 @@ abstract class Schema implements ArrayAccess { */ const SCHEMA = null; - /** @var array */ - protected $definition; + protected array $definition; + + function getDefinition(): array { + return $this->definition; + } /** retourner true si le schéma est de nature tableau associatif */ function isAssoc(?AssocSchema &$assoc=null): bool { return false; } diff --git a/src/schema/_scalar/ScalarResult.php b/src/schema/_scalar/ScalarResult.php index 3cd0e60..1ef71ee 100644 --- a/src/schema/_scalar/ScalarResult.php +++ b/src/schema/_scalar/ScalarResult.php @@ -16,11 +16,12 @@ use nur\sery\wip\schema\Result; * @property bool $null si la valeur est disponible, est-elle nulle? * @property bool $valid si la valeur est disponible, est-elle valide? * @property bool $normalized si la valeur est valide, est-elle normalisée? - * @property string|null $orig valeur originale avant analyse avec parse() + * @property string|null $orig valeur originale avant extraction et analyse + * @property string|null $messageKey clé de message si la valeur n'est pas valide * @property string|null $message message si la valeur n'est pas valide */ class ScalarResult extends Result { - const KEYS = ["resultAvailable", "present", "available", "null", "valid", "normalized", "orig", "message"]; + const KEYS = ["resultAvailable", "present", "available", "null", "valid", "normalized", "orig", "messageKey", "message"]; function isScalar(?ScalarResult &$scalar=null): bool { $scalar = $this; return true; } @@ -85,7 +86,8 @@ class ScalarResult extends Result { $this->normalized = true; return ref_analyze::NORMALIZED; } else { - $message = $this->getMessage("missing", $schema); + $messageKey = $this->messageKey = "missing"; + $message = $this->getMessage($messageKey, $schema); self::replace_key($message, $schema->name); $this->message = $message; return ref_analyze::MISSING; @@ -102,7 +104,8 @@ class ScalarResult extends Result { $this->normalized = true; return ref_analyze::NORMALIZED; } else { - $message = $this->getMessage("unavailable", $schema); + $messageKey = $this->messageKey = "unavailable"; + $message = $this->getMessage($messageKey, $schema); self::replace_key($message, $schema->name); $this->message = $message; return ref_analyze::UNAVAILABLE; @@ -119,7 +122,8 @@ class ScalarResult extends Result { $this->normalized = true; return ref_analyze::NORMALIZED; } else { - $message = $this->getMessage("null", $schema); + $messageKey = $this->messageKey = "null"; + $message = $this->getMessage($messageKey, $schema); self::replace_key($message, $schema->name); $this->message = $message; return ref_analyze::NULL; @@ -133,7 +137,8 @@ class ScalarResult extends Result { $this->null = false; $this->valid = false; $this->orig = $value; - $message = $this->getMessage("invalid", $schema); + $messageKey = $this->messageKey = "invalid"; + $message = $this->getMessage($messageKey, $schema); self::replace_key($message, $schema->name); self::replace_orig($message, $schema->orig); $this->message = $message; diff --git a/src/schema/_scalar/ScalarValue.php b/src/schema/_scalar/ScalarValue.php index b836465..b4d6269 100644 --- a/src/schema/_scalar/ScalarValue.php +++ b/src/schema/_scalar/ScalarValue.php @@ -1,9 +1,13 @@ input = $input; $this->destKey = $destKey; $this->type = null; - $this->_analyze(); + $this->analyzeExtractParse(); if ($verifix === null) $verifix = $this->defaultVerifix; if ($verifix) $this->verifix(); return $this; @@ -68,12 +72,12 @@ class ScalarValue extends Value { } /** analyser la valeur et résoudre son type */ - function _analyze(): int { + function _analyze(AnalyzerContext $context): int { $schema = $this->schema; $input = $this->input; $destKey = $this->destKey; $result = $this->result; - $result->reset(); + if (!$input->isPresent($destKey)) return $result->setMissing($schema); $haveType = false; @@ -82,8 +86,16 @@ class ScalarValue extends Value { $haveValue = false; $value = null; # d'abord chercher un type pour lequel c'est une valeur normalisée - foreach ($schema->type as $name) { - $type = types::get($name); + $index = 0; + foreach ($schema->type as $key => $name) { + if ($key === $index) { + $index++; + $params = null; + } else { + $params = $name; + $name = $key; + } + $type = types::get($name, $params, $this->schema->getDefinition()); if ($firstType === null) $firstType = $type; $types[] = $type; if ($type->isAvailable($input, $destKey)) { @@ -113,7 +125,7 @@ class ScalarValue extends Value { if (!$haveType) $type = $this->type = $firstType; if (!$type->isAvailable($input, $destKey)) return $result->setUnavailable($schema); - $value = $input->get($destKey); + $value = $context->value = $input->get($destKey); if ($type->isNull($value)) return $result->setNull($schema); if ($type->isValid($value, $normalized)) { if ($normalized) return $result->setNormalized(); @@ -124,6 +136,44 @@ class ScalarValue extends Value { else return $result->setInvalid($value, $schema); } + function _extract(AnalyzerContext $context): void { + $value = $context->value; + $value = $context->type->extract($value); + $context->value = $value; + } + + function _parse(AnalyzerContext $context): void { + $value = $context->value; + $value = $context->type->parse($value); + $context->value = $value; + } + + function analyzeExtractParse(): void { + $schema = $this->schema; + $input = $this->input; + $destKey = $this->destKey; + $result = $this->result; + $result->reset(); + $context = new AnalyzerContext($schema, $input, $destKey, $result); + + /** @var func $analyzerFunc */ + $analyzerFunc = $schema->analyzerFunc; + if ($analyzerFunc !== null) $what = $analyzerFunc->invoke([$context]); + else $what = $this->_analyze($context); + + if ($what === ref_analyze::STRING) { + /** @var func $extractorFunc */ + $extractorFunc = $schema->extractorFunc; + if ($extractorFunc !== null) $extractorFunc->invoke([$context]); + else $this->_extract($context); + + /** @var func $parserFunc */ + $parserFunc = $schema->parserFunc; + if ($parserFunc !== null) $parserFunc->invoke([$context]); + else $this->_parse($context); + } + } + function verifix(?bool $throw=null): bool { if ($throw === null) $throw = $this->defaultThrow; $destKey = $this->destKey; @@ -182,7 +232,7 @@ class ScalarValue extends Value { function set($value, ?bool $verifix=null): ScalarValue { $this->input->set($value, $this->destKey); - $this->_analyze(); + $this->analyzeExtractParse(); if ($verifix === null) $verifix = $this->defaultVerifix; if ($verifix) $this->verifix(); return $this; @@ -190,7 +240,7 @@ class ScalarValue extends Value { function unset(?bool $verifix=null): ScalarValue { $this->input->unset($this->destKey); - $this->_analyze(); + $this->analyzeExtractParse(); if ($verifix === null) $verifix = $this->defaultVerifix; if ($verifix) $this->verifix(); return $this; diff --git a/src/schema/types.php b/src/schema/types.php index ae3a11f..4c5c8f2 100644 --- a/src/schema/types.php +++ b/src/schema/types.php @@ -24,8 +24,8 @@ class types { return self::$registry; } - static function get(string $name): IType { - return self::registry()->get($name); + static function get(string $name, ?array $params=null, ?array $definition=null): IType { + return self::registry()->get($name, $params, $definition); } static function rawstring(): trawstring { return self::get("rawstring"); } diff --git a/src/schema/types/IType.php b/src/schema/types/IType.php index e470811..30969e1 100644 --- a/src/schema/types/IType.php +++ b/src/schema/types/IType.php @@ -18,6 +18,12 @@ interface IType { /** la valeur $value est-elle valide et normalisée le cas échéant? */ function isValid($value, ?bool &$normalized=null): bool; + /** extraire de la chaine la valeur à analyser */ + function extract(string $value): string; + + /** analyser la chaine et retourner la valeur "convertie" */ + function parse(string $value); + /** * analyser, corriger éventuellement et normaliser la valeur * diff --git a/src/schema/types/Registry.php b/src/schema/types/Registry.php index be8fe0c..b3c14a6 100644 --- a/src/schema/types/Registry.php +++ b/src/schema/types/Registry.php @@ -27,16 +27,14 @@ class Registry { /** @var IType[] */ protected $types; - function get(string $name, ?array $params=null): IType { + function get(string $name, ?array $params=null, ?array $definition=null): IType { + $class = self::TYPES[$name]; + $params = cl::merge($class::get_params($definition), $params); if ($params !== null) { - $class = self::TYPES[$name]; return func::with([$class, false, $params])->invoke(); } $type = cl::get($this->types, $name); - if ($type === null) { - $class = self::TYPES[$name]; - $type = $this->types[$name] = new $class(); - } + if ($type === null) $type = $this->types[$name] = new $class(); return $type; } } diff --git a/src/schema/types/_tsimple.php b/src/schema/types/_tsimple.php index a300743..b858461 100644 --- a/src/schema/types/_tsimple.php +++ b/src/schema/types/_tsimple.php @@ -4,6 +4,10 @@ namespace nur\sery\wip\schema\types; use nur\sery\wip\schema\input\Input; abstract class _tsimple implements IType { + static function get_params(?array $definition): ?array { + return null; + } + function __construct(?array $params=null) { $this->params = $params; } @@ -17,4 +21,12 @@ abstract class _tsimple implements IType { function isNull($value): bool { return $value === null || (is_string($value) && trim($value) === ""); } + + function extract(string $value): string { + return $value; + } + + function parse(string $value) { + return $value; + } } diff --git a/src/schema/types/tint.php b/src/schema/types/tint.php index fb511e7..1f3b9b6 100644 --- a/src/schema/types/tint.php +++ b/src/schema/types/tint.php @@ -7,6 +7,13 @@ use nur\sery\wip\schema\Result; use nur\sery\wip\schema\Schema; class tint extends _tsimple { + static function get_params(?array $definition): ?array { + if ($definition === null) return null; + $format = $definition["format"] ?? null; + if ($format === null) return null; + return ["format" => $format]; + } + static function ensure_int(&$int): void { if (!is_int($int)) $int = intval($int); } @@ -15,12 +22,16 @@ class tint extends _tsimple { if ($int !== null) self::ensure_int($int); } - const INT_PATTERN = '/^[-+]?[0-9]+(?:\.[0-9]*)?$/'; + //const INT_PATTERN = '/^[-+]?[0-9]+(?:\.[0-9]*)?$/'; function isValid($value, ?bool &$normalized=null): bool { $normalized = is_int($value); - if (is_string($value)) $valid = is_numeric(trim($value)); - else $valid = is_scalar($value); + if (is_string($value)) { + $value = str_replace(",", ".", trim($value)); + $valid = is_numeric($value); + } else { + $valid = is_scalar($value); + } return $valid; } @@ -33,7 +44,7 @@ class tint extends _tsimple { $result->setNormalized(); return false; } elseif (is_string($value)) { - $int = trim($value); + $int = str_replace(",", ".", trim($value)); if (is_numeric($int)) $value = intval($int); else return $result->setInvalid($value, $schema); } elseif (is_scalar($value)) { @@ -41,11 +52,12 @@ class tint extends _tsimple { } else { return $result->setInvalid($value, $schema); } - $result->setValid(); + $result->setNormalized(); return true; } function format($value, $format=null): string { + $format ??= $this->params["format"] ?? null; if ($format !== null) return sprintf($format, $value); else return strval($value); } diff --git a/tests/wip/schema/_scalar/ScalarValueTest.php b/tests/wip/schema/_scalar/ScalarValueTest.php index a8bdc70..db29d83 100644 --- a/tests/wip/schema/_scalar/ScalarValueTest.php +++ b/tests/wip/schema/_scalar/ScalarValueTest.php @@ -35,26 +35,26 @@ class ScalarValueTest extends TestCase { $string = " "; $value->reset($string); $this->checkValue($value, " ", true, true, true, true); - $string = "value"; $value->reset($string, null, false); - $this->checkValue($value, "value", true, true, true, true); - $string = "value"; $value->reset($string); - $this->checkValue($value, "value", true, true, true, true); + $string = "text"; $value->reset($string, null, false); + $this->checkValue($value, "text", true, true, true, true); + $string = "text"; $value->reset($string); + $this->checkValue($value, "text", true, true, true, true); - $string = " value "; $value->reset($string, null, false); - $this->checkValue($value, " value ", true, true, true, true); - $string = " value "; $value->reset($string); - $this->checkValue($value, " value ", true, true, true, true); - - $string = true; $value->reset($string, null, false); - $this->checkValue($value, true, true, true, true, false); - $string = true; $value->reset($string); - $this->checkValue($value, "1", true, true, true, true); + $string = " text "; $value->reset($string, null, false); + $this->checkValue($value, " text ", true, true, true, true); + $string = " text "; $value->reset($string); + $this->checkValue($value, " text ", true, true, true, true); $string = false; $value->reset($string, null, false); $this->checkValue($value, null, true, false, true, true); $string = false; $value->reset($string); $this->checkValue($value, null, true, false, true, true); + $string = true; $value->reset($string, null, false); + $this->checkValue($value, true, true, true, true, false); + $string = true; $value->reset($string); + $this->checkValue($value, "1", true, true, true, true); + $string = 42; $value->reset($string, null, false); $this->checkValue($value, 42, true, true, true, false); $string = 42; $value->reset($string); @@ -108,26 +108,26 @@ class ScalarValueTest extends TestCase { $string = " "; $value->reset($string); $this->checkValue($value, "", true, true, true, true); - $string = "value"; $value->reset($string, null, false); - $this->checkValue($value, "value", true, true, true, true); - $string = "value"; $value->reset($string); - $this->checkValue($value, "value", true, true, true, true); + $string = "text"; $value->reset($string, null, false); + $this->checkValue($value, "text", true, true, true, true); + $string = "text"; $value->reset($string); + $this->checkValue($value, "text", true, true, true, true); - $string = " value "; $value->reset($string, null, false); - $this->checkValue($value, " value ", true, true, true, false); - $string = " value "; $value->reset($string); - $this->checkValue($value, "value", true, true, true, true); - - $string = true; $value->reset($string, null, false); - $this->checkValue($value, true, true, true, true, false); - $string = true; $value->reset($string); - $this->checkValue($value, "1", true, true, true, true); + $string = " text "; $value->reset($string, null, false); + $this->checkValue($value, " text ", true, true, true, false); + $string = " text "; $value->reset($string); + $this->checkValue($value, "text", true, true, true, true); $string = false; $value->reset($string, null, false); $this->checkValue($value, null, true, false, true, true); $string = false; $value->reset($string); $this->checkValue($value, null, true, false, true, true); + $string = true; $value->reset($string, null, false); + $this->checkValue($value, true, true, true, true, false); + $string = true; $value->reset($string); + $this->checkValue($value, "1", true, true, true, true); + $string = 42; $value->reset($string, null, false); $this->checkValue($value, 42, true, true, true, false); $string = 42; $value->reset($string); @@ -159,4 +159,93 @@ class ScalarValueTest extends TestCase { $string = false; $value->reset($string); }); } + + function testInt() { + $schema = new ScalarSchema("int"); + $value = $schema->newValue(); + + $int = null; $value->reset($int, null, false); + $this->checkValue($value, null, true, true, false, false); + self::assertException(ValueException::class, function() use (&$value) { + $int = null; + $value->reset($int); + }); + + $int = 42; $value->reset($int, null, false); + $this->checkValue($value, 42, true, true, true, true); + $int = 42; $value->reset($int); + $this->checkValue($value, 42, true, true, true, true); + + $int = "42"; $value->reset($int, null, false); + $this->checkValue($value, "42", true, true, true, false); + $int = "42"; $value->reset($int); + $this->checkValue($value, 42, true, true, true, true); + + $int = "42.5"; $value->reset($int, null, false); + $this->checkValue($value, "42.5", true, true, true, false); + $int = "42.5"; $value->reset($int); + $this->checkValue($value, 42, true, true, true, true); + + $int = "42,5"; $value->reset($int, null, false); + $this->checkValue($value, "42,5", true, true, true, false); + $int = "42,5"; $value->reset($int); + $this->checkValue($value, 42, true, true, true, true); + + $int = ""; $value->reset($int, null, false); + $this->checkValue($value, "", true, true, false, false); + self::assertException(ValueException::class, function() use (&$value) { + $int = ""; + $value->reset($int); + }); + + $int = " "; $value->reset($int, null, false); + $this->checkValue($value, " ", true, true, false, false); + self::assertException(ValueException::class, function() use (&$value) { + $int = " "; + $value->reset($int); + }); + + $int = "text"; $value->reset($int, null, false); + $this->checkValue($value, "text", true, true, false, true); + self::assertException(ValueException::class, function() use (&$value) { + $int = "text"; + $value->reset($int); + }); + + $int = false; $value->reset($int, null, false); + $this->checkValue($value, null, true, false, true, true); + $int = false; $value->reset($int); + $this->checkValue($value, null, true, false, true, true); + + $int = true; $value->reset($int, null, false); + $this->checkValue($value, true, true, true, true, false); + $int = true; $value->reset($int); + $this->checkValue($value, 1, true, true, true, true); + + $int = []; $value->reset($int, null, false); + $this->checkValue($value, [], true, true, false, false); + self::assertException(ValueException::class, function() use (&$value) { + $string = null; + $value->reset($string); + }); + + ## Tester nullable + $schema = new ScalarSchema("?int"); + $value = $schema->newValue(); + + $int = null; $value->reset($int, null, false); + $this->checkValue($value, null, true, true, true, true); + $int = null; $value->reset($int, null); + $this->checkValue($value, null, true, true, true, true); + + ## Tester required + $schema = new ScalarSchema(["int", "required" => true]); + $value = $schema->newValue(); + + $int = false; $value->reset($int, null, false); + $this->checkValue($value, null, true, false, false, false); + self::assertException(ValueException::class, function() use (&$value) { + $string = false; $value->reset($string); + }); + } }