diff --git a/src/schema/Schema.php b/src/schema/Schema.php index 82b7fa4..3f110ad 100644 --- a/src/schema/Schema.php +++ b/src/schema/Schema.php @@ -3,6 +3,7 @@ namespace nur\sery\schema; use ArrayAccess; use LogicException; +use nulib\AccessException; use nulib\cl; use nur\sery\schema\_assoc\AssocSchema; use nur\sery\schema\_list\ListSchema; @@ -12,8 +13,17 @@ abstract class Schema implements ArrayAccess { /** * créer si besoin une nouvelle instance de {@link Schema} à partir d'une * définition de schéma + * + * - si $schema est une instance de schéma, la retourner + * - si $schema est un array, c'est une définition, et elle est remplacée par + * l'instance de Schema nouvelle créée + * - sinon, prendre $definition comme définition */ - static function ns(?Schema &$schema, $definition, $definitionKey=null): self { + static function ns(&$schema, $definition=null, $definitionKey=null): self { + if (is_array($schema)) { + $definition = $schema; + $schema = null; + } if ($schema === null) { if (AssocSchema::isa_definition($definition)) { $schema = new AssocSchema($definition, $definitionKey); @@ -30,15 +40,16 @@ abstract class Schema implements ArrayAccess { /** * Créer si besoin une nouvelle instance de {@link Value} qui référence la - * variable $dest + * variable $dest (si $destKey===null) ou $dest[$destKey] si $destKey n'est + * pas null */ - static function nv(?Value &$value=null, &$dest=null, $key=null, ?Schema &$schema=null, $definition=null): Value { + static function nv(?Value &$value=null, &$dest=null, $destKey=null, &$schema=null, $definition=null): Value { if ($definition === null) { # bien que techniquement, $definition peut être null (il s'agit alors du # schéma d'un scalaire quelconque), on ne l'autorise pas ici throw SchemaException::invalid_schema("definition is required"); } - return self::ns($schema, $definition)->newValue($value, $dest, $key); + return self::ns($schema, $definition)->newValue($value, $dest, $destKey); } /** @@ -70,10 +81,10 @@ abstract class Schema implements ArrayAccess { else return $this->definition[$offset]; } function offsetSet($offset, $value): void { - throw new LogicException("read-only"); + throw AccessException::read_only(null, $offset); } function offsetUnset($offset): void { - throw new LogicException("read-only"); + throw AccessException::read_only(null, $offset); } const _PROPERTY_PKEYS = []; diff --git a/src/schema/TODO.md b/src/schema/TODO.md index 2367491..a9b25c7 100644 --- a/src/schema/TODO.md +++ b/src/schema/TODO.md @@ -5,7 +5,7 @@ on pourrait avoir d'une manière générale quelque chose comme: ~~~ - Schema::ensure(&$schema, ?array $def=null, $key=null): Schema; + Schema::ensure(&$schema, ?array $def=null, $defKey=null): Schema; ~~~ * si $schema est une instance de Schema, la retourner * si c'est un array, c'est une définition et il faut la remplacer par l'instance de Schema correspondant diff --git a/src/schema/Value.php b/src/schema/Value.php index bc8de23..c80239b 100644 --- a/src/schema/Value.php +++ b/src/schema/Value.php @@ -14,7 +14,7 @@ abstract class Value implements ArrayAccess, IteratorAggregate { function isScalar(?ScalarValue &$scalar=null): bool { return false; } /** spécifier la valeur destination gérée par cet objet */ - abstract function reset(&$dest, $destKey=null, bool $verifix=true): self; + abstract function reset(&$dest, $destKey=null, ?bool $verifix=null): self; /** * Obtenir la liste des clés valides pour les valeurs accessibles via cet @@ -31,33 +31,55 @@ abstract class Value implements ArrayAccess, IteratorAggregate { } } - /** retourner le type associé à la valeur */ - abstract function getType(): IType; + /** + * obtenir le résultat de l'appel d'une des fonctions {@link set()} ou + * {@link unset()} + */ + abstract function getResult(): Result; /** retourner true si la valeur existe */ abstract function isPresent(): bool; + /** retourner le type associé à la valeur */ + abstract function getType(): IType; + /** retourner true si la valeur est disponible */ abstract function isAvailable(): bool; - /** supprimer la valeur */ - abstract function unset(): void; - - /** remplacer la valeur */ - abstract function set($value): self; - - /** obtenir le résultat de l'appel de la fonction {@link set()} */ - abstract function getResult(): Result; - /** retourner true si la valeur est valide */ abstract function isValid(): bool; - /** obtenir la valeur */ - abstract function get($default=null); - /** retourner true si la valeur est dans sa forme normalisée */ abstract function isNormalized(): bool; + /** obtenir la valeur */ + abstract function get($default=null); + + /** remplacer la valeur */ + abstract function set($value): self; + + /** supprimer la valeur */ + abstract function unset(): void; + /** formatter la valeur pour affichage */ abstract function format($format=null): string; + + ############################################################################# + # key & properties + + function offsetExists($offset): bool { + return in_array($offset, $this->getKeys()); + } + + function offsetGet($offset) { + return $this->getValue($offset); + } + + function offsetSet($offset, $value): void { + $this->getValue($offset)->set($value); + } + + function offsetUnset($offset): void { + $this->getValue($offset)->unset(); + } } diff --git a/src/schema/_scalar/ScalarResult.php b/src/schema/_scalar/ScalarResult.php index 388a759..c58279b 100644 --- a/src/schema/_scalar/ScalarResult.php +++ b/src/schema/_scalar/ScalarResult.php @@ -11,8 +11,8 @@ use nur\sery\schema\Result; * * @property bool $present la valeur existe-t-elle? * @property bool $available si la valeur existe, est-elle disponible? - * @property bool $null si la valeur existe, est-elle nulle? - * @property bool $valid si la valeur existe, est-elle valide? + * @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 $message message si la valeur n'est pas valide @@ -105,4 +105,36 @@ class ScalarResult extends Result { return ref_analyze::NULL; } } + + function setInvalid(ScalarSchema $schema): int { + $this->present = true; + $this->available = true; + $this->null = false; + $this->valid = false; + $message = cl::get($schema->messages, "invalid"); + self::replace_key($message, $schema->name); + $this->message = $message; + return ref_analyze::INVALID; + } + + function setValid(): int { + $this->present = true; + $this->available = true; + $this->null = false; + $this->valid = true; + return ref_analyze::VALID; + } + + function setNormalized(): int { + $this->present = true; + $this->available = true; + $this->null = false; + $this->valid = true; + $this->normalized = true; + return ref_analyze::NORMALIZED; + } + + function throw(bool $throw): void { + if ($throw) throw new ValueException($this->message); + } } diff --git a/src/schema/_scalar/ScalarValue.php b/src/schema/_scalar/ScalarValue.php index 4d880fb..84c4355 100644 --- a/src/schema/_scalar/ScalarValue.php +++ b/src/schema/_scalar/ScalarValue.php @@ -3,15 +3,18 @@ namespace nur\sery\schema\_scalar; use nulib\ValueException; use nur\sery\schema\input\Input; +use nur\sery\schema\ref\ref_analyze; use nur\sery\schema\Result; +use nur\sery\schema\types; use nur\sery\schema\types\IType; use nur\sery\schema\Value; class ScalarValue extends Value { - function __construct(ScalarSchema $schema, &$dest=null, $key=null, bool $verifix=true) { + function __construct(ScalarSchema $schema, &$dest=null, $destKey=null, bool $defaultVerifix=true) { $this->schema = $schema; + $this->defaultVerifix = $defaultVerifix; $this->result = new ScalarResult(); - $this->reset($dest, $key, $verifix); + $this->reset($dest, $destKey); } function isScalar(?ScalarValue &$scalar=null): bool { $scalar = $this; return true; } @@ -22,24 +25,27 @@ class ScalarValue extends Value { /** @var Input source et destination de la valeur */ protected $input; - /** @var string|int clé de la valeur dans le tableau destination */ + /** @var string|int|null clé de la valeur dans le tableau destination */ protected $destKey; - /** @var IType */ + /** @var bool */ + protected $defaultVerifix; + + /** @var IType type de la valeur après analyse */ protected $type; /** @var ?ScalarResult résultat de l'analyse de la valeur */ protected $result; - function reset(&$dest, $destKey=null, bool $verifix=true): Value { + function reset(&$dest, $destKey=null, ?bool $verifix=null): Value { if ($dest instanceof Input) $input = $dest; else $input = new Input($dest); $this->input = $input; $this->destKey = $destKey; - $this->result = null; - #XXX résoudre les types ici? + $this->type = null; + $this->_analyze(); + if ($verifix === null) $verifix = $this->defaultVerifix; if ($verifix) $this->verifix(); - else $this->_analyze(); return $this; } @@ -59,42 +65,28 @@ class ScalarValue extends Value { $destKey = $this->destKey; $result = $this->result; $result->reset(); - #XXX résoudre le type - if (!$input->isAvailable()) return $result->setUnavailable($schema); + if (!$input->isPresent($destKey)) return $result->setMissing($schema); + $haveType = false; + $firstType = null; + foreach ($schema->type as $name) { + $type = types::get($name); + if ($firstType === null) $firstType = $type; + if ($type->canAnalyze($input, $destKey)) { + $haveType = true; + $this->type = $type; + break; + } + } + if (!$haveType) $type = $this->type = $firstType; + if (!$type->isAvailable($input, $destKey)) return $result->setUnavailable($schema); $value = $input->get($destKey); - if ($value === null) return $result->setNull($schema); - } - - function isPresent(): bool { - return $this->input->isPresent($this->destKey); - } - - function isAvailable(): bool { - return $this->input->isAvailable($this->destKey); - } - - function get($default=null) { - $destKey = $this->destKey; - $input = $this->input; - if ($input->isAvailable($destKey)) return $input->get($destKey); - else return $default; - } - - function set($value, bool $verifix=true): Value { - $this->input->set($value, $this->destKey); - if ($verifix) $this->verifix(); - return $this; - } - - function getType(): IType { - if ($this->type === null) $this->type = $this->schema->getType($this->destKey); - return $this->type; - } - - function isValid(): bool { - } - - function isNormalized(): bool { + if ($type->isNull($value)) return $result->setNull($schema); + if ($type->isValid($value, $normalized)) { + if ($normalized) return $result->setNormalized(); + else return $result->setValid(); + } + if (is_string($value)) return ref_analyze::STRING; + else return $result->setInvalid($schema); } /** @@ -102,26 +94,61 @@ class ScalarValue extends Value { * * si la valeur était déjà normalisée, retourner false. */ - function verifix(bool $throw=true, ?Result &$result=null): bool { + function verifix(bool $throw=true): bool { $type = $this->getType(); - $key = $this->destKey; - if ($key === null) $modified = $type->verifix($this->input, $throw, $result); - else $modified = $type->verifix($this->input[$key], $throw, $result); - $this->result = $result; + $destKey = $this->destKey; + $value = $this->input->get($destKey); + $modified = $type->verifix($value, $this->result); + if ($this->result->valid) $this->input->set($value, $destKey); + else $this->result->throw($throw); return $modified; } - function parse($value, bool $throw=true, ?Result &$result=null) { - $this->getType()->verifix($value, $throw, $result); - $this->set($value); - $this->result = $result; - return $value; + function getResult(): Result { + return $this->result; } - function format(?string $format=null): string { - $type = $this->getType(); - $key = $this->destKey; - if ($key === null) return $type->format($this->input, $format); - else return $type->format($this->input[$key], $format); + 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->destKey); + else return $default; + } + + function set($value, ?bool $verifix=null): Value { + $this->input->set($value, $this->destKey); + $this->_analyze(); + if ($verifix === null) $verifix = $this->defaultVerifix; + if ($verifix) $this->verifix(); + return $this; + } + + function unset(?bool $verifix=null): void { + $this->input->unset($this->destKey); + $this->_analyze(); + if ($verifix === null) $verifix = $this->defaultVerifix; + if ($verifix) $this->verifix(); + } + + function format($format=null): string { + return $this->getType()->format($this->input->get($this->destKey), $format); } } diff --git a/src/schema/input/Input.php b/src/schema/input/Input.php index bbfb540..2012a55 100644 --- a/src/schema/input/Input.php +++ b/src/schema/input/Input.php @@ -56,4 +56,9 @@ class Input { if ($key === null) $this->dest = $value; else $this->dest[$key] = $value; } + + function unset($key=null): void { + if ($key === null) $this->dest = null; + else unset($this->dest[$key]); + } } diff --git a/src/schema/ref/ref_types.php b/src/schema/ref/ref_types.php index f7e5198..710fee0 100644 --- a/src/schema/ref/ref_types.php +++ b/src/schema/ref/ref_types.php @@ -3,8 +3,8 @@ namespace nur\sery\schema\ref; class ref_types { const ALIASES = [ - "key" => "string|int", - "pkey" => "string|int|array", - "content" => "string|array", + "boolean" => "bool", + "integer" => "int", + "flt" => "float", "double" => "float", "dbl" => "float", ]; } diff --git a/src/schema/types/IType.php b/src/schema/types/IType.php index cb59359..73dc77f 100644 --- a/src/schema/types/IType.php +++ b/src/schema/types/IType.php @@ -1,13 +1,22 @@ tfloat::class, "dbl" => tfloat::class, "array" => tarray::class, "callable" => tcallable::class, + # types spéciaux + "key" => tkey::class, + "pkey" => tpkey::class, + "content" => tcontent::class, ]; function __construct() { diff --git a/src/schema/types/tarray.php b/src/schema/types/tarray.php index b6f1c10..a9fff99 100644 --- a/src/schema/types/tarray.php +++ b/src/schema/types/tarray.php @@ -3,7 +3,7 @@ namespace nur\sery\schema\types; use nulib\cl; -class tarray implements IType { +abstract class tarray implements IType { static function ensure_array(&$array): void { if (!is_array($array)) $array = cl::with($array); } diff --git a/src/schema/types/tbool.php b/src/schema/types/tbool.php index 83cb0ba..19fb060 100644 --- a/src/schema/types/tbool.php +++ b/src/schema/types/tbool.php @@ -1,7 +1,7 @@ ["string", "int"], "nullable" => false]); - self::assertSame($key, ScalarSchema::normalize("key")); - self::assertSame($key, ScalarSchema::normalize("key|string")); + self::assertSame($key, ScalarSchema::normalize("string|int")); $nkey = self::schema(["type" => ["string", "int"], "nullable" => true]); - self::assertSame($nkey, ScalarSchema::normalize("?key")); - self::assertSame($nkey, ScalarSchema::normalize("?key|string")); + self::assertSame($nkey, ScalarSchema::normalize("?string|int")); + self::assertSame($nkey, ScalarSchema::normalize("string|?int")); } } diff --git a/tests/schema/schemaTest.php b/tests/schema/schemaTest.php index f70e479..cd6bc16 100644 --- a/tests/schema/schemaTest.php +++ b/tests/schema/schemaTest.php @@ -9,15 +9,15 @@ class schemaTest extends TestCase { function testInt() { /** @var ScalarValue $intv */ Schema::nv($intv, $int, null, $schema, "int"); - $f = function($value) use($intv) { + $intvSetter = function($value) use($intv) { return function() use($intv, $value) { $intv->set($value); }; }; - self::assertException(Exception::class, $f(null)); - self::assertException(Exception::class, $f("")); - self::assertException(Exception::class, $f(" ")); + self::assertException(Exception::class, $intvSetter(null)); + self::assertException(Exception::class, $intvSetter("")); + self::assertException(Exception::class, $intvSetter(" ")); $intv->set(12); self::assertSame(12, $intv->get()); @@ -31,17 +31,17 @@ class schemaTest extends TestCase { $intv->set(" 12 "); self::assertSame(12, $intv->get()); - self::assertException(Exception::class, $f(true)); - self::assertException(Exception::class, $f(false)); - self::assertException(Exception::class, $f("a")); - self::assertException(Exception::class, $f([])); - self::assertException(Exception::class, $f(["a"])); + self::assertException(Exception::class, $intvSetter(true)); + self::assertException(Exception::class, $intvSetter(false)); + self::assertException(Exception::class, $intvSetter("a")); + self::assertException(Exception::class, $intvSetter([])); + self::assertException(Exception::class, $intvSetter(["a"])); } function testNint() { /** @var ScalarValue $intv */ Schema::nv($intv, $int, null, $schema, "?int"); - $f = function($value) use($intv) { + $intvSetter = function($value) use($intv) { return function() use($intv, $value) { $intv->set($value); }; @@ -74,11 +74,11 @@ class schemaTest extends TestCase { $intv->set(" 12 "); self::assertSame(12, $int); - self::assertException(Exception::class, $f(true)); - self::assertException(Exception::class, $f(false)); - self::assertException(Exception::class, $f("a")); - self::assertException(Exception::class, $f([])); - self::assertException(Exception::class, $f(["a"])); + self::assertException(Exception::class, $intvSetter(true)); + self::assertException(Exception::class, $intvSetter(false)); + self::assertException(Exception::class, $intvSetter("a")); + self::assertException(Exception::class, $intvSetter([])); + self::assertException(Exception::class, $intvSetter(["a"])); } function testUnionTypes() {