gestion simplifiée des schéma

This commit is contained in:
Jephté Clain 2025-10-22 17:42:08 +04:00
parent efb4f037ec
commit 5e41e7d5e0
13 changed files with 381 additions and 13 deletions

View File

@ -0,0 +1,58 @@
<?php
namespace nulib\php\types;
use nulib\exceptions;
use nulib\str;
class _schema {
/** @var array association type simple / vclass */
const VCLASSES = [
"rawstring" => vrawstring::class,
"string" => vstring::class,
"text" => vtext::class,
"bool" => vbool::class,
"int" => vint::class,
"float" => vfloat::class,
"array" => varray::class,
"func" => vfunc::class,
"raw" => vraw::class,
"mixed" => vmixed::class,
"key" => vkey::class,
"pkey" => vpkey::class,
"content" => vcontent::class,
"datetime" => vdatetime::class,
"date" => vdate::class,
"time" => vtime::class,
];
static function get_types($schema, &$default = null, ?bool &$required = null): array {
if (is_array($schema)) {
$types = $schema["type"] ?? $schema[0] ?? null;
$default = $schema["default"] ?? $schema[1] ?? null;
$required = vbool::with($schema["required"] ?? false);
} elseif (is_string($schema)) {
$types = $schema;
$default = null;
$required = false;
} else {
throw exceptions::invalid_value($schema, "schema");
}
if (is_string($types)) {
$types = explode(",", $types);
} elseif (!is_array($types)) {
throw exceptions::invalid_value($types, "types");
}
return $types;
}
static function get_vclass(array $types, ?bool &$nullable): ?string {
foreach ($types as $type) {
$vclass = self::VCLASSES[$type] ?? null;
if ($vclass !== null) {
$nullable = str::del_prefix($type, "?");
return $vclass;
}
}
return null;
}
}

View File

@ -0,0 +1,75 @@
<?php
namespace nulib\php\types;
use nulib\exceptions;
/**
* Class assoc: gestion simplifiée de schémas associatifs
*
* seuls les types simples sont reconnus
*/
class assoc {
/** indiquer si $array est conforme au schéma */
public static function check_schema(?array $array, array $schema, bool $strict = false): bool {
foreach ($schema as $key => $kschema) {
$types = _schema::get_types($kschema, $default, $required);
$exists = array_key_exists($key, $array);
if (!$exists) {
if ($required) return false;
else continue;
}
$vclass = _schema::get_vclass($types, $nullable);
# le test échoue si le type n'est pas supporté
if ($vclass === null) return false;
$value = $array[$key];
if ($value === null) {
if (!$nullable) return false;
} else {
if (!$vclass::isa($value, $strict)) return false;
}
}
return true;
}
/**
* s'assurer que $array est conforme au schéma
* - les clés ne sont pas créées si elles n'existent pas, sauf si la valeur
* est requise ou si une valeur par défaut non nulle est fournie
*/
public static function ensure_schema(&$array, array $schema): bool {
$ensured = true;
foreach ($schema as $key => $kschema) {
$types = _schema::get_types($kschema, $default, $required);
$exists = array_key_exists($key, $array);
if (!$exists) {
if ($required) {
if ($default !== null) {
$array[$key] = $default;
} else {
throw exceptions::missing_value(null, "array", "$key est obligatoire");
}
} elseif ($default !== null) {
$array[$key] = $default;
}
continue;
}
$vclass = _schema::get_vclass($types, $nullable);
# le test échoue si le type n'est pas supporté
if ($vclass === null) {
$ensured = false;
continue;
}
$value = $array[$key];
if ($nullable) {
$array[$key] = $vclass::withn($value);
} else {
if ($value === null) $value = $default;
$array[$key] = $vclass::with($value);
}
}
return $ensured;
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace nulib\php\types;
use nulib\exceptions;
use nulib\str;
/**
* Class scalar: gestion simplifiée de schémas de valeurs simples
*/
class scalar {
/** indiquer si $value est conforme au schéma */
static function check_schema($value, $schema, bool $strict=false): bool {
$types = _schema::get_types($schema);
$vclass = _schema::get_vclass($types, $nullable);
# le test échoue si le type n'est pas supporté
if ($vclass === null) return false;
if ($value === null) return $nullable;
return $vclass::isa($value, $strict);
}
static function ensure_schema(&$value, $schema): bool {
$types = _schema::get_types($schema, $default);
$vclass = _schema::get_vclass($types, $nullable);
# la conversion échoue si le type n'est pas supporté
if ($vclass === null) return false;
if ($nullable) {
$value = $vclass::withn($value);
} else {
if ($value === null) $value = $default;
$value = $vclass::with($value);
}
return true;
}
}

View File

@ -2,6 +2,7 @@
namespace nulib\php\types;
use nulib\cl;
use nulib\exceptions;
class varray {
static function isa($value, bool $strict=false) : bool {
@ -10,7 +11,10 @@ class varray {
}
static function ensure(&$array): void {
if (!is_array($array)) $array = cl::with($array);
if (is_array($array)) return;
if ($array === null || $array === false) $array = [];
elseif (is_scalar($array)) $array = cl::with($array);
else throw exceptions::invalid_value($array, "array");
}
static function ensuren(&$array): void {

View File

@ -1,6 +1,8 @@
<?php
namespace nulib\php\types;
use nulib\exceptions;
class vbool {
/** liste de valeurs chaines à considérer comme 'OUI' */
public const YES_VALUES = [
@ -37,11 +39,18 @@ class vbool {
}
static function ensure(&$bool): void {
if (is_string($bool)) {
if (is_bool($bool)) return;
if ($bool === null) {
$bool = false;
} elseif (is_string($bool)) {
if (self::is_yes($bool)) $bool = true;
elseif (self::is_no($bool)) $bool = false;
else throw exceptions::invalid_value($bool, "boolean");
} elseif (is_scalar($bool)) {
$bool = boolval($bool);
} else {
throw exceptions::invalid_value($bool, "boolean");
}
if (!is_bool($bool)) $bool = boolval($bool);
}
static function ensuren(&$bool): void {

View File

@ -1,15 +1,28 @@
<?php
namespace nulib\php\types;
use nulib\exceptions;
use nulib\php\content\IContent;
use nulib\php\content\IPrintable;
class vcontent {
static function isa($value, bool $strict=false) : bool {
if ($strict) return is_array($value) || is_string($value);
else return is_array($value) || is_scalar($value);
return is_array($value) || is_scalar($value) ||
$value instanceof IContent || $value instanceof IPrintable;
}
static function ensure(&$content): void {
if ($content === null || $content === false) $content = [];
elseif (!is_string($content) && !is_array($content)) $content = strval($content);
if (is_array($content) || is_string($content)) return;
if ($content === null || $content === false) {
$content = [];
} elseif (is_scalar($content)) {
$content = [$content];
} elseif ($content instanceof IContent || $content instanceof IPrintable) {
$content = [$content];
} else {
throw exceptions::invalid_value($content, "content");
}
}
static function ensuren(&$content): void {

View File

@ -1,6 +1,8 @@
<?php
namespace nulib\php\types;
use nulib\exceptions;
class vfloat {
static function isa($value, bool $strict=false) : bool {
if ($strict) return is_float($value);
@ -8,7 +10,10 @@ class vfloat {
}
static function ensure(&$float): void {
if (!is_float($float)) $float = floatval($float);
if (is_float($float)) return;
if ($float === null || $float === false) $float = 0.0;
elseif (is_numeric($float)) $float = floatval($float);
else throw exceptions::invalid_value($float, "float");
}
static function ensuren(&$float): void {

View File

@ -1,6 +1,8 @@
<?php
namespace nulib\php\types;
use nulib\exceptions;
class vint {
static function isa($value, bool $strict=false) : bool {
if ($strict) return is_int($value);
@ -8,7 +10,10 @@ class vint {
}
static function ensure(&$int): void {
if (!is_int($int)) $int = intval($int);
if (is_int($int)) return;
if ($int === null || $int === false) $int = 0.0;
elseif (is_numeric($int)) $int = intval($int);
else throw exceptions::invalid_value($int, "int");
}
static function ensuren(&$int): void {

View File

@ -1,6 +1,8 @@
<?php
namespace nulib\php\types;
use nulib\exceptions;
class vkey {
static function isa($value, bool $strict=false) : bool {
if ($strict) return is_int($value) || is_string($value);
@ -8,9 +10,11 @@ class vkey {
}
static function ensure(&$key): void {
if (is_int($key) || is_string($key)) return;
if ($key === null) $key = "";
elseif ($key === false) $key = 0;
elseif (!is_string($key) && !is_int($key)) $key = strval($key);
elseif (is_scalar($key)) $key = strval($key);
else throw exceptions::invalid_value($key, "key");
}
static function ensuren(&$key): void {

View File

@ -1,6 +1,8 @@
<?php
namespace nulib\php\types;
use nulib\exceptions;
class vpkey {
static function isa($value, bool $strict=false) : bool {
if ($strict) return is_array($value) || is_int($value) || is_string($value);
@ -8,15 +10,19 @@ class vpkey {
}
static function ensure(&$pkey): void {
if ($pkey === null) $pkey = "";
elseif ($pkey === false) $pkey = 0;
elseif (!is_string($pkey) && !is_int($pkey) && !is_array($pkey)) $pkey = strval($pkey);
if (is_array($pkey)) {
foreach ($pkey as &$key) {
vkey::ensure($key);
};
unset($key);
return;
} elseif (is_int($pkey) || is_string($pkey)) {
return;
}
if ($pkey === null) $pkey = "";
elseif ($pkey === false) $pkey = 0;
elseif (is_scalar($pkey)) $pkey = strval($pkey);
else throw exceptions::invalid_value($pkey, "pkey");
}
static function ensuren(&$pkey): void {

View File

@ -1,6 +1,7 @@
<?php
namespace nulib\php\types;
use nulib\exceptions;
use nulib\str;
class vrawstring {
@ -15,7 +16,13 @@ class vrawstring {
}
static function ensure(&$string): void {
if (!is_string($string)) $string = strval($string);
if (!is_string($string)) {
if (is_scalar($string)) {
$string = strval($string);
} else {
throw exceptions::invalid_value($string, "string");
}
}
if (static::TRIM) $string = trim($string);
if (static::NORM_NL) $string = str::norm_nl($string);
}

View File

@ -0,0 +1,88 @@
<?php
namespace nulib\php\types;
use nulib\tests\TestCase;
use nulib\ValueException;
class assocTest extends TestCase {
static function assertEnsureOk(array $schema, ?array $input, ?array $output) {
assoc::ensure_schema($input, $schema);
self::assertSame($output, $input);
}
static function assertEnsureFail(array $schema, ?array $input, ?array $output) {
self::assertException(ValueException::class, function() use ($input, $schema) {
assoc::ensure_schema($input, $schema);
});
}
function testEnsureSchema() {
self::assertEnsureOk([
"string" => "string",
"int" => "int",
], [
"string" => 5,
"int" => "42",
], [
"string" => "5",
"int" => 42,
]);
}
const SCHEMA_S = ["s" => "string"];
const SCHEMA_SD = ["s" => ["string", null]];
const SCHEMA_SND = ["s" => ["string", "not null"]];
const SCHEMA_RS = ["s" => ["string", "required" => true]];
const SCHEMA_RSD = ["s" => ["string", null, "required" => true]];
const SCHEMA_RSND = ["s" => ["string", "not null", "required" => true]];
const SCHEMA_NS = ["s" => "?string"];
const SCHEMA_NSD = ["s" => ["?string", null]];
const SCHEMA_NSND = ["s" => ["?string", "not null"]];
const SCHEMA_NRS = ["s" => ["?string", "required" => true]];
const SCHEMA_NRSD = ["s" => ["?string", null, "required" => true]];
const SCHEMA_NRSND = ["s" => ["?string", "not null", "required" => true]];
function testEnsureSchema2() {
self::assertEnsureOk(self::SCHEMA_S, [], []);
self::assertEnsureFail(self::SCHEMA_S, ["s" => null], ["s" => null]);
self::assertEnsureOk(self::SCHEMA_S, ["s" => "not null"], ["s" => "not null"]);
self::assertEnsureOk(self::SCHEMA_NS, [], []);
self::assertEnsureOk(self::SCHEMA_NS, ["s" => null], ["s" => null]);
self::assertEnsureOk(self::SCHEMA_NS, ["s" => "not null"], ["s" => "not null"]);
self::assertEnsureOk(self::SCHEMA_SD, [], []);
self::assertEnsureFail(self::SCHEMA_SD, ["s" => null], ["s" => null]);
self::assertEnsureOk(self::SCHEMA_SD, ["s" => "not null"], ["s" => "not null"]);
self::assertEnsureOk(self::SCHEMA_NSD, [], []);
self::assertEnsureOk(self::SCHEMA_NSD, ["s" => null], ["s" => null]);
self::assertEnsureOk(self::SCHEMA_NSD, ["s" => "not null"], ["s" => "not null"]);
self::assertEnsureOk(self::SCHEMA_SND, [], ["s" => "not null"]);
self::assertEnsureOk(self::SCHEMA_SND, ["s" => null], ["s" => "not null"]);
self::assertEnsureOk(self::SCHEMA_SND, ["s" => "not null"], ["s" => "not null"]);
self::assertEnsureOk(self::SCHEMA_NSND, [], ["s" => "not null"]);
self::assertEnsureOk(self::SCHEMA_NSND, ["s" => null], ["s" => null]);
self::assertEnsureOk(self::SCHEMA_NSND, ["s" => "not null"], ["s" => "not null"]);
self::assertEnsureFail(self::SCHEMA_RS, [], []);
self::assertEnsureFail(self::SCHEMA_RS, ["s" => null], []);
self::assertEnsureOk(self::SCHEMA_RS, ["s" => "not null"], ["s" => "not null"]);
self::assertEnsureFail(self::SCHEMA_NRS, [], []);
self::assertEnsureOk(self::SCHEMA_NRS, ["s" => null], ["s" => null]);
self::assertEnsureOk(self::SCHEMA_NRS, ["s" => "not null"], ["s" => "not null"]);
self::assertEnsureFail(self::SCHEMA_RSD, [], []);
self::assertEnsureFail(self::SCHEMA_RSD, ["s" => null], []);
self::assertEnsureOk(self::SCHEMA_RSD, ["s" => "not null"], ["s" => "not null"]);
self::assertEnsureFail(self::SCHEMA_NRSD, [], []);
self::assertEnsureOk(self::SCHEMA_NRSD, ["s" => null], ["s" => null]);
self::assertEnsureOk(self::SCHEMA_NRSD, ["s" => "not null"], ["s" => "not null"]);
self::assertEnsureOk(self::SCHEMA_RSND, [], ["s" => "not null"]);
self::assertEnsureOk(self::SCHEMA_RSND, ["s" => null], ["s" => "not null"]);
self::assertEnsureOk(self::SCHEMA_RSND, ["s" => "not null"], ["s" => "not null"]);
self::assertEnsureOk(self::SCHEMA_NRSND, [], ["s" => "not null"]);
self::assertEnsureOk(self::SCHEMA_NRSND, ["s" => null], ["s" => null]);
self::assertEnsureOk(self::SCHEMA_NRSND, ["s" => "not null"], ["s" => "not null"]);
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace nulib\php\types;
use nulib\tests\TestCase;
use nulib\ValueException;
class scalarTest extends TestCase {
function testCheckSchema() {
self::assertTrue(scalar::check_schema(5, "int"));
self::assertTrue(scalar::check_schema(5, "string"));
self::assertTrue(scalar::check_schema(5, "float"));
self::assertTrue(scalar::check_schema(5, "int", true));
self::assertFalse(scalar::check_schema(5, "string", true));
self::assertFalse(scalar::check_schema(5, "float", true));
self::assertTrue(scalar::check_schema("5", "int"));
self::assertTrue(scalar::check_schema("5", "string"));
self::assertTrue(scalar::check_schema("5", "float"));
self::assertFalse(scalar::check_schema("5", "int", true));
self::assertTrue(scalar::check_schema("5", "string", true));
self::assertFalse(scalar::check_schema("5", "float", true));
self::assertFalse(scalar::check_schema("bad format", "int"));
}
function testEnsureSchema() {
$v = 5; scalar::ensure_schema($v, "int");
self::assertSame(5, $v);
$v = "5"; scalar::ensure_schema($v, "int");
self::assertSame(5, $v);
$v = "5.0"; scalar::ensure_schema($v, "int");
self::assertSame(5, $v);
$v = 5.0; scalar::ensure_schema($v, "int");
self::assertSame(5, $v);
$v = 5; scalar::ensure_schema($v, "string");
self::assertSame("5", $v);
$v = "5"; scalar::ensure_schema($v, "string");
self::assertSame("5", $v);
$v = "5.0"; scalar::ensure_schema($v, "string");
self::assertSame("5.0", $v);
$v = 5.0; scalar::ensure_schema($v, "string");
self::assertSame("5", $v);
$v = 5; scalar::ensure_schema($v, "float");
self::assertSame(5.0, $v);
$v = "5"; scalar::ensure_schema($v, "float");
self::assertSame(5.0, $v);
$v = "5.0"; scalar::ensure_schema($v, "float");
self::assertSame(5.0, $v);
$v = 5.0; scalar::ensure_schema($v, "float");
self::assertSame(5.0, $v);
self::assertException(ValueException::class, function() {
$v = "bad format"; scalar::ensure_schema($v, "int");
});
}
}