diff --git a/.idea/php-test-framework.xml b/.idea/php-test-framework.xml new file mode 100644 index 0000000..bf466ae --- /dev/null +++ b/.idea/php-test-framework.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/schema/AssocSchema.php b/src/schema/AssocSchema.php index af722f6..0c8704b 100644 --- a/src/schema/AssocSchema.php +++ b/src/schema/AssocSchema.php @@ -37,4 +37,8 @@ class AssocSchema extends Schema { if ($normalize) $definition = self::normalize($definition); $this->definition = $definition; } + + function isAssoc(): bool { + return true; + } } diff --git a/src/schema/ListSchema.php b/src/schema/ListSchema.php index 0f9b6aa..879af16 100644 --- a/src/schema/ListSchema.php +++ b/src/schema/ListSchema.php @@ -37,4 +37,8 @@ class ListSchema extends Schema { if ($normalize) $definition = self::normalize($definition); $this->definition = $definition; } + + function isList(): bool { + return true; + } } diff --git a/src/schema/OldSchema.php b/src/schema/OldSchema.php index 29e1b27..65e9dc9 100644 --- a/src/schema/OldSchema.php +++ b/src/schema/OldSchema.php @@ -21,60 +21,6 @@ use nulib\StateException; # format() et [formatter_func] --> formatter la valeur et retourner une chaine class OldSchema { - /** - * créer une nouvelle instance de cette classe à partir d'un schéma - * - * @param bool $normalized indique si le schéma est normalisé - * normalisé - */ - static function new(&$md, $schema, bool $normalized=false): self { - if ($md === null) $md = new static($schema, !$normalized); - return $md; - } - - static function normalize($schema): array { - if ($schema === null) throw new SchemaException("schema is required"); - elseif (!is_array($schema)) $schema = [$schema]; - #XXX - return $schema; - } - - const SCHEMA = null; - - function __construct($schema=null, bool $normalize=true) { - if ($schema === null) $schema = static::SCHEMA; - if ($normalize) $schema = self::normalize($schema); - $this->schema = $schema; - } - - /** - * @var array schéma normalisé - * - * il y a 3 formes pour un schéma: - * - schéma valeur scalaire [0 => type, 1 => default, ...] - * - schéma tableau séquentiel [schema] - * - schéma tableau associatif [key => schema, ...] - */ - protected $schema; - - /** - * retourner true si le schéma est pour une valeur scalaire, false s'il s'agit - * du schéma d'un tableau (séquentiel ou associatif) - */ - function isScalar(): bool { - return array_key_exists(0, $this->schema) && array_key_exists(1, $this->schema); - } - - /** retourner true si le schéma est pour un tableau séquentiel */ - function isSeq(): bool { - return array_key_exists(0, $this->schema) && !array_key_exists(1, $this->schema); - } - - /** retourner true si le schéma est pour un tableau associatif */ - function isAssoc(): bool { - return !array_key_exists(0, $this->schema) && !array_key_exists(1, $this->schema); - } - /** * @var bool true si toutes les clés du schéma doivent exister, avec leur * valeur par défaut le cas échéant diff --git a/src/schema/ScalarSchema.php b/src/schema/ScalarSchema.php index 5a1f221..21dc011 100644 --- a/src/schema/ScalarSchema.php +++ b/src/schema/ScalarSchema.php @@ -1,9 +1,44 @@ 0, "default" => 1, "title" => 2, "required" => 3, "nullable" => 4, "desc" => 5, + "analyzer_func" => 6, "extractor_func" => 7, "parser_func" => 8, "normalizer_func" => 9, "messages" => 10, + "formatter_func" => 11, "format" => 12, + "" => 13, "name" => 14, + ]; /** * indiquer si $definition est une définition de schéma scalaire que @@ -37,7 +72,82 @@ class ScalarSchema extends Schema { } static function normalize($definition): array { - + if (!is_array($definition)) $definition = [$definition]; + # s'assurer que toutes les clés existent avec leur valeur par défaut + $index = 0; + foreach (self::METASCHEMA_KEYS as $key) { + if (!array_key_exists($key, $definition)) { + if (array_key_exists($index, $definition)) { + $definition[$key] = $definition[$index]; + unset($definition[$index]); + $index++; + } else { + $definition[$key] = self::METASCHEMA[$key][1]; + } + } + } + # réordonner les clés numériques + if (cl::have_num_keys($definition)) { + $keys = array_keys($definition); + $index = 0; + foreach ($keys as $key) { + if (!is_int($key)) continue; + $definition[$index] = $definition[$key]; + unset($definition[$key]); + $index++; + } + } + # type + $types = []; + $deftype = $definition["type"]; + $nullable = $definition["nullable"]; + if ($deftype === null) { + $types[] = null; + $nullable = true; + } else { + if (!is_array($deftype)) { + if (!is_string($deftype)) throw SchemaException::invalid_type($deftype); + $deftype = explode("|", $deftype); + } + foreach ($deftype as $type) { + if ($type === null || $type === "null") { + $nullable = true; + continue; + } + if (!is_string($type)) throw SchemaException::invalid_type($type); + if (substr($type, 0, 1) == "?") { + $type = substr($type, 1); + $nullable = true; + } + if ($type === "") throw SchemaException::invalid_type($type); + $type = cl::get(ref_types::ALIASES, $type, $type); + $types = array_merge($types, explode("|", $type)); + } + if (!$types) throw SchemaException::invalid_schema("scalar: type is required"); + $types = array_keys(array_fill_keys($types, true)); + } + $definition["type"] = $types; + $definition["nullable"] = $nullable; + # nature + $nature = $definition[""]; + self::ensure_array($nature); + if (!array_key_exists(0, $nature) || $nature[0] !== "scalar") { + throw SchemaException::invalid_schema("expected scalar nature"); + } + $definition[""] = $nature; + # autres éléments + self::ensure_nstring($definition["title"]); + self::ensure_bool($definition["required"]); + self::ensure_bool($definition["nullable"]); + self::ensure_ncontent($definition["desc"]); + self::ensure_ncallable($definition["analyzer_func"]); + self::ensure_ncallable($definition["extractor_func"]); + self::ensure_ncallable($definition["parser_func"]); + self::ensure_ncallable($definition["normalizer_func"]); + self::ensure_narray($definition["messages"]); + self::ensure_ncallable($definition["formatter_func"]); + self::ensure_nkey($definition["name"]); + return $definition; } function __construct($definition=null, bool $normalize=true) { @@ -45,4 +155,8 @@ class ScalarSchema extends Schema { if ($normalize) $definition = self::normalize($definition); $this->definition = $definition; } + + function isScalar(): bool { + return true; + } } diff --git a/src/schema/Schema.php b/src/schema/Schema.php index 5519a1d..587c52f 100644 --- a/src/schema/Schema.php +++ b/src/schema/Schema.php @@ -1,6 +1,8 @@ ["string", null, "" => 0, - "required" => true, - "desc" => "type de la valeur", - ], - "default" => [null, null, "" => 1, - "desc" => "valeur par défaut", - ], - "key" => ["?key", null, "" => "", - "desc" => "clé de la valeur si elle est dans un tableau", - ], - "required" => ["bool", false, - "desc" => "cette valeur est-elle requise?", - ], - "formatter" => ["?callable", null, - "desc" => "signature de la fonction: (value, format, type) => string", - ], - "parser" => ["?callable", null, - "desc" => "signature de la fonction: (value, type) => [value, result]", - ], - "messages" => ["?array", null, - "desc" => "message à afficher en cas d'erreur d'analyse", - ], - ]; - - const MESSAGES = [ - "absent" => "{key}: Vous devez spécifier cette valeur", - "null" => "{key}: Cette valeur ne doit pas être nulle", - "empty" => "{key}: Cette valeur ne doit pas être vide", - "invalid" => "{key}: {orig}: cette valeur est invalide", - ]; - /** @var array schéma des natures de schéma */ - public const NATURE_METASCHEMA = [ + const NATURE_METASCHEMA = [ "nature" => ["string", null, "nature du schéma", "key" => 0, "allowed_values" => ["assoc", "list", "scalar"], @@ -49,7 +17,7 @@ class ref_schema { ]; /** @var array meta-schema d'un schéma de nature scalaire */ - public const SCALAR_METASCHEMA = [ + const SCALAR_METASCHEMA = [ "type" => ["array", null, "types possibles de la valeur", "required" => true], "default" => [null, null, "valeur par défaut si la valeur n'existe pas"], "title" => ["?string", null, "libellé de la valeur"], @@ -63,7 +31,16 @@ class ref_schema { "messages" => ["?array", null, "messages à afficher en cas d'erreur d'analyse"], "formatter_func" => ["?callable", null, "fonction qui formatte la valeur pour affichage"], "format" => [null, null, "format à utiliser pour l'affichage"], - "" => ["" => ["assoc", "schema" => self::NATURE_METASCHEMA]], + "" => ["array", "scalar", "nature du schéma", + "" => ["assoc", "schema" => self::NATURE_METASCHEMA], + ], "name" => ["?key", null, "identifiant de la valeur"], ]; + + const MESSAGES = [ + "absent" => "{key}: Vous devez spécifier cette valeur", + "null" => "{key}: Cette valeur ne doit pas être nulle", + "empty" => "{key}: Cette valeur ne doit pas être vide", + "invalid" => "{key}: {orig}: cette valeur est invalide", + ]; } diff --git a/src/schema/ref/ref_types.php b/src/schema/ref/ref_types.php new file mode 100644 index 0000000..a3733b5 --- /dev/null +++ b/src/schema/ref/ref_types.php @@ -0,0 +1,9 @@ + "string|int", + "content" => "string|array", + ]; +} diff --git a/tests/schema/ScalarSchemaTest.php b/tests/schema/ScalarSchemaTest.php new file mode 100644 index 0000000..40cdd2a --- /dev/null +++ b/tests/schema/ScalarSchemaTest.php @@ -0,0 +1,60 @@ + [null], + "default" => null, + "title" => null, + "required" => false, + "nullable" => true, + "desc" => null, + "analyzer_func" => null, + "extractor_func" => null, + "parser_func" => null, + "normalizer_func" => null, + "messages" => null, + "formatter_func" => null, + "format" => null, + "" => ["scalar"], + "name" => null, + ]; + + static function schema(array $schema): array { + return array_merge(self::NULL_SCHEMA, $schema); + } + + function testNormalize() { + self::assertSame(self::NULL_SCHEMA, ScalarSchema::normalize(null)); + self::assertSame(self::NULL_SCHEMA, ScalarSchema::normalize([])); + self::assertSame(self::NULL_SCHEMA, ScalarSchema::normalize([null])); + self::assertException(SchemaException::class, function () { + ScalarSchema::normalize([[]]); + }); + self::assertException(SchemaException::class, function () { + ScalarSchema::normalize([[null]]); + }); + + $string = self::schema(["type" => ["string"], "nullable" => false]); + self::assertSame($string, ScalarSchema::normalize("string")); + self::assertSame($string, ScalarSchema::normalize(["string"])); + + $nstring = self::schema(["type" => ["string"]]); + self::assertSame($nstring, ScalarSchema::normalize(["?string"])); + self::assertSame($nstring, ScalarSchema::normalize(["?string|null"])); + self::assertSame($nstring, ScalarSchema::normalize(["string|null"])); + self::assertSame($nstring, ScalarSchema::normalize([["?string", "null"]])); + self::assertSame($nstring, ScalarSchema::normalize([["string", "null"]])); + self::assertSame($nstring, ScalarSchema::normalize([["string", null]])); + + $key = self::schema(["type" => ["string", "int"], "nullable" => false]); + self::assertSame($key, ScalarSchema::normalize("key")); + self::assertSame($key, ScalarSchema::normalize("key|string")); + + $nkey = self::schema(["type" => ["string", "int"], "nullable" => true]); + self::assertSame($nkey, ScalarSchema::normalize("?key")); + self::assertSame($nkey, ScalarSchema::normalize("?key|string")); + } +}