diff --git a/src/schema/TODO.md b/src/schema/TODO.md index 5c1bf19..6db1a74 100644 --- a/src/schema/TODO.md +++ b/src/schema/TODO.md @@ -1,5 +1,7 @@ # nulib\schema +* tenir compte de la valeur par défaut, qui *doit* être du bon type +* type vaut soit une instance de IType, soit une tableau de types * dans la définition, `[type]` est remplacé par l'instance de IType lors de sa résolution? --> NON, sauf si c'est un type union * newInput dans Schema diff --git a/src/schema/_scalar/ScalarSchema.php b/src/schema/_scalar/ScalarSchema.php index 2aeeca7..4a0ab60 100644 --- a/src/schema/_scalar/ScalarSchema.php +++ b/src/schema/_scalar/ScalarSchema.php @@ -6,6 +6,7 @@ use nulib\ref\schema\ref_schema; use nulib\ref\schema\ref_types; use nur\sery\wip\schema\Schema; use nur\sery\wip\schema\SchemaException; +use nur\sery\wip\schema\types; use nur\sery\wip\schema\types\tarray; use nur\sery\wip\schema\types\tbool; use nur\sery\wip\schema\types\tcallable; @@ -103,7 +104,7 @@ class ScalarSchema extends Schema { # type $types = []; $deftype = $definition["type"]; - $nullable = $definition["nullable"]; + $nullable = $definition["nullable"] ?? false; if ($deftype === null) { $types[] = null; $nullable = true; @@ -164,6 +165,20 @@ class ScalarSchema extends Schema { tarray::ensure_narray($definition["messages"]); tcallable::ensure_ncallable($definition["formatter_func"]); tbool::ensure_nbool($definition["composite"]); + + # s'il n'y a qu'une seul type, l'instancier tout de suite + if (count($types) == 1 && $types[0] !== null) { + foreach ($types as $key => $name) { + if ($key === 0) { + $params = null; + } else { + $params = $name; + $name = $key; + } + $definition["type"] = [types::get($nullable, $name, $params, $definition)]; + } + } + return $definition; } diff --git a/src/schema/_scalar/ScalarWrapper.php b/src/schema/_scalar/ScalarWrapper.php index 13c3d9f..2427e8b 100644 --- a/src/schema/_scalar/ScalarWrapper.php +++ b/src/schema/_scalar/ScalarWrapper.php @@ -71,57 +71,65 @@ class ScalarWrapper extends Wrapper { /** analyser la valeur et résoudre son type */ function _analyze(AnalyzerContext $context): int { - $schema = $this->schema; - $input = $this->input; - $destKey = $this->destKey; - $result = $this->result; + /** @var ScalarSchema $schema */ + $schema = $context->schema; + $input = $context->input; + $destKey = $context->destKey; + /** @var ScalarResult $result */ + $result = $context->result; if (!$input->isPresent($destKey)) return $result->setMissing($schema); - $haveType = false; - $types = []; - $type = $firstType = null; - $haveValue = false; - $value = null; - # d'abord chercher un type pour lequel c'est une valeur normalisée - $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)) { - if (!$haveValue) { - $value = $input->get($destKey); - $haveValue = true; + $schemaTypes = $schema->type; + if (count($schemaTypes) == 1 && $schemaTypes[0] instanceof IType) { + $type = $schemaTypes[0]; + } 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; } - if ($type->isValid($value, $normalized) && $normalized) { - $haveType = true; - $this->type = $type; - break; + $type = types::get($schema->nullable, $name, $params, $this->schema->getDefinition()); + if ($firstType === null) $firstType = $type; + $types[] = $type; + if ($type->isAvailable($input, $destKey)) { + if (!$haveValue) { + $value = $input->get($destKey); + $haveValue = true; + } + if ($type->isValid($value, $normalized) && $normalized) { + $haveType = true; + break; + } } } - } - if (!$haveType) { # ensuite chercher un type pour lequel la valeur est valide - foreach ($types as $type) { - if ($type->isAvailable($input, $destKey) && $type->isValid($value)) { - $haveType = true; - $this->type = $type; - break; + if (!$haveType) { + foreach ($types as $type) { + if ($type->isAvailable($input, $destKey) && $type->isValid($value)) { + $haveType = true; + break; + } } } + # sinon prendre le premier type + if (!$haveType) { + $type = $firstType; + } } + $context->type = $this->type = $type; - # sinon prendre le premier type - if (!$haveType) $type = $this->type = $firstType; - $context->type = $type; if (!$type->isAvailable($input, $destKey)) return $result->setUnavailable($schema); $value = $input->get($destKey); diff --git a/src/schema/types.php b/src/schema/types.php index 96b703d..f02a696 100644 --- a/src/schema/types.php +++ b/src/schema/types.php @@ -25,16 +25,16 @@ class types { return self::$registry; } - static function get(string $name, ?array $params=null, ?array $definition=null): IType { - return self::registry()->get($name, $params, $definition); + static function get(bool $nullable, string $name, ?array $params=null, ?array $definition=null): IType { + return self::registry()->get($nullable, $name, $params, $definition); } - static function rawstring(): trawstring { return self::get("rawstring"); } - static function string(): tstring { return self::get("string"); } - static function text(): ttext { return self::get("text"); } - static function bool(): tbool { return self::get("bool"); } - static function int(): tint { return self::get("int"); } - static function float(): tfloat { return self::get("float"); } - static function array(): tarray { return self::get("array"); } - static function callable(): tcallable { return self::get("callable"); } + static function rawstring(bool $nullable=true): trawstring { return self::get($nullable, "rawstring"); } + static function string(bool $nullable=true): tstring { return self::get($nullable, "string"); } + static function text(bool $nullable=true): ttext { return self::get($nullable, "text"); } + static function bool(bool $nullable=true): tbool { return self::get($nullable, "bool"); } + static function int(bool $nullable=true): tint { return self::get($nullable, "int"); } + static function float(bool $nullable=true): tfloat { return self::get($nullable, "float"); } + static function array(bool $nullable=true): tarray { return self::get($nullable, "array"); } + static function callable(bool $nullable=true): tcallable { return self::get($nullable, "callable"); } } diff --git a/src/schema/types/IType.php b/src/schema/types/IType.php index f0e564f..2518248 100644 --- a/src/schema/types/IType.php +++ b/src/schema/types/IType.php @@ -10,6 +10,54 @@ use nur\sery\wip\schema\Schema; * Interface IType: un type de données */ interface IType { + /** + * @return string la classe des objets gérés par ce format: le type attendu + * par {@link format()} et le type retourné par {@link verifix()} + * + * Les valeurs "mixed", "bool", "float", "int", "string" et "array" peuvent + * aussi être retournées, bien qu'elles ne soient pas à proprement parler des + * classes. + * + * La valeur "mixed" signifie qu'il peut s'agir de n'importe quelle classe + * et/ou que la valeur ne peut pas être déterminée à l'avance. + */ + function getClass(): string; + + /** + * comme {@link getClass()} mais peut être utilisé directement comme type dans + * une déclaration PHP. notamment, si le type est nullable et que + * $allowNullable==true, il y a "?" devant le nom. + * + * Par exemple: + * - pour un type chaine nullable, {@link getClass()} retournerait "string" + * alors que cette méthode retournerait "?string". + * - pour un type mixed, {@link getClass()} retournerait "mixed" alors que + * cette méthode retournerait null + */ + function getPhpType(bool $allowNullable=true): ?string; + + /** + * indiquer si c'est le type d'une valeur qui ne peut prendre que 2 états: une + * "vraie" et une "fausse" + */ + function is2States(): bool; + + /** + * Si {@link is2States()} est vrai, retourner les deux valeurs [faux, vrai] + */ + function get2States(): array; + + /** + * indiquer si c'est le type d'une valeur qui ne peut prendre que 3 états: une + * "vraie", une "fausse", et une "indéterminée" + */ + function is3States(): bool; + + /** + * Si {@link is3States()} est vrai, retourner les 3 valeurs [faux, vrai, undef] + */ + function get3States(): array; + /** la donnée $input($destKey) est-elle disponible? */ function isAvailable(Input $input, $destKey): bool; @@ -54,4 +102,29 @@ interface IType { * garantie d'être du bon type */ function format($value, $format=null): string; + + ############################################################################# + + /** @return string le nom d'un getter pour une valeur de ce type */ + function getGetterName(string $name): string; + + /** @return string le nom d'un setter pour une valeur de ce type */ + function getSetterName(string $name): string; + + /** @return string le nom d'un deleter pour une valeur de ce type */ + function getDeleterName(string $name): string; + + /** + * @return string le nom d'une constante de classe pour une valeur de ce type + */ + function getClassConstName(string $name): string; + + /** + * @return string le nom d'une propriété d'une classe pour une valeur de ce + * type + */ + function getObjectPropertyName(string $name): string; + + /** @return string le nom d'une clé d'un tableau pour une valeur de ce type */ + function getArrayKeyName(string $name): string; } diff --git a/src/schema/types/Registry.php b/src/schema/types/Registry.php index 2879f8b..ac64560 100644 --- a/src/schema/types/Registry.php +++ b/src/schema/types/Registry.php @@ -28,14 +28,15 @@ class Registry { /** @var IType[] */ protected $types; - function get(string $name, ?array $params=null, ?array $definition=null): IType { + function get(bool $nullable, string $name, ?array $params=null, ?array $definition=null): IType { $class = self::TYPES[$name]; $params = cl::merge($class::get_params_from_definition($definition), $params); if ($params !== null) { - return func::with([$class, false, $params])->invoke(); + return func::with([$class, false, $nullable, $params])->invoke(); } + if ($nullable) $name = "?$name"; $type = cl::get($this->types, $name); - if ($type === null) $type = $this->types[$name] = new $class(); + if ($type === null) $type = $this->types[$name] = new $class($nullable); return $type; } } diff --git a/src/schema/types/_tsimple.php b/src/schema/types/_tsimple.php index 91ded5d..957f0aa 100644 --- a/src/schema/types/_tsimple.php +++ b/src/schema/types/_tsimple.php @@ -1,19 +1,44 @@ nullable = $nullable; $this->params = $params; } + protected bool $nullable; + protected ?array $params; + function getPhpType(bool $allowNullable=true): ?string { + $phpType = $this->getClass(); + if ($phpType === "mixed") return null; + if ($this->nullable && $allowNullable) $phpType = "?$phpType"; + return $phpType; + } + + function get2States(): array { + throw StateException::not_implemented(); + } + + function is3States(): bool { + return false; + } + + function get3States(): array { + throw StateException::not_implemented(); + } + function isAvailable(Input $input, $destKey): bool { return $input->isAvailable($destKey) && $input->get($destKey) !== false; } @@ -25,4 +50,34 @@ abstract class _tsimple implements IType { function extract(string $value): string { return $value; } + + function is2States(): bool { + return false; + } + + ############################################################################# + + function getGetterName(string $name): string { + return prop::get_getter_name($name); + } + + function getSetterName(string $name): string { + return prop::get_setter_name($name); + } + + function getDeleterName(string $name): string { + return prop::get_deletter_name($name); + } + + function getClassConstName(string $name): string { + return strtoupper($name); + } + + function getObjectPropertyName(string $name): string { + return str::us2camel($name); + } + + function getArrayKeyName(string $name): string { + return $name; + } } diff --git a/src/schema/types/_tunion.php b/src/schema/types/_tunion.php new file mode 100644 index 0000000..113e02d --- /dev/null +++ b/src/schema/types/_tunion.php @@ -0,0 +1,10 @@ +nullable; + } + + function get2States(): array { + return [false, true]; + } + + function is3States(): bool { + return $this->nullable; + } + + function get3States(): array { + return [false, true, null]; + } + function isAvailable(Input $input, $destKey): bool { return $input->isAvailable($destKey); } @@ -124,4 +145,8 @@ class tbool extends _tformatable { } return $value? $format[0]: $format[1]; } + + function getGetterName(string $name): string { + return prop::get_getter_name($name, !$this->nullable); + } } diff --git a/src/schema/types/tcallable.php b/src/schema/types/tcallable.php index f5ec37c..a36032c 100644 --- a/src/schema/types/tcallable.php +++ b/src/schema/types/tcallable.php @@ -18,6 +18,10 @@ class tcallable extends _tsimple { if ($callable !== null) self::ensure_callable($callable); } + function getClass(): string { + return func::class; + } + function isValid($value, ?bool &$normalized=null): bool { $normalized = is_callable($value); return func::check($value); diff --git a/src/schema/types/tcontent.php b/src/schema/types/tcontent.php index 0a950ca..aff7d17 100644 --- a/src/schema/types/tcontent.php +++ b/src/schema/types/tcontent.php @@ -7,7 +7,7 @@ use nur\sery\wip\schema\_scalar\ScalarSchema; use nur\sery\wip\schema\Result; use nur\sery\wip\schema\Schema; -abstract class tcontent extends _tsimple { +abstract class tcontent extends _tunion { static function ensure_content(&$content): void { if ($content === null || $content === false) $content = []; elseif (!is_string($content) && !is_array($content)) $content = strval($content); @@ -17,6 +17,10 @@ abstract class tcontent extends _tsimple { if ($content !== null) self::ensure_content($content); } + function getClass(): string { + return "string|array"; + } + function isValid($value, ?bool &$normalized=null): bool { $normalized = is_string($value) || is_array($value); return is_scalar($value) || is_array($value); diff --git a/src/schema/types/tfloat.php b/src/schema/types/tfloat.php index 47f3956..486187e 100644 --- a/src/schema/types/tfloat.php +++ b/src/schema/types/tfloat.php @@ -16,6 +16,10 @@ class tfloat extends _tformatable { if ($float !== null) self::ensure_float($float); } + function getClass(): string { + return "float"; + } + function isValid($value, ?bool &$normalized=null): bool { $normalized = is_float($value); return is_scalar($value); diff --git a/src/schema/types/tint.php b/src/schema/types/tint.php index 51dbeec..33ae3ac 100644 --- a/src/schema/types/tint.php +++ b/src/schema/types/tint.php @@ -17,7 +17,11 @@ class tint extends _tformatable { } //const INT_PATTERN = '/^[-+]?[0-9]+(?:\.[0-9]*)?$/'; - + + function getClass(): string { + return "int"; + } + function isValid($value, ?bool &$normalized=null): bool { $normalized = is_int($value); return is_scalar($value); diff --git a/src/schema/types/tkey.php b/src/schema/types/tkey.php index ff10afb..102beee 100644 --- a/src/schema/types/tkey.php +++ b/src/schema/types/tkey.php @@ -6,7 +6,7 @@ use nur\sery\wip\schema\_scalar\ScalarSchema; use nur\sery\wip\schema\Result; use nur\sery\wip\schema\Schema; -class tkey extends _tsimple { +class tkey extends _tunion { static function ensure_key(&$key): void { if ($key === null) $key = ""; elseif ($key === false) $key = 0; @@ -17,6 +17,10 @@ class tkey extends _tsimple { if ($key !== null) self::ensure_key($key); } + function getClass(): string { + return "string|int"; + } + function isValid($value, ?bool &$normalized=null): bool { $normalized = is_string($value) || is_int($value); return is_scalar($value); diff --git a/src/schema/types/tpkey.php b/src/schema/types/tpkey.php index 3766b04..1606087 100644 --- a/src/schema/types/tpkey.php +++ b/src/schema/types/tpkey.php @@ -6,7 +6,7 @@ use nur\sery\wip\schema\_scalar\ScalarSchema; use nur\sery\wip\schema\Result; use nur\sery\wip\schema\Schema; -class tpkey extends _tsimple { +class tpkey extends _tunion { static function ensure_pkey(&$pkey): void { if ($pkey === null) $pkey = ""; elseif ($pkey === false) $pkey = 0; @@ -22,6 +22,10 @@ class tpkey extends _tsimple { if ($pkey !== null) self::ensure_pkey($pkey); } + function getClass(): string { + return "string|int|array"; + } + function isValid($value, ?bool &$normalized=null): bool { $normalized = is_string($value) || is_int($value) || is_array($value); return is_scalar($value) || is_array($value); diff --git a/src/schema/types/trawstring.php b/src/schema/types/trawstring.php index f36bdd1..2b60bc0 100644 --- a/src/schema/types/trawstring.php +++ b/src/schema/types/trawstring.php @@ -18,6 +18,10 @@ class trawstring extends _tstring { if ($string !== null) self::ensure_string($string); } + function getClass(): string { + return "string"; + } + function isNull($value): bool { return $value === null; }