2023-12-03 22:10:18 +04:00
|
|
|
<?php
|
|
|
|
namespace nur;
|
|
|
|
|
|
|
|
use nur\b\ValueException;
|
|
|
|
use nur\ref\ref_type;
|
|
|
|
use Traversable;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Class md: gestion de métadonnées. cette classe est une version simplifiée de
|
|
|
|
* {@link Metadata}
|
|
|
|
*/
|
|
|
|
class md {
|
|
|
|
/**
|
|
|
|
* fonction de support pour tester si une définition de type désigne une valeur
|
|
|
|
* nullable. retourner true si la valeur est nullable, false sinon.
|
|
|
|
* $type est mis à jour pour enlever le marqueur "?" le cas échéant
|
|
|
|
*/
|
|
|
|
static final function check_nullable(&$type): bool {
|
|
|
|
if (is_string($type) && substr($type, 0, 1) === "?") {
|
|
|
|
$type = substr($type, 1);
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return $type === null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array méta-schéma, i.e schéma d'un champ de schéma
|
|
|
|
*
|
|
|
|
* A propos des types: en temps normal, quand il s'agit de valider les valeurs
|
|
|
|
* d'un objet par rapport à un schéma, les valeurs suivantes sont spéciales:
|
|
|
|
* - false: indiquer que le champ n'est pas spécifié, il obtient donc la
|
|
|
|
* valeur par défaut
|
|
|
|
* - null: indiquer que la champ a une valeur et que cette valeur est null
|
|
|
|
*
|
|
|
|
* Cependant, certains types modifient ce comportement:
|
|
|
|
* - bool: la valeur false n'a pas de signification particulière. la valeur
|
|
|
|
* null indique que le champ n'est pas spécifié et doit recevoir la valeur
|
|
|
|
* par défaut
|
|
|
|
* - mixed|?bool: les valeurs false et null n'ont pas de signification
|
|
|
|
* particulières elles sont donc reprises telles quelles
|
|
|
|
* - array|?array: la valeur est un tableau. le schéma de la valeur est
|
|
|
|
* pris en compte s'il est spécifié
|
|
|
|
* - array[]|?array[]: la valeur est une *liste* de tableaux. le schéma de
|
|
|
|
* chacun des éléments du tabvleau est pris en compte s'il est spécifié.
|
|
|
|
*
|
|
|
|
* Pour les types bool, int, double, float, string, array (et les variantes
|
|
|
|
* ?type qui autorisent la valeur null), la valeur est transformée dans le
|
|
|
|
* type demandé
|
|
|
|
*/
|
|
|
|
const MSFIELD_SCHEMA = [
|
|
|
|
"type" => [
|
|
|
|
"type" => "mixed",
|
|
|
|
"default" => null,
|
|
|
|
"title" => "type PHP de la valeur",
|
|
|
|
"required" => false,
|
|
|
|
"key" => "type",
|
|
|
|
"header" => "type",
|
|
|
|
"desc" => null,
|
|
|
|
"schema" => null,
|
|
|
|
"composite" => false,
|
|
|
|
],
|
|
|
|
"default" => [
|
|
|
|
"type" => "mixed",
|
|
|
|
"default" => null,
|
|
|
|
"title" => "valeur par défaut du champ",
|
|
|
|
"required" => false,
|
|
|
|
"key" => "default",
|
|
|
|
"header" => "default",
|
|
|
|
"desc" => null,
|
|
|
|
"schema" => null,
|
|
|
|
"composite" => false,
|
|
|
|
],
|
|
|
|
"title" => [
|
|
|
|
"type" => "?string",
|
|
|
|
"default" => null,
|
|
|
|
"title" => "libellé de la valeur utilisé par exemple dans un formulaire",
|
|
|
|
"required" => false,
|
|
|
|
"key" => "title",
|
|
|
|
"header" => "title",
|
|
|
|
"desc" => null,
|
|
|
|
"schema" => null,
|
|
|
|
"composite" => false,
|
|
|
|
],
|
|
|
|
"required" => [
|
|
|
|
"type" => "bool",
|
|
|
|
"default" => false,
|
|
|
|
"title" => "cette valeur est-elle requise?",
|
|
|
|
"required" => false,
|
|
|
|
"key" => "required",
|
|
|
|
"header" => "required",
|
|
|
|
"desc" => null,
|
|
|
|
"schema" => null,
|
|
|
|
"composite" => false,
|
|
|
|
],
|
|
|
|
"key" => [
|
|
|
|
"type" => "?string",
|
|
|
|
"default" => null,
|
|
|
|
"title" => "nom de la clé",
|
|
|
|
"required" => false,
|
|
|
|
"key" => "key",
|
|
|
|
"header" => "key",
|
|
|
|
"desc" => null,
|
|
|
|
"schema" => null,
|
|
|
|
"composite" => false,
|
|
|
|
],
|
|
|
|
"header" => [
|
|
|
|
"type" => "?string",
|
|
|
|
"default" => null,
|
|
|
|
"title" => "nom de l'en-tête s'il fallait présenter cette donnée dans un tableau",
|
|
|
|
"required" => false,
|
|
|
|
"key" => "header",
|
|
|
|
"header" => "header",
|
|
|
|
"desc" => null,
|
|
|
|
"schema" => null,
|
|
|
|
"composite" => false,
|
|
|
|
],
|
|
|
|
"desc" => [
|
|
|
|
"type" => "?string",
|
|
|
|
"default" => null,
|
|
|
|
"title" => "description de la valeur",
|
|
|
|
"required" => false,
|
|
|
|
"key" => "desc",
|
|
|
|
"header" => "desc",
|
|
|
|
"desc" => null,
|
|
|
|
"schema" => null,
|
|
|
|
"composite" => false,
|
|
|
|
],
|
|
|
|
"schema" => [
|
|
|
|
"type" => "?array",
|
|
|
|
"default" => null,
|
|
|
|
"title" => "schema de la donnée",
|
|
|
|
"required" => false,
|
|
|
|
"key" => "schema",
|
|
|
|
"header" => "schema",
|
|
|
|
"desc" => null,
|
|
|
|
"schema" => null,
|
|
|
|
"composite" => false,
|
|
|
|
],
|
|
|
|
"composite" => [
|
|
|
|
"type" => "bool",
|
|
|
|
"default" => false,
|
|
|
|
"title" => "ce champ fait-il partie d'une valeur composite?",
|
|
|
|
"required" => false,
|
|
|
|
"key" => "composite",
|
|
|
|
"header" => "composite",
|
|
|
|
"desc" => null,
|
|
|
|
"schema" => null,
|
|
|
|
"composite" => false,
|
|
|
|
],
|
|
|
|
];
|
|
|
|
const MSFIELD_INDEXES = ["type" => 0, "default" => 1, "title" => 2, "required" => 3, "key" => 4, "header" => 5, "desc" => 6, "schema" => 7, "composite" => 8];
|
|
|
|
const ARRAY_TYPES = ["array", "?array", "array[]", "?array[]"];
|
|
|
|
const APPLY2ITEMS_TYPES = ["array[]", "?array[]"];
|
|
|
|
|
|
|
|
static final function normalize_schema(array &$schema, bool $recursive=true): array {
|
|
|
|
foreach ($schema as $key => &$msfield) {
|
|
|
|
if ($key === "") continue;
|
|
|
|
self::_ensure_msfield($msfield, $key);
|
|
|
|
|
|
|
|
$is_array_type = in_array($msfield["type"], self::ARRAY_TYPES);
|
|
|
|
if ($msfield["schema"] !== null) {
|
|
|
|
if (!$is_array_type) $msfield["schema"] = null;
|
|
|
|
elseif ($recursive) self::normalize_schema($msfield["schema"], $recursive);
|
|
|
|
}
|
|
|
|
}; unset($msfield);
|
|
|
|
return array_flip(array_keys($schema));
|
|
|
|
}
|
|
|
|
|
|
|
|
/** s'assurer que $msfield est un champ de méta-schéma conforme */
|
|
|
|
static final function _ensure_msfield(&$msfield, $key=null): void {
|
|
|
|
if (!is_array($msfield)) {
|
|
|
|
$msfield = [
|
|
|
|
"type" => $msfield, "default" => null, "title" => $key, "required" => false,
|
|
|
|
"key" => $key, "header" => $key,
|
|
|
|
"desc" => null, "schema" => null, "composite" => false,
|
|
|
|
];
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ($key !== null) $msfield["key"] = $key;
|
|
|
|
self::_ensure_schema($msfield, self::MSFIELD_SCHEMA, self::MSFIELD_INDEXES, false);
|
|
|
|
if ($msfield["title"] === null) $msfield["title"] = $msfield["key"];
|
|
|
|
if ($msfield["header"] === null) $msfield["header"] = $msfield["title"];
|
|
|
|
if ($msfield["schema"] !== null) {
|
|
|
|
foreach ($msfield["schema"] as $key2 => &$msfield2) {
|
|
|
|
self::_ensure_msfield($msfield2, $key2);
|
|
|
|
}; unset($msfield2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* s'assurer que
|
|
|
|
* - $sfield est un tableau
|
|
|
|
* - il contient toutes les clés du schéma, en provisionnant les valeurs par
|
|
|
|
* défaut à chaque fois que c'est nécessaire
|
|
|
|
* - les valeurs sont du bon type. seules les types standards listés dans
|
|
|
|
* {@link KNOWN_TYPES} sont considérés
|
|
|
|
*
|
|
|
|
* notez que:
|
|
|
|
* - les clés ne sont pas réordonnées
|
|
|
|
* - la présence de tous les champs requis n'est pas vérifiée par défaut
|
|
|
|
* (utiliser {@link check_required()} pour cela)
|
|
|
|
* - seuls les types simples sont supportés. notamment, les types composites
|
|
|
|
* ne sont pas supportés
|
|
|
|
* - les types "éléments de liste" ne sont pas supportés
|
|
|
|
*/
|
|
|
|
static final function ensure_schema(&$item, ?array $schema, $item_key=null, bool $check_required=false): void {
|
|
|
|
if ($schema !== null) {
|
|
|
|
$indexes = self::normalize_schema($schema);
|
|
|
|
self::_ensure_array_item($item, $schema, $item_key);
|
|
|
|
self::_ensure_schema_recursive($item, $schema, $indexes);
|
|
|
|
if ($check_required) self::check_required($item, $schema);
|
|
|
|
} else {
|
|
|
|
self::_ensure_array_item($item, $schema, $item_key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-04 14:22:43 +04:00
|
|
|
private static function _ensure_array_item(&$item, ?array $schema, $item_key): void {
|
2023-12-03 22:10:18 +04:00
|
|
|
if ($item_key !== null) {
|
|
|
|
if (is_array($item)) {
|
|
|
|
if ($schema !== null) {
|
|
|
|
# n'utiliser item_key que si la première clé du schéma n'existe pas
|
|
|
|
$first_key = array_key_first($schema);
|
|
|
|
if (!array_key_exists($first_key, $item)) {
|
|
|
|
$tmp = [$item_key];
|
|
|
|
A::merge3($tmp, $item);
|
|
|
|
$item = $tmp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$item = [$item_key, $item];
|
|
|
|
}
|
|
|
|
} elseif ($item !== null && !is_array($item)) {
|
|
|
|
$item = [$item];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-04 14:22:43 +04:00
|
|
|
private static function _ensure_schema_recursive(&$item, array $schema, ?array $indexes=null): void {
|
2023-12-03 22:10:18 +04:00
|
|
|
self::_ensure_schema($item, $schema, $indexes);
|
|
|
|
foreach ($schema as $key => $sfield) {
|
|
|
|
$schema2 = $sfield["schema"];
|
|
|
|
if ($schema2 === null) continue;
|
|
|
|
switch ($sfield["type"]) {
|
|
|
|
case "?array":
|
|
|
|
if ($item[$key] === null) continue 2;
|
|
|
|
case "array":
|
|
|
|
self::_ensure_array_item($item[$key], $schema2, null);
|
|
|
|
self::_ensure_schema_recursive($item[$key], $schema2);
|
|
|
|
break;
|
|
|
|
case "?array[]":
|
|
|
|
if ($item[$key] === null) continue 2;
|
|
|
|
case "array[]":
|
|
|
|
$index2 = 0;
|
|
|
|
foreach ($item[$key] as $key2 => &$item2) {
|
|
|
|
if ($key2 === $index2) {
|
|
|
|
$index2++;
|
|
|
|
$key2 = null;
|
|
|
|
}
|
|
|
|
self::_ensure_array_item($item2, $schema2, $key2);
|
|
|
|
self::_ensure_schema_recursive($item2, $schema2);
|
|
|
|
}; unset($item2);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* s'assurer que $item est conforme au schéma $schema.
|
|
|
|
*
|
|
|
|
* on assume que $schema est un schéma normalisé. $indexes est reconstruit si
|
|
|
|
* nécessaire
|
|
|
|
*/
|
|
|
|
static final function _ensure_schema(&$item, array $schema, ?array $indexes=null, bool $ensure_type=true): void {
|
|
|
|
$keys = array_keys($schema);
|
|
|
|
if ($indexes === null) $indexes = array_flip($keys);
|
|
|
|
if ($item === null) $item = [];
|
|
|
|
elseif (!is_array($item)) $item = [$item];
|
|
|
|
|
|
|
|
$src = $item;
|
|
|
|
$dones = array_fill(0, count($keys), false);
|
|
|
|
# d'abord les clés associatives
|
|
|
|
$inputIndex = 0;
|
|
|
|
foreach ($src as $key => $value) {
|
|
|
|
if ($key === $inputIndex) {
|
|
|
|
# clé séquentielle
|
|
|
|
$inputIndex++;
|
|
|
|
} else {
|
|
|
|
# clé associative
|
|
|
|
$is_schema_key = array_key_exists($key, $schema);
|
|
|
|
if ($ensure_type && $is_schema_key) {
|
|
|
|
$sfield = $schema[$key];
|
|
|
|
self::_ensure_type($sfield["type"], $value, $sfield["default"], true);
|
|
|
|
}
|
|
|
|
$item[$key] = $value;
|
|
|
|
if ($is_schema_key) $dones[$indexes[$key]] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
# ensuite les clés séquentielles
|
|
|
|
$inputIndex = 0;
|
|
|
|
$outputIndex = 0;
|
|
|
|
foreach ($src as $index => $value) {
|
|
|
|
if ($index === $inputIndex) {
|
|
|
|
# clé séquentielle
|
|
|
|
$inputIndex++;
|
|
|
|
unset($item[$index]);
|
|
|
|
$found = false;
|
|
|
|
foreach ($keys as $kindex => $key) {
|
|
|
|
if (!$dones[$kindex]) {
|
|
|
|
$sfield = $schema[$key];
|
|
|
|
if ($ensure_type) {
|
|
|
|
self::_ensure_type($sfield["type"], $value, $sfield["default"], true);
|
|
|
|
}
|
|
|
|
$item[$key] = $value;
|
|
|
|
$dones[$kindex] = true;
|
|
|
|
$found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!$found) {
|
|
|
|
$item[$outputIndex++] = $value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
# puis mettre les valeurs par défaut des clés qui restent
|
|
|
|
foreach ($dones as $dindex => $done) {
|
|
|
|
if (!$done) {
|
|
|
|
$key = $keys[$dindex];
|
|
|
|
$sfield = $schema[$key];
|
|
|
|
$value = $sfield["default"];
|
|
|
|
if ($ensure_type) {
|
|
|
|
self::_ensure_type($sfield["type"], $value, $sfield["default"], false);
|
|
|
|
}
|
|
|
|
$item[$key] = $value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* fonction de support pour s'assurer que $value est dans le bon type. seuls
|
|
|
|
* les types simples sont reconnus. s'il s'agit d'un type complexe, la valeur
|
|
|
|
* n'est pas vérifiée ni modifiée
|
|
|
|
*/
|
2024-10-04 14:22:43 +04:00
|
|
|
private static function _ensure_type(?string $type, &$value, $default, bool $exists): void {
|
2023-12-03 22:10:18 +04:00
|
|
|
if (self::_check_known_type($type, $value, $default, $exists)) {
|
|
|
|
self::_convert_value($type, $value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* fonction de support pour vérifier que $type est un type simple dans lequel
|
|
|
|
* on peut convertir la valeur. s'il est possible de convertir la valeur, le
|
|
|
|
* faire
|
|
|
|
* - retourner false s'il n'est pas nécessaire de faire plus de traitement. la
|
|
|
|
* valeur est déjà dans le bon type (ou alors c'est un type complexe et il
|
|
|
|
* n'est pas possible ici de convertir la valeur)
|
|
|
|
* - retourner true s'il faut convertir la valeur avec {@link _convert_value()}
|
|
|
|
*/
|
|
|
|
static final function _check_known_type(&$type, &$value, $default, bool $exists): bool {
|
|
|
|
$nullable = true;
|
|
|
|
if (!is_array($type)) $nullable = self::check_nullable($type);
|
|
|
|
if ($type === ref_type::ANY) {
|
|
|
|
# Le type null est particulier: false correspondant à non existant
|
|
|
|
if (!$exists || $value === false) $value = $default;
|
|
|
|
return false;
|
|
|
|
} elseif ($type === ref_type::MIXED) {
|
|
|
|
# Le type mixed est particulier: la valeur est prise en l'état sans aucune
|
|
|
|
# modification
|
|
|
|
if (!$exists) $value = $default;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!is_array($type)) $type = A::get(ref_type::ALIASES, $type, $type);
|
|
|
|
if (is_array($type) || !in_array($type, ref_type::KNOWN_TYPES)) {
|
|
|
|
if (!$exists) $value = $default;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($type === ref_type::BOOL) {
|
|
|
|
# avec bool, null fonctionne comme "non présent"
|
|
|
|
if (!$exists || (!$nullable && $value === null)) $value = $default;
|
|
|
|
} elseif (!$exists || $value === false || (!$nullable && $value === null)) {
|
|
|
|
$value = $default;
|
|
|
|
}
|
|
|
|
if ($value === null && $nullable) return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const FALSE_VALUES = ["false", "faux", "non", "no", "f", "n", "0", ""];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* fonction de support pour convertir la valeur dans le type simple spécifié
|
|
|
|
*/
|
|
|
|
static final function _convert_value(string $type, &$value): void {
|
|
|
|
switch ($type) {
|
|
|
|
case ref_type::BOOL:
|
|
|
|
if (!is_string($value)) $value = boolval($value);
|
|
|
|
else $value = !in_array(strtolower(trim($value)), self::FALSE_VALUES);
|
|
|
|
return;
|
|
|
|
case ref_type::INT:
|
|
|
|
$value = intval($value);
|
|
|
|
return;
|
|
|
|
case ref_type::FLOAT:
|
|
|
|
$value = floatval($value);
|
|
|
|
return;
|
|
|
|
case ref_type::RAWSTRING:
|
|
|
|
if (is_array($value)) $value = str::join3($value);
|
|
|
|
elseif (!is_string($value)) $value = strval($value);
|
|
|
|
return;
|
|
|
|
case ref_type::STRING:
|
|
|
|
case ref_type::TEXT:
|
|
|
|
if (is_array($value)) $value = str::join3($value);
|
|
|
|
elseif (!is_string($value)) $value = strval($value);
|
|
|
|
$value = str::norm_nl(trim($value));
|
|
|
|
return;
|
|
|
|
case ref_type::KEY:
|
|
|
|
if (!is_int($value)) {
|
|
|
|
if (is_array($value)) $value = str::join3($value, ".");
|
|
|
|
$value = strval($value);
|
|
|
|
if (preg_match('/^[0-9]+$/', $value)) {
|
|
|
|
$value = intval($value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
case ref_type::CONTENT:
|
|
|
|
$value = c::qnz($value);
|
|
|
|
return;
|
|
|
|
case ref_type::FILE:
|
|
|
|
case ref_type::ARRAY:
|
|
|
|
A::ensure_array($value);
|
|
|
|
return;
|
|
|
|
case ref_type::ARRAY_ARRAY:
|
|
|
|
A::ensure_array($value);
|
|
|
|
foreach ($value as &$item) {
|
|
|
|
A::ensure_array($item);
|
|
|
|
}; unset($item);
|
|
|
|
return;
|
|
|
|
case ref_type::ITERABLE:
|
|
|
|
if (!($value instanceof Traversable)) {
|
|
|
|
A::ensure_array($value);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
case ref_type::RESOURCE:
|
|
|
|
if (!is_resource($value)) {
|
|
|
|
throw ValueException::invalid_value($value, "resource");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* vérifier que tous les champs marqués comme requis dans le schéma n'ont pas
|
|
|
|
* une valeur null. sinon lancer une exception {@link ValueException}
|
|
|
|
*
|
|
|
|
* on assume que $item est conforme au schéma
|
|
|
|
*/
|
|
|
|
static final function check_required(array $item, ?array $schema): void {
|
|
|
|
if ($schema === null) return;
|
|
|
|
self::normalize_schema($schema);
|
|
|
|
self::_check_required($item, $schema);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** comme {@link check_required()} mais $schema doit être déjà normalisé */
|
|
|
|
static final function _check_required(array $item, array $schema, string $key_prefix=""): void {
|
|
|
|
foreach ($schema as $key => $sfield) {
|
|
|
|
$exists = array_key_exists($key, $item) && $item[$key] !== null;
|
|
|
|
if ($sfield["required"] && !$exists) {
|
|
|
|
throw new ValueException("$key_prefix$key is required");
|
|
|
|
}
|
|
|
|
$schema2 = $sfield["schema"];
|
|
|
|
if ($schema2 !== null && $exists) {
|
|
|
|
#XXX si le type est array[], il faut vérifier sur chacun des éléments!
|
|
|
|
self::_check_required($item[$key], $schema2, "$key_prefix$key.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* obtenir la valeur de la clé $key depuis $item en tenant compte des
|
|
|
|
* informations du schéma. si la clé n'existe pas, retourner la valeur par
|
|
|
|
* défaut: soit $default s'il n'est pas null, soit la valeur par défaut du
|
|
|
|
* schéma.
|
|
|
|
*
|
|
|
|
* $item n'a pas besoin d'être conforme au schéma: il n'est pas nécessaire
|
|
|
|
* que toutes les clés soient présentes, et $item peut être un tableau
|
|
|
|
* séquentiel
|
|
|
|
*
|
|
|
|
* XXX si le type n'est pas bool, interpréter false comme "non présent"
|
|
|
|
*/
|
|
|
|
static final function has($item, $key, ?array $schema): bool {
|
|
|
|
if ($schema === null) return $key === 0;
|
|
|
|
$indexes = self::normalize_schema($schema);
|
|
|
|
return self::_has($item, $key, $schema, $indexes);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** comme {@link has()} mais $schema doit être déjà normalisé */
|
|
|
|
static final function _has($item, $key, array $schema, ?array $indexes=null) {
|
|
|
|
if (!array_key_exists($key, $schema)) {
|
|
|
|
return is_array($item) && array_key_exists($key, $item);
|
|
|
|
}
|
|
|
|
if (!is_array($item)) $item = [$item];
|
|
|
|
if (array_key_exists($key, $item)) return true;
|
|
|
|
if ($indexes === null) $indexes = array_flip(array_keys($schema));
|
|
|
|
$index = $indexes[$key];
|
|
|
|
return array_key_exists($index, $item);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* obtenir la valeur de la clé $key depuis $item en tenant compte des
|
|
|
|
* informations du schéma. si la clé n'existe pas, retourner la valeur par
|
|
|
|
* défaut: soit $default s'il n'est pas null, soit la valeur par défaut du
|
|
|
|
* schéma.
|
|
|
|
*
|
|
|
|
* $item n'a pas besoin d'être conforme au schéma: il n'est pas nécessaire
|
|
|
|
* que toutes les clés soient présentes, et $item peut être un tableau
|
|
|
|
* séquentiel
|
|
|
|
*/
|
|
|
|
static final function get($item, $key, ?array $schema, $default=null, bool $ensure_type=true) {
|
|
|
|
if ($schema === null) return $key === 0? $item: null;
|
|
|
|
$indexes = self::normalize_schema($schema);
|
|
|
|
return self::_get($item, $key, $schema, $default, $ensure_type, $indexes);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** comme {@link get()} mais $schema doit être déjà normalisé */
|
|
|
|
static final function _get($item, $key, array $schema, $default=null, bool $ensure_type=true, ?array $indexes=null) {
|
|
|
|
if (!array_key_exists($key, $schema)) {
|
|
|
|
if (is_array($item) && array_key_exists($key, $item)) {
|
|
|
|
return $item[$key];
|
|
|
|
} else {
|
|
|
|
return $default;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$sfield = $schema[$key];
|
|
|
|
|
|
|
|
if (!is_array($item)) $item = [$item];
|
|
|
|
if (array_key_exists($key, $item)) {
|
|
|
|
$exists = true;
|
|
|
|
$value = $item[$key];
|
|
|
|
} else {
|
|
|
|
if ($indexes === null) $indexes = array_flip(array_keys($schema));
|
|
|
|
$index = $indexes[$key];
|
|
|
|
if (array_key_exists($index, $item)) {
|
|
|
|
$exists = true;
|
|
|
|
$value = $item[$index];
|
|
|
|
} elseif ($default !== null) {
|
|
|
|
$exists = true;
|
|
|
|
$value = $default;
|
|
|
|
} else {
|
|
|
|
$exists = false;
|
|
|
|
$value = $sfield["default"];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($ensure_type) {
|
|
|
|
self::_ensure_type($sfield["type"], $value, $sfield["default"], $exists);
|
|
|
|
$schema2 = $sfield["schema"];
|
|
|
|
if ($schema2 !== null) self::_ensure_schema_recursive($value, $schema2);
|
|
|
|
}
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* sélectionner dans $items les valeurs du schéma, et les retourner dans
|
|
|
|
* l'ordre du schéma
|
|
|
|
*
|
|
|
|
* $item doit être un tableau associatif. idéalement, il a été au préalable
|
|
|
|
* rendu conforme au schéma
|
|
|
|
*
|
|
|
|
* si $item n'est pas conforme au schéma, les champs ne sont reconnus que si
|
|
|
|
* l'on utilise les clés associatives. il n'est pas nécessaire que toutes les
|
|
|
|
* clés du schéma soient présentes. dans ce cas, seuls les clés présentes sont
|
|
|
|
* dans le tableau résultat. dans ce cas de figure, $ensure_type==true permet
|
|
|
|
* de s'assurer aussi que les valeurs sont dans le bon type
|
|
|
|
*/
|
|
|
|
static final function get_values(?array $item, ?array $schema, bool $ensure_type=false): array {
|
|
|
|
if ($item === null || $schema === null) return [];
|
|
|
|
self::normalize_schema($schema);
|
|
|
|
return self::_get_values($item, $schema, $ensure_type);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** comme {@link get_values()} mais $schema doit être déjà normalisé */
|
|
|
|
static final function _get_values(array $item, array $schema, bool $ensure_type=false): array {
|
|
|
|
$values = [];
|
|
|
|
foreach ($schema as $key => $sfield) {
|
|
|
|
if (!array_key_exists($key, $item)) continue;
|
|
|
|
$value = $item[$key];
|
|
|
|
if ($ensure_type) {
|
|
|
|
self::_ensure_type($sfield["type"], $value, $sfield["default"], true);
|
|
|
|
}
|
|
|
|
$values[$key] = $value;
|
|
|
|
}
|
|
|
|
return $values;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* complément de {@link get_values()}: retourner les clés qui n'ont pas été
|
|
|
|
* sélectionnées
|
|
|
|
*/
|
|
|
|
static final function get_others(?array $item, ?array $schema): array {
|
|
|
|
if ($item === null) return [];
|
|
|
|
elseif ($schema === null) return $item;
|
|
|
|
self::normalize_schema($schema);
|
|
|
|
return self::_get_others($item, $schema);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** comme {@link get_others()} mais $schema doit être déjà normalisé */
|
|
|
|
static final function _get_others(array $item, array $schema): array {
|
|
|
|
$others = [];
|
|
|
|
foreach ($item as $key => $value) {
|
|
|
|
if (array_key_exists($key, $schema)) continue;
|
|
|
|
$others[$key] = $value;
|
|
|
|
}
|
|
|
|
return $others;
|
|
|
|
}
|
|
|
|
}
|