614 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			614 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?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);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  private static final function _ensure_array_item(&$item, ?array $schema, $item_key): void {
 | 
						|
    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];
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  private static final function _ensure_schema_recursive(&$item, array $schema, ?array $indexes=null): void {
 | 
						|
    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
 | 
						|
   */
 | 
						|
  private static final function _ensure_type(?string $type, &$value, $default, bool $exists): void {
 | 
						|
    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;
 | 
						|
  }
 | 
						|
}
 |