ajout nur/data
This commit is contained in:
		
							parent
							
								
									09cd738458
								
							
						
					
					
						commit
						8dc8d12ab3
					
				
							
								
								
									
										15
									
								
								nur_src/data/Context.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								nur_src/data/Context.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data;
 | 
			
		||||
 | 
			
		||||
use nur\data\expr\IContext;
 | 
			
		||||
 | 
			
		||||
class Context implements IContext {
 | 
			
		||||
  /**
 | 
			
		||||
   * ajouter une nouvelle source de données
 | 
			
		||||
   *
 | 
			
		||||
   * $data contient une description de la source de données, conforme au schéma
 | 
			
		||||
   * {@link IContext::SOURCE_SCHEMA}
 | 
			
		||||
   */
 | 
			
		||||
  function addSource(array $data, $source) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										57
									
								
								nur_src/data/expr/GenericExpr.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								nur_src/data/expr/GenericExpr.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,57 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\expr;
 | 
			
		||||
 | 
			
		||||
use nur\A;
 | 
			
		||||
use nur\b\coll\BaseArray;
 | 
			
		||||
use nur\b\ValueException;
 | 
			
		||||
use nur\func;
 | 
			
		||||
use nur\md;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class GenericExpr: une expression générique, utilisée par défaut
 | 
			
		||||
 */
 | 
			
		||||
class GenericExpr extends BaseArray implements IExpr {
 | 
			
		||||
  static final function with($expr, ?string $key=null): IExpr {
 | 
			
		||||
    if ($expr instanceof IExpr) return $expr;
 | 
			
		||||
    return new GenericExpr($expr, $key);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function __construct($data=null, ?string $key=null) {
 | 
			
		||||
    md::ensure_schema($data, static::SCHEMA, null, false);
 | 
			
		||||
    A::replace_z($data, "name", $key);
 | 
			
		||||
    A::replace_z_indirect($data, "name", "value");
 | 
			
		||||
    A::replace_z_indirect($data, "title", "name");
 | 
			
		||||
    parent::__construct($data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function getValue(): ?string {
 | 
			
		||||
    return $this->data["value"];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function getName(): string {
 | 
			
		||||
    return $this->data["name"];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function getTitle(): string {
 | 
			
		||||
    return $this->data["title"];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public function eval(IContext $context) {
 | 
			
		||||
    $expr = $this->getValue();
 | 
			
		||||
    if (func::is_static($expr) || func::is_method($expr)) {
 | 
			
		||||
      return $context->callMethod($expr);
 | 
			
		||||
    } elseif (is_string($expr)) {
 | 
			
		||||
      $prefix = substr($expr, 0, 1);
 | 
			
		||||
      if ($prefix == "*") {
 | 
			
		||||
        # session
 | 
			
		||||
        return $context->getSession(substr($expr, 1));
 | 
			
		||||
      } elseif ($prefix == "+") {
 | 
			
		||||
        # config
 | 
			
		||||
        return $context->getConfig(substr($expr, 1));
 | 
			
		||||
      }
 | 
			
		||||
      # valeur à récupérer du contexte
 | 
			
		||||
      return $context->getValue($expr);
 | 
			
		||||
    }
 | 
			
		||||
    throw ValueException::unexpected_type(["string", "callable"], $expr);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										60
									
								
								nur_src/data/expr/IContext.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								nur_src/data/expr/IContext.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,60 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\expr;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Interface IContext: un contexte à partir duquel sont accédées certaines
 | 
			
		||||
 * données
 | 
			
		||||
 */
 | 
			
		||||
interface IContext {
 | 
			
		||||
  /** schéma d'une représentation d'un contexte sous forme de tableau */
 | 
			
		||||
  const SCHEMA = [
 | 
			
		||||
    "sources" => [null, null, "sources des données de ce contexte", true],
 | 
			
		||||
    "exprs" => [null, null, "liste des expressions définies dans ce contexte", true],
 | 
			
		||||
    "conds" => [null, null, "liste des expressions conditionnelles définies dans ce contexte", true],
 | 
			
		||||
  ];
 | 
			
		||||
  /** schéma d'une description de source sous forme de tableau */
 | 
			
		||||
  const SOURCE_SCHEMA = [
 | 
			
		||||
    "name" => [null, null, "identifiant de la source de donnée", true],
 | 
			
		||||
    "title" => [null, null, "description de la source de donnée, pour affichage", false],
 | 
			
		||||
  ];
 | 
			
		||||
  /** schéma d'une description d'une expression sous forme de tableau */
 | 
			
		||||
  const EXPR_SCHEMA = IExpr::SCHEMA;
 | 
			
		||||
  /** schéma d'une description d'une condition sous forme de tableau */
 | 
			
		||||
  const COND_SCHEMA = IExpr::SCHEMA;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @return array des informations sur ce contexte, sous la forme d'un tableau
 | 
			
		||||
   * conforme au schéma {@link IContext::SCHEMA}
 | 
			
		||||
   */
 | 
			
		||||
  function getContextInfos(): array;
 | 
			
		||||
 | 
			
		||||
  /** @return mixed obtenir la valeur correspondant au chemin */
 | 
			
		||||
  function getValue(string $pkey);
 | 
			
		||||
 | 
			
		||||
  /** @return mixed obtenir la valeur de la session correspondant au chemin */
 | 
			
		||||
  function getSession(string $pkey);
 | 
			
		||||
 | 
			
		||||
  /** @return mixed obtenir la valeur de configuration correspondant au chemin */
 | 
			
		||||
  function getConfig(string $pkey);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * appeler la méthode spécifiée et retourner le résultat de l'appel.
 | 
			
		||||
   *
 | 
			
		||||
   * La méthode peut être dans un des formats suivants:
 | 
			
		||||
   * - "Class::method" ou ["Class", "method"] pour appeler une méthode statique
 | 
			
		||||
   * de la classe spécifiée
 | 
			
		||||
   * - "::method", ["method"] ou [null, "method"] pour appeler une méthode
 | 
			
		||||
   * statique de la classe par défaut
 | 
			
		||||
   * - "->method", ["->method"] ou [anything, "->method"] pour appeler une
 | 
			
		||||
   * méthode de l'objet par défaut
 | 
			
		||||
   *
 | 
			
		||||
   * La classe et l'objet par défaut sont déterminés par le contexte.
 | 
			
		||||
   *
 | 
			
		||||
   * Si $method est un tableau, il peut contenir des éléments supplémentaires
 | 
			
		||||
   * qui sont considérés comme des arguments de l'appel, e.g:
 | 
			
		||||
   *     $context->callMethod(["MyClass", "method", "hello", "world"]);
 | 
			
		||||
   * est équivant à:
 | 
			
		||||
   *     MyClass::method("hello", "world");
 | 
			
		||||
   */
 | 
			
		||||
  function callMethod($method);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										28
									
								
								nur_src/data/expr/IExpr.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								nur_src/data/expr/IExpr.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,28 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\expr;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Interface IExpr: une expression à évaluer dans un contexte
 | 
			
		||||
 */
 | 
			
		||||
interface IExpr {
 | 
			
		||||
  /** schéma d'une représentation d'une expression sous forme de tableau */
 | 
			
		||||
  const SCHEMA = [
 | 
			
		||||
    "value" => [null, null, "définition de l'expression", true],
 | 
			
		||||
    "name" => [null, null, "identifiant de l'expression dans le modèle", true],
 | 
			
		||||
    "title" => [null, null, "description courte de l'expression, pour affichage", false,
 | 
			
		||||
      "desc" => "vaut [name] par défaut",
 | 
			
		||||
    ],
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /** obtenir la définition de l'expression, le cas échéant */
 | 
			
		||||
  function getValue(): ?string;
 | 
			
		||||
 | 
			
		||||
  /** obtenir l'identifiant de l'expression */
 | 
			
		||||
  function getName(): string;
 | 
			
		||||
 | 
			
		||||
  /** obtenir une description courte de l'expression, pour affichage */
 | 
			
		||||
  function getTitle(): string;
 | 
			
		||||
 | 
			
		||||
  /** évalue l'expression dans le contexte spécifié et retourne sa valeur */
 | 
			
		||||
  function eval(IContext $context);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										75
									
								
								nur_src/data/expr/SimpleContext.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								nur_src/data/expr/SimpleContext.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,75 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\expr;
 | 
			
		||||
 | 
			
		||||
use nur\A;
 | 
			
		||||
use nur\b\coll\BaseArray;
 | 
			
		||||
use nur\config;
 | 
			
		||||
use nur\func;
 | 
			
		||||
use nur\session;
 | 
			
		||||
 | 
			
		||||
class SimpleContext extends BaseArray implements IContext {
 | 
			
		||||
  /**
 | 
			
		||||
   * @return array une liste de sources {$name => $source} conformes au schéma
 | 
			
		||||
   * {@link IContext::SOURCE_SCHEMA}
 | 
			
		||||
   */
 | 
			
		||||
  protected function SOURCES(): array {
 | 
			
		||||
    return static::SOURCES;
 | 
			
		||||
  } const SOURCES = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @return array une liste d'expressions {$name => $expr} conformes au schéma
 | 
			
		||||
   * {@link IContext::EXPR_SCHEMA}
 | 
			
		||||
   */
 | 
			
		||||
  protected function EXPRS(): array {
 | 
			
		||||
    return static::EXPRS;
 | 
			
		||||
  } const EXPRS = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @return array une liste de conditions {$key => $cond} conformes au schéma
 | 
			
		||||
   * {@link IContext::COND_SCHEMA}
 | 
			
		||||
   */
 | 
			
		||||
  protected function CONDS(): array {
 | 
			
		||||
    return static::CONDS;
 | 
			
		||||
  } const CONDS = [];
 | 
			
		||||
 | 
			
		||||
  /** @var mixed l'objet sur lequel sont appliquées les appels de méthode */
 | 
			
		||||
  protected $object;
 | 
			
		||||
 | 
			
		||||
  function __construct(?array $data=null) {
 | 
			
		||||
    parent::__construct($data);
 | 
			
		||||
    $this->object = $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function getContextInfos(): array {
 | 
			
		||||
    return [
 | 
			
		||||
      "sources" => $this->SOURCES(),
 | 
			
		||||
      "exprs" => $this->EXPRS(),
 | 
			
		||||
      "conds" => $this->CONDS(),
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function getValue(string $pkey) {
 | 
			
		||||
    #XXX parcourir les sources
 | 
			
		||||
    return A::pget($this->data, $pkey);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function getSession(string $pkey) {
 | 
			
		||||
    return session::pget($pkey);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function getConfig(string $pkey) {
 | 
			
		||||
    return config::get($pkey);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function callMethod($method) {
 | 
			
		||||
    func::ensure_func($method, $this->object, $args);
 | 
			
		||||
    return func::call($method, ...$args);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ## ArrayAccess
 | 
			
		||||
  function has($key): bool { return $this->_has($key); }
 | 
			
		||||
  function &get($key, $default=null) { return $this->_get($key, $default); }
 | 
			
		||||
  function set($key, $value): self { return $this->_set($key, $value); }
 | 
			
		||||
  function add($value): self { return $this->_set(null, $value); }
 | 
			
		||||
  function del($key): self { return $this->_del($key); }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								nur_src/data/flow/IStateMachine.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								nur_src/data/flow/IStateMachine.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\flow;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Interface IStateMachine: interface pour une machine à état
 | 
			
		||||
 */
 | 
			
		||||
interface IStateMachine {
 | 
			
		||||
  /** obtenir l'état courant */
 | 
			
		||||
  function get_current_state(): array;
 | 
			
		||||
 | 
			
		||||
  /** obtenir la liste des actions possible à partir de l'état courant */
 | 
			
		||||
  function get_next_actions(): array;
 | 
			
		||||
 | 
			
		||||
  /** faire l'action spécifiée */
 | 
			
		||||
  function perform_action(string $action, ?array $data=null): void;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								nur_src/data/template/ITemplate.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								nur_src/data/template/ITemplate.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\template;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Interface ITemplate: un modèle de document
 | 
			
		||||
 */
 | 
			
		||||
interface ITemplate {
 | 
			
		||||
  /**
 | 
			
		||||
   * instancier le modèle et "retourner" les données.
 | 
			
		||||
   *
 | 
			
		||||
   * la façon dont les données sont "retournées" dépend de l'implémentation
 | 
			
		||||
   */
 | 
			
		||||
  function apply();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										204
									
								
								nur_src/data/template/InterpTemplate.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								nur_src/data/template/InterpTemplate.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,204 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\template;
 | 
			
		||||
 | 
			
		||||
use nur\A;
 | 
			
		||||
use nur\b\io\IOException;
 | 
			
		||||
use nur\data\expr\SimpleContext;
 | 
			
		||||
use nur\func;
 | 
			
		||||
use nur\session;
 | 
			
		||||
use ZipArchive;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class InterpTemplate: réimplémentation de l'ancienne classe interp
 | 
			
		||||
 */
 | 
			
		||||
class InterpTemplate extends SimpleContext implements ITemplate {
 | 
			
		||||
  use TTemplate;
 | 
			
		||||
 | 
			
		||||
  function __construct(?string $text=null, $data=null, $quote=true, bool $allowPattern=true) {
 | 
			
		||||
    if ($data === null) $data = [];
 | 
			
		||||
    parent::__construct($data);
 | 
			
		||||
    $this->context = $this;
 | 
			
		||||
    $this->setText($text, $data, $quote, $allowPattern);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @var string */
 | 
			
		||||
  protected $text;
 | 
			
		||||
  /** @var bool|array */
 | 
			
		||||
  protected $quote;
 | 
			
		||||
  /** @var bool */
 | 
			
		||||
  protected $allowPattern;
 | 
			
		||||
 | 
			
		||||
  function setText(?string $text, $data=null, $quote=true, bool $allowPattern=true): void {
 | 
			
		||||
    $this->text = $text;
 | 
			
		||||
    if ($data !== null) {
 | 
			
		||||
      $this->data = A::with($data);
 | 
			
		||||
      $this->quote = $quote;
 | 
			
		||||
      $this->allowPattern = $allowPattern;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Traiter la valeur $value selon la valeur de $quote
 | 
			
		||||
   *
 | 
			
		||||
   * D'abord la transformer en chaine, puis:
 | 
			
		||||
   * - si $quote===true, la mettre en échappement avec htmlspecialchars()
 | 
			
		||||
   * - si $quote===false ou null, ne pas la mettre en échappement
 | 
			
		||||
   * - sinon, ce doit être une fonction qui met la valeur en échappement
 | 
			
		||||
   */
 | 
			
		||||
  private static final function quote($value, $quote) {
 | 
			
		||||
    if (A::is_array($value)) $value = print_r(A::with($value), true);
 | 
			
		||||
    elseif (!is_string($value)) $value = strval($value);
 | 
			
		||||
    if ($quote === true) return htmlspecialchars($value);
 | 
			
		||||
    else if ($quote === false || $quote === null) return $value;
 | 
			
		||||
    else return func::call($quote, $value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Traiter la valeur $value selon la valeur de $quote. Le traitement final est
 | 
			
		||||
   * fait avec la méthode quote()
 | 
			
		||||
   *
 | 
			
		||||
   * - Si $quote est un tableau, alors $quote[$name] est la valeur utilisée pour
 | 
			
		||||
   *   décider comment traiter la valeur et sa valeur par défaut est true.
 | 
			
		||||
   * - Sinon prendre la valeur $quote telle quelle
 | 
			
		||||
   */
 | 
			
		||||
  private static final function quote_nv(string $name, $value, $quote) {
 | 
			
		||||
    if (is_array($quote)) {
 | 
			
		||||
      if (isset($quote[$name])) {
 | 
			
		||||
        $value = self::quote($value, $quote[$name]);
 | 
			
		||||
      } else {
 | 
			
		||||
        # quoter par défaut quand on fournit un tableau
 | 
			
		||||
        $value = self::quote($value, true);
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      $value = self::quote($value, $quote);
 | 
			
		||||
    }
 | 
			
		||||
    return $value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Résoudre la valeur du nom $name
 | 
			
		||||
   * - Si $name est de la forme '+keyp', prendre la valeur dans la configuration
 | 
			
		||||
   * - Si $name est de la forme '*keyp', prendre la valeur dans la session
 | 
			
		||||
   * - Sinon, $name est le chemin de clé dans le tableau $values
 | 
			
		||||
   */
 | 
			
		||||
  private function resolve(string $name, ?array $values, $quote) {
 | 
			
		||||
    switch (substr($name, 0, 1)) {
 | 
			
		||||
    case "+":
 | 
			
		||||
      return $this->getConfig(substr($name, 1));
 | 
			
		||||
    case "*":
 | 
			
		||||
      if (!session::started()) return "";
 | 
			
		||||
      return $this->getSession(substr($name, 1));
 | 
			
		||||
    default:
 | 
			
		||||
      $value = A::pget_s($values, $name, "");
 | 
			
		||||
      $value = self::quote_nv($name, $value, $quote);
 | 
			
		||||
      return $value;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function apply() {
 | 
			
		||||
    $text = $this->text;
 | 
			
		||||
    $data = $this->data;
 | 
			
		||||
    $quote = $this->quote;
 | 
			
		||||
    ## d'abord les remplacements complexes
 | 
			
		||||
    if ($this->allowPattern) {
 | 
			
		||||
      if ($data !== null) {
 | 
			
		||||
        $names = array_keys($data);
 | 
			
		||||
        $name_keys_pattern = '('.implode("|", $names).')((?:\.[a-zA-Z0-9_]+)*)';
 | 
			
		||||
        $or_name_pattern = '|(?:(?:'.implode("|", $names).')(?:\.[a-zA-Z0-9_]+)*)';
 | 
			
		||||
      } else {
 | 
			
		||||
        $name_keys_pattern = null;
 | 
			
		||||
        $or_name_pattern = '';
 | 
			
		||||
      }
 | 
			
		||||
      # patterns conditionnels
 | 
			
		||||
      # autoriser un niveau de variable complexe à l'intérieur de la condition, i.e {{if(cond){{var.value}}}}
 | 
			
		||||
      $pattern = '/\{\{(if|unless)\(((?:[+*][a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)*)'.$or_name_pattern.')\)((?:[^{}]*(?:\{\{[^{}]*\}\})?(?:\{[^{}]*\})?)*)\}\}/s';
 | 
			
		||||
      $text = preg_replace_callback($pattern, function($matches) use($data, $quote) {
 | 
			
		||||
        $cond = $this->resolve($matches[2], $data, $quote);
 | 
			
		||||
        if ($matches[1] == "if") {
 | 
			
		||||
          if ($cond) $value = $matches[3];
 | 
			
		||||
          else return "";
 | 
			
		||||
        } elseif ($matches[1] == "unless") {
 | 
			
		||||
          if (!$cond) $value = $matches[3];
 | 
			
		||||
          else return "";
 | 
			
		||||
        } else {
 | 
			
		||||
          return $matches[0];
 | 
			
		||||
        }
 | 
			
		||||
        $value = self::xml($value, $data, $quote);
 | 
			
		||||
        return $value;
 | 
			
		||||
      }, $text);
 | 
			
		||||
      # valeurs de config
 | 
			
		||||
      $pattern = '/\{\{(\+[a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)*)\}\}/s';
 | 
			
		||||
      $text = preg_replace_callback($pattern, function($matches) use($data, $quote) {
 | 
			
		||||
        $name = $matches[1];
 | 
			
		||||
        $value = $this->resolve($name, $data, $quote);
 | 
			
		||||
        return $value;
 | 
			
		||||
      }, $text);
 | 
			
		||||
      # valeurs de la session
 | 
			
		||||
      $pattern = '/\{\{(\*[a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)*)\}\}/s';
 | 
			
		||||
      $text = preg_replace_callback($pattern, function($matches) use($data, $quote) {
 | 
			
		||||
        $name = $matches[1];
 | 
			
		||||
        $value = $this->resolve($name, $data, $quote);
 | 
			
		||||
        return $value;
 | 
			
		||||
      }, $text);
 | 
			
		||||
      if ($data !== null) {
 | 
			
		||||
        # indirections
 | 
			
		||||
        $pattern = '/\{\{'.$name_keys_pattern.'\}\}/s';
 | 
			
		||||
        $text = preg_replace_callback($pattern, function($matches) use($data, $quote) {
 | 
			
		||||
          $name = $matches[1];
 | 
			
		||||
          $value = $this->resolve("$name$matches[2]", $data, $quote);
 | 
			
		||||
          return $value;
 | 
			
		||||
        }, $text);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if ($data !== null) {
 | 
			
		||||
      ## ensuite les remplacements simples
 | 
			
		||||
      $froms = array();
 | 
			
		||||
      $tos = array();
 | 
			
		||||
      foreach ($data as $name => $value) {
 | 
			
		||||
        $froms[] = "{".$name."}";
 | 
			
		||||
        $tos[] = self::quote_nv($name, $value, $quote);
 | 
			
		||||
      }
 | 
			
		||||
      $text = str_replace($froms, $tos, $text);
 | 
			
		||||
    }
 | 
			
		||||
    return $text;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function xml(?string $text, $data=null, $quote=true, bool $allowPattern=true): string {
 | 
			
		||||
    $this->setText($text, $data, $quote, $allowPattern);
 | 
			
		||||
    return $this->apply();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Comme {@link xml()} mais les valeurs ne sont pas mises en échappement par
 | 
			
		||||
   * défaut.
 | 
			
		||||
   */
 | 
			
		||||
  function string(?string $text, $data=null): string {
 | 
			
		||||
    return $this->xml($text, $data, false);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** Comme {@link xml()} pour un fichier au format LibreOffice Writer */
 | 
			
		||||
  function odt(string $file, $data=null, $quote=true): void {
 | 
			
		||||
    $zip = new ZipArchive();
 | 
			
		||||
    $error = $zip->open($file);
 | 
			
		||||
    if ($error !== true) {
 | 
			
		||||
      throw new IOException("$file: error $error occured on open");
 | 
			
		||||
    }
 | 
			
		||||
    $oldContent = $zip->getFromName("content.xml");
 | 
			
		||||
    if ($oldContent !== false) {
 | 
			
		||||
      $newContent = $this->xml($oldContent, $data, $quote);
 | 
			
		||||
      if ($newContent !== $oldContent) {
 | 
			
		||||
        $zip->deleteName("content.xml");
 | 
			
		||||
        $zip->addFromString("content.xml", $newContent);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $oldStyles = $zip->getFromName("styles.xml");
 | 
			
		||||
    if ($oldStyles !== false) {
 | 
			
		||||
      $newStyles = $this->xml($oldStyles, null, $quote);
 | 
			
		||||
      if ($newStyles != $oldStyles) {
 | 
			
		||||
        $zip->deleteName("styles.xml");
 | 
			
		||||
        $zip->addFromString("styles.xml", $newStyles);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $zip->close();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										62
									
								
								nur_src/data/template/StreamTemplate.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								nur_src/data/template/StreamTemplate.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,62 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\template;
 | 
			
		||||
 | 
			
		||||
use nur\b\io\FileReader;
 | 
			
		||||
use nur\b\io\IReader;
 | 
			
		||||
use nur\b\io\IWriter;
 | 
			
		||||
use nur\b\ValueException;
 | 
			
		||||
use nur\data\expr\SimpleContext;
 | 
			
		||||
 | 
			
		||||
class StreamTemplate extends SimpleContext implements ITemplate {
 | 
			
		||||
  use TTemplate;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * retourner un nom de fichier à utiliser par défaut en entrée si l'argument
 | 
			
		||||
   * $input n'est pas spécifié.
 | 
			
		||||
   */
 | 
			
		||||
  protected function INPUT(): ?string {
 | 
			
		||||
    return static::INPUT;
 | 
			
		||||
  } const INPUT = null;
 | 
			
		||||
 | 
			
		||||
  /** @var IReader */
 | 
			
		||||
  private $input;
 | 
			
		||||
 | 
			
		||||
  /** @var IWriter */
 | 
			
		||||
  private $output;
 | 
			
		||||
 | 
			
		||||
  /** @var bool */
 | 
			
		||||
  private $autoClose;
 | 
			
		||||
 | 
			
		||||
  function __construct(IWriter $output, ?IReader $input=null, bool $autoClose=true) {
 | 
			
		||||
    parent::__construct();
 | 
			
		||||
    $this->input = $input;
 | 
			
		||||
    $this->output = $output;
 | 
			
		||||
    $this->autoClose = $autoClose;
 | 
			
		||||
    $this->context = $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function ensureInput(): IReader {
 | 
			
		||||
    $input = $this->input;
 | 
			
		||||
    if ($input === null) {
 | 
			
		||||
      $input = $this->INPUT();
 | 
			
		||||
      if ($input === null) {
 | 
			
		||||
        throw new ValueException("input is required");
 | 
			
		||||
      }
 | 
			
		||||
      $input = new FileReader($input);
 | 
			
		||||
    }
 | 
			
		||||
    return $input;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** retourner true */
 | 
			
		||||
  function apply() {
 | 
			
		||||
    $context = $this->getContext();
 | 
			
		||||
    $input = $this->ensureInput();
 | 
			
		||||
    $output = $this->output;
 | 
			
		||||
    foreach ($input as $line) {
 | 
			
		||||
      $line = $this->applyRules($line, $context);
 | 
			
		||||
      $output->wnl($line);
 | 
			
		||||
    }
 | 
			
		||||
    $output->close($this->autoClose);
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										34
									
								
								nur_src/data/template/StringTemplate.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								nur_src/data/template/StringTemplate.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\template;
 | 
			
		||||
 | 
			
		||||
use nur\data\expr\SimpleContext;
 | 
			
		||||
 | 
			
		||||
class StringTemplate extends SimpleContext implements ITemplate {
 | 
			
		||||
  use TTemplate;
 | 
			
		||||
 | 
			
		||||
  /** @return string le texte du modèle */
 | 
			
		||||
  protected function TEXT(): string {
 | 
			
		||||
    $text = $this->text;
 | 
			
		||||
    if ($text === null) $text = static::TEXT;
 | 
			
		||||
    return $text;
 | 
			
		||||
  } const TEXT = "";
 | 
			
		||||
 | 
			
		||||
  function __construct(?array $data=null) {
 | 
			
		||||
    parent::__construct($data);
 | 
			
		||||
    $this->context = $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @var string */
 | 
			
		||||
  protected $text;
 | 
			
		||||
 | 
			
		||||
  function setText(string $text): void {
 | 
			
		||||
    $this->text = $text;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** retourner le texte avec les variables renseignées */
 | 
			
		||||
  function apply() {
 | 
			
		||||
    $text = $this->TEXT();
 | 
			
		||||
    $context = $this->getContext();
 | 
			
		||||
    return $this->applyRules($text, $context);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								nur_src/data/template/TTemplate.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								nur_src/data/template/TTemplate.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\template;
 | 
			
		||||
 | 
			
		||||
use nur\base;
 | 
			
		||||
use nur\data\expr\GenericExpr;
 | 
			
		||||
use nur\data\expr\IContext;
 | 
			
		||||
 | 
			
		||||
trait TTemplate {
 | 
			
		||||
  /** @var IContext */
 | 
			
		||||
  protected $context;
 | 
			
		||||
 | 
			
		||||
  function getContext(): IContext {
 | 
			
		||||
    return $this->context;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function setContext(IContext $context): void {
 | 
			
		||||
    $this->context = $context;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function applyRules(string $text, IContext $context) {
 | 
			
		||||
    foreach ($this->EXPRS() as $name => $expr) {
 | 
			
		||||
      $value = GenericExpr::with($expr, $name)->eval($context);
 | 
			
		||||
      if (!base::is_undef($value)) $text = str_replace($name, $value, $text);
 | 
			
		||||
    }
 | 
			
		||||
    #XXX ajouter le support des expressions conditionnelles. traiter ligne par
 | 
			
		||||
    # ligne s'il y a des expressions conditionnelles
 | 
			
		||||
    return $text;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										119
									
								
								nur_src/data/types/AbstractComplexType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								nur_src/data/types/AbstractComplexType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,119 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
use nur\md;
 | 
			
		||||
use nur\types;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class CompositeType: squelette d'implémentation d'un type complexe
 | 
			
		||||
 *
 | 
			
		||||
 * un type complexe est la composition de plusieurs types simples. il n'est pas
 | 
			
		||||
 * prévu pour l'analyse ou le formatage, uniquement l'utilisation de la méthode
 | 
			
		||||
 * {@link verifix()}
 | 
			
		||||
 */
 | 
			
		||||
abstract class AbstractComplexType extends AbstractSimpleType {
 | 
			
		||||
  use _Tparametrable;
 | 
			
		||||
 | 
			
		||||
  const ALLOW_NULL = true;
 | 
			
		||||
 | 
			
		||||
  const COMPONENT_SCHEMA = [
 | 
			
		||||
    "name" => ["string", null, "nom de la composante", "required" => true],
 | 
			
		||||
    "type" => ["mixed", null, "type de la composante", "required" => true],
 | 
			
		||||
    "title" => ["?string", null, "libellé de la composante"],
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /** @var array la définition des composantes */
 | 
			
		||||
  const COMPONENTS = [];
 | 
			
		||||
 | 
			
		||||
  /** @var string le séparateur entre chaque composant utilisé lors du formatage */
 | 
			
		||||
  const SEPARATOR = " ";
 | 
			
		||||
 | 
			
		||||
  /** @var string le pattern qui matche le séparateur entre les composants */
 | 
			
		||||
  const SEPARATOR_PATTERN = '/^\s+/';
 | 
			
		||||
 | 
			
		||||
  #############################################################################
 | 
			
		||||
 | 
			
		||||
  /** @var array */
 | 
			
		||||
  protected $components;
 | 
			
		||||
 | 
			
		||||
  function setComponents(array $components): self {
 | 
			
		||||
    foreach ($components as $key => &$component) {
 | 
			
		||||
      md::ensure_schema($component, self::COMPONENT_SCHEMA, $key, false);
 | 
			
		||||
      $type = $component["type"];
 | 
			
		||||
      if (is_array($type) && count($type) == 1) {
 | 
			
		||||
        $type[] = $component;
 | 
			
		||||
      }
 | 
			
		||||
      $component["type"] = types::get($type);
 | 
			
		||||
    }; unset($component);
 | 
			
		||||
    $this->components = $components;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const PARAMETRABLE_PARAMS_SCHEMA = [
 | 
			
		||||
    "separator" => ["string", null, "séparateur pour le format"],
 | 
			
		||||
    "separator_pattern" => ["string", null, "séparateur pour l'analyse"],
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /** @var string */
 | 
			
		||||
  protected $ppSeparator;
 | 
			
		||||
 | 
			
		||||
  /** @var string */
 | 
			
		||||
  protected $ppSeparatorPattern;
 | 
			
		||||
 | 
			
		||||
  #############################################################################
 | 
			
		||||
 | 
			
		||||
  function __construct(?array $params=null) {
 | 
			
		||||
    $this->setComponents(static::COMPONENTS);
 | 
			
		||||
    $this->ppSeparator = static::SEPARATOR;
 | 
			
		||||
    $this->ppSeparatorPattern = static::SEPARATOR_PATTERN;
 | 
			
		||||
    parent::__construct($params);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function getClass(): string {
 | 
			
		||||
    return "string";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function canFormat(): bool {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function canParse(): bool {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function parse(string &$input, ?array $params=null) {
 | 
			
		||||
    $data = $input;
 | 
			
		||||
    $values = [];
 | 
			
		||||
    foreach ($this->components as $component) {
 | 
			
		||||
      $cname = $component["name"];
 | 
			
		||||
      # cette fonction sert uniquement à avoir la valeur dans le bon type
 | 
			
		||||
      /** @var IType $ctype */
 | 
			
		||||
      $ctype = $component["type"];
 | 
			
		||||
      if ($data !== "") {
 | 
			
		||||
        $value = $ctype->parse($data);
 | 
			
		||||
      } elseif ($ctype->isAllowEmpty()) {
 | 
			
		||||
        $value = "";
 | 
			
		||||
        $ctype->verifixReplaceEmpty($value);
 | 
			
		||||
      } else {
 | 
			
		||||
        $value = false;
 | 
			
		||||
      }
 | 
			
		||||
      if ($value === false) return false;
 | 
			
		||||
      if ($ctype instanceof AbstractSimpleType) $ctype->verifixConvert($value);
 | 
			
		||||
      $values[$cname] = $value;
 | 
			
		||||
      $data = preg_replace(static::SEPARATOR_PATTERN, "", $data);
 | 
			
		||||
    }
 | 
			
		||||
    $input = $data;
 | 
			
		||||
    return $values;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function verifixConvert(&$value): bool {
 | 
			
		||||
    $parts = [];
 | 
			
		||||
    foreach ($this->components as $component) {
 | 
			
		||||
      $cname = $component["name"];
 | 
			
		||||
      $ctype = $component["type"];
 | 
			
		||||
      $parts[] = $ctype->format($value[$cname]);
 | 
			
		||||
    }
 | 
			
		||||
    $value = implode($this->ppSeparator, $parts);
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										255
									
								
								nur_src/data/types/AbstractCompositeType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										255
									
								
								nur_src/data/types/AbstractCompositeType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,255 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
use Exception;
 | 
			
		||||
use nur\A;
 | 
			
		||||
use nur\b\ValueException;
 | 
			
		||||
use nur\md;
 | 
			
		||||
use nur\types;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class CompositeType: squelette d'implémentation d'un type composite
 | 
			
		||||
 *
 | 
			
		||||
 * un type composite permet de gérer une valeur qui est stockée dans plusieurs
 | 
			
		||||
 * champs de types différents
 | 
			
		||||
 *
 | 
			
		||||
 * Par convention, les classes dérivées sont nommées C{name}Type
 | 
			
		||||
 */
 | 
			
		||||
abstract class AbstractCompositeType extends AbstractType {
 | 
			
		||||
  use _Tparametrable;
 | 
			
		||||
 | 
			
		||||
  const ALLOW_NULL = true;
 | 
			
		||||
 | 
			
		||||
  const COMPONENT_SCHEMA = [
 | 
			
		||||
    "name" => ["string", null, "nom de la composante", "required" => true],
 | 
			
		||||
    "type" => ["mixed", null, "type de la composante", "required" => true],
 | 
			
		||||
    "key" => ["?string", null, "nom de la clé de la composante dans l'objet destination"],
 | 
			
		||||
    "title" => ["?string", null, "libellé de la composante"],
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /** @var array messages standards */
 | 
			
		||||
  const CMESSAGES = [
 | 
			
		||||
    "empty" => "{cname}: cette valeur ne doit pas être vide",
 | 
			
		||||
    "false" => "{cname}: cette valeur ne doit pas être false",
 | 
			
		||||
    "null" => "{cname}: cette valeur ne doit pas être null",
 | 
			
		||||
    "invalid" => "{cname}: {value}: cette valeur est invalide",
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /** @var array la définition des composantes */
 | 
			
		||||
  const COMPONENTS = [];
 | 
			
		||||
 | 
			
		||||
  /** @var string le séparateur entre chaque composant utilisé lors du formatage */
 | 
			
		||||
  const SEPARATOR = " ";
 | 
			
		||||
 | 
			
		||||
  /** @var string le pattern qui matche le séparateur entre les composants */
 | 
			
		||||
  const SEPARATOR_PATTERN = '/^\s+/';
 | 
			
		||||
 | 
			
		||||
  private static function itype($type): IType { return $type; }
 | 
			
		||||
 | 
			
		||||
  #############################################################################
 | 
			
		||||
 | 
			
		||||
  /** @var array */
 | 
			
		||||
  protected $components;
 | 
			
		||||
 | 
			
		||||
  function setComponents(array $components): self {
 | 
			
		||||
    foreach ($components as $key => &$component) {
 | 
			
		||||
      md::ensure_schema($component, self::COMPONENT_SCHEMA, $key, false);
 | 
			
		||||
      A::replace_n_indirect($component, "key", "name");
 | 
			
		||||
      $type = $component["type"];
 | 
			
		||||
      if (is_array($type) && count($type) == 1) {
 | 
			
		||||
        $type[] = $component;
 | 
			
		||||
      }
 | 
			
		||||
      $component["type"] = types::get($type);
 | 
			
		||||
    }; unset($component);
 | 
			
		||||
    $this->components = $components;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const PARAMETRABLE_PARAMS_SCHEMA = [
 | 
			
		||||
    "ckeys" => ["array", null, "clés à utiliser pour les composantes"],
 | 
			
		||||
    "separator" => ["string", null, "séparateur pour le format"],
 | 
			
		||||
    "separator_pattern" => ["string", null, "séparateur pour l'analyse"],
 | 
			
		||||
    "cmessages" => ["array", null, "messages à retourner en cas d'erreur pour les composantes"],
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  function pp_setCkeys(array $ckeys): self {
 | 
			
		||||
    if (A::is_seq($ckeys)) {
 | 
			
		||||
      $index = 0;
 | 
			
		||||
      foreach ($this->components as &$component) {
 | 
			
		||||
        $component["key"] = $ckeys[$index++];
 | 
			
		||||
      }; unset($component);
 | 
			
		||||
    } else {
 | 
			
		||||
      foreach ($ckeys as $cname => $ckey) {
 | 
			
		||||
        $this->components[$cname]["key"] = $ckey;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @var string */
 | 
			
		||||
  protected $ppSeparator;
 | 
			
		||||
 | 
			
		||||
  /** @var string */
 | 
			
		||||
  protected $ppSeparatorPattern;
 | 
			
		||||
 | 
			
		||||
  /** @var array messages à retourner en cas d'erreur */
 | 
			
		||||
  protected $ppCmessages;
 | 
			
		||||
 | 
			
		||||
  #############################################################################
 | 
			
		||||
 | 
			
		||||
  function __construct(?array $params=null) {
 | 
			
		||||
    $this->setComponents(static::COMPONENTS);
 | 
			
		||||
    $this->ppSeparator = static::SEPARATOR;
 | 
			
		||||
    $this->ppSeparatorPattern = static::SEPARATOR_PATTERN;
 | 
			
		||||
    parent::__construct($params);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * retourner une liste {cname => component} des composantes, qui sont chacune
 | 
			
		||||
   * conformes au schéma {@link COMPONENT_SCHEMA}
 | 
			
		||||
   */
 | 
			
		||||
  function getComponents(): array {
 | 
			
		||||
    return $this->components;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function _getComponent(string $cname) {
 | 
			
		||||
    return ValueException::check_nn(A::get($this->components, $cname), "$cname: invalid component");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** obtenir le type d'une composante en particulier */
 | 
			
		||||
  function getCtype(string $cname): IType {
 | 
			
		||||
    return $this->_getComponent($cname)["type"];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function _getCvalue(array $component, $value) {
 | 
			
		||||
    return $value[$component["key"]];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function _setCvalue(array $component, &$value, $cvalue): void {
 | 
			
		||||
    $value[$component["key"]] = $cvalue;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** obtenir la valeur d'une composante en particulier */
 | 
			
		||||
  function getCvalue(string $cname, $value) {
 | 
			
		||||
    return $this->_getCvalue($this->_getComponent($cname), $value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** spécifier la valeur d'une composante en particulier */
 | 
			
		||||
  function setCvalue(string $cname, &$value, $cvalue): void {
 | 
			
		||||
    $this->_setCvalue($this->_getComponent($cname), $value, $cvalue);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function getClass(): string {
 | 
			
		||||
    return "array";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function format($value, ?array $params=null): string {
 | 
			
		||||
    $parts = [];
 | 
			
		||||
    foreach ($this->components as $component) {
 | 
			
		||||
      $ctype = self::itype($component["type"]);
 | 
			
		||||
      $parts[] = $ctype->format($this->_getCvalue($component, $value));
 | 
			
		||||
    }
 | 
			
		||||
    return implode(static::SEPARATOR, $parts);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function parse(string &$input, ?array $params=null) {
 | 
			
		||||
    $data = $input;
 | 
			
		||||
    $values = [];
 | 
			
		||||
    foreach ($this->components as $component) {
 | 
			
		||||
      $ckey = $component["key"];
 | 
			
		||||
      $ctype = self::itype($component["type"]);
 | 
			
		||||
      $isaSimpleType = $ctype instanceof AbstractSimpleType;
 | 
			
		||||
      if ($data !== "") {
 | 
			
		||||
        $value = $ctype->parse($data);
 | 
			
		||||
      } elseif ($isaSimpleType && $ctype->isAllowParseEmpty()) {
 | 
			
		||||
        $value = null;
 | 
			
		||||
      } elseif (!$isaSimpleType && $ctype->isAllowEmpty()) {
 | 
			
		||||
        $value = "";
 | 
			
		||||
        $ctype->verifixReplaceEmpty($value);
 | 
			
		||||
      } else {
 | 
			
		||||
        $value = false;
 | 
			
		||||
      }
 | 
			
		||||
      if ($value === false) return false;
 | 
			
		||||
      if ($isaSimpleType) $ctype->verifixConvert($value);
 | 
			
		||||
      $values[$ckey] = $value;
 | 
			
		||||
      $data = preg_replace(static::SEPARATOR_PATTERN, "", $data);
 | 
			
		||||
    }
 | 
			
		||||
    $input = $data;
 | 
			
		||||
    return $values;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #############################################################################
 | 
			
		||||
 | 
			
		||||
  protected static function _update_error(array &$cresult, string $error_code, array $component, ?array $cmessages): void {
 | 
			
		||||
    $cresult["cname"] = $component["name"];
 | 
			
		||||
    $cresult["ckey"] = $component["key"];
 | 
			
		||||
    if ($cmessages === null) $cmessages = static::CMESSAGES;
 | 
			
		||||
    self::_set_error($cresult, $error_code, $cmessages);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * fonction de support: convertir le résultat de l'analyse de la chaine dans
 | 
			
		||||
   * le type approprié. retourner true si la valeur est valide, false si la
 | 
			
		||||
   * valeur est invalide (bien que peut-être syntaxiquement correcte)
 | 
			
		||||
   */
 | 
			
		||||
  function verifixConvert(&$value): bool {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * retourner une description de la valeur, utilisable le cas échéant dans les
 | 
			
		||||
   * messages d'erreur
 | 
			
		||||
   */
 | 
			
		||||
  protected function getValueDesc($orig, $parsed, $value): ?string {
 | 
			
		||||
    if (is_array($value)) {
 | 
			
		||||
      $parts = [];
 | 
			
		||||
      foreach ($this->components as $component) {
 | 
			
		||||
        $parts[] = A::get($value, $component["key"]);
 | 
			
		||||
      }
 | 
			
		||||
      return implode("-", $parts);
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function _verifix(&$value, array &$result=null): void {
 | 
			
		||||
    $orig = $value;
 | 
			
		||||
    if ($this->verifixNoParse($value, $result)) return;
 | 
			
		||||
    if (is_string($value)) {
 | 
			
		||||
      try {
 | 
			
		||||
        $input = $unparsed = $value;
 | 
			
		||||
        $cvalues = $this->parse($input);
 | 
			
		||||
        $parsed = substr($unparsed, 0, strlen($unparsed) - strlen($input));
 | 
			
		||||
        if (($input === "" || !$this->ppParseAll) && $this->verifixConvert($cvalues)) {
 | 
			
		||||
          $value = $cvalues;
 | 
			
		||||
          self::result_valid($result, $value, $orig, $parsed, $input);
 | 
			
		||||
        } else {
 | 
			
		||||
          $orig_desc = $this->getValueDesc($orig, $parsed, $cvalues);
 | 
			
		||||
          $value = self::result_invalid($result, "invalid", $orig, $orig_desc, $value, $input, $this->ppMessages, null);
 | 
			
		||||
        }
 | 
			
		||||
      } catch (Exception $e) {
 | 
			
		||||
        $orig_desc = $this->getValueDesc($orig, false, $value);
 | 
			
		||||
        $value = self::result_invalid($result, "invalid", $orig, $orig_desc, false, false, $this->ppMessages, $e);
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      $cresults = [];
 | 
			
		||||
      $cvalid = true;
 | 
			
		||||
      foreach ($this->components as $component) {
 | 
			
		||||
        $ckey = $component["key"];
 | 
			
		||||
        $ctype = self::itype($component["type"]);
 | 
			
		||||
        $ctype->verifix($value[$ckey], $cresult);
 | 
			
		||||
        if (!$cresult["valid"]) {
 | 
			
		||||
          $cvalid = false;
 | 
			
		||||
          self::_update_error($cresult, $cresult["error_code"], $component, $this->ppCmessages);
 | 
			
		||||
        }
 | 
			
		||||
        $cresults[$ckey] = $cresult;
 | 
			
		||||
      }
 | 
			
		||||
      if ($cvalid && $this->verifixConvert($value)) {
 | 
			
		||||
        self::result_valid($result, $value, $orig, false, false);
 | 
			
		||||
      } else {
 | 
			
		||||
        $orig_desc = $this->getValueDesc($orig, false, $value);
 | 
			
		||||
        $value = self::result_invalid($result, "invalid", $orig, $orig_desc, false, false, $this->ppMessages, null);
 | 
			
		||||
      }
 | 
			
		||||
      $result["cresults"] = $cresults;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										128
									
								
								nur_src/data/types/AbstractSimpleType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								nur_src/data/types/AbstractSimpleType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,128 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
use Exception;
 | 
			
		||||
use nur\A;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class SimpleType: squelette d'implémentation pour un type simple
 | 
			
		||||
 */
 | 
			
		||||
abstract class AbstractSimpleType extends AbstractType {
 | 
			
		||||
  use _Tparametrable;
 | 
			
		||||
 | 
			
		||||
  /** @var bool suffit-il de vérifier la classe pour être sûr du type? */
 | 
			
		||||
  const CHECK_CLASS = true;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @var bool est-il autorisé que l'analyse ne consomme aucune donnée et
 | 
			
		||||
   * retourne null?
 | 
			
		||||
   */
 | 
			
		||||
  const ALLOW_PARSE_EMPTY = false;
 | 
			
		||||
 | 
			
		||||
  const PARAMETRABLE_PARAMS_SCHEMA = [
 | 
			
		||||
    "allow_parse_empty" => ["bool", null, "une analyse qui ne consomme aucun caractère est-elle autorisée?"],
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /** @var bool */
 | 
			
		||||
  protected $ppAllowParseEmpty;
 | 
			
		||||
 | 
			
		||||
  function isAllowParseEmpty(): bool {
 | 
			
		||||
    return $this->ppAllowParseEmpty;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #############################################################################
 | 
			
		||||
 | 
			
		||||
  function __construct(?array $params=null) {
 | 
			
		||||
    A::replace_n($params, "allow_parse_empty", static::ALLOW_PARSE_EMPTY);
 | 
			
		||||
    parent::__construct($params);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #############################################################################
 | 
			
		||||
 | 
			
		||||
  function canFormat(): bool {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function format($value): string {
 | 
			
		||||
    return strval($value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #############################################################################
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * fonction de support: mettre en forme la valeur, qui est déjà dans le bon
 | 
			
		||||
   * type. @return false s'il n'est pas possible de mettre en forme la valeur.
 | 
			
		||||
   */
 | 
			
		||||
  function beforeCheckInstance(&$value): bool {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * fonction de support: mettre en forme la valeur, qui est déjà dans le bon type
 | 
			
		||||
   */
 | 
			
		||||
  function afterCheckInstance(&$value): void {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** fonction de support: traiter les valeurs qui sont déjà dans le bon type */
 | 
			
		||||
  protected function verifixCheckClass(&$value, $orig, array &$result=null): bool {
 | 
			
		||||
    if (!static::CHECK_CLASS) return false;
 | 
			
		||||
    if (!$this->beforeCheckInstance($value)) return false;
 | 
			
		||||
    if (!$this->isInstance($value)) return false;
 | 
			
		||||
    $this->afterCheckInstance($value);
 | 
			
		||||
    self::result_valid($result, $value, $orig);
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * fonction de support: convertir le résultat de l'analyse de la chaine dans
 | 
			
		||||
   * le type approprié. retourner true si la valeur est valide, false si la
 | 
			
		||||
   * valeur est invalide (bien que peut-être syntaxiquement correcte)
 | 
			
		||||
   *
 | 
			
		||||
   * NB: si $this->allowParseEmpty==true, alors $value peut être null. il faut
 | 
			
		||||
   * donc toujours tester ce cas, parce que la classe peut-être instanciée avec
 | 
			
		||||
   * ce paramètre
 | 
			
		||||
   */
 | 
			
		||||
  function verifixConvert(&$value): bool {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * retourner une description de la valeur, utilisable le cas échéant dans les
 | 
			
		||||
   * messages d'erreur
 | 
			
		||||
   */
 | 
			
		||||
  protected function getValueDesc($orig, $parsed, $value): ?string {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * fonction de support: analyser la chaine et retourner la valeur dans le type
 | 
			
		||||
   * approprié
 | 
			
		||||
   */
 | 
			
		||||
  protected function verifixParse(string $value, $orig, ?array &$result=null) {
 | 
			
		||||
    try {
 | 
			
		||||
      $input = $unparsed = $value;
 | 
			
		||||
      $value = $this->parse($input);
 | 
			
		||||
      $parsed = substr($unparsed, 0, strlen($unparsed) - strlen($input));
 | 
			
		||||
      if (($input === "" || !$this->ppParseAll) && $this->verifixConvert($value)) {
 | 
			
		||||
        return self::result_valid($result, $value, $orig, $parsed, $input);
 | 
			
		||||
      }
 | 
			
		||||
      $orig_desc = $this->getValueDesc($orig, $parsed, $value);
 | 
			
		||||
      return self::result_invalid($result, "invalid", $orig, $orig_desc, $parsed, $input, $this->ppMessages, null);
 | 
			
		||||
    } catch (Exception $e) {
 | 
			
		||||
      $orig_desc = $this->getValueDesc($orig, false, $value);
 | 
			
		||||
      return self::result_invalid($result, "invalid", $orig, $orig_desc, false, false, $this->ppMessages, $e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function _verifix(&$value, array &$result=null): void {
 | 
			
		||||
    $orig = $value;
 | 
			
		||||
    if ($this->verifixNoParse($value, $result)) return;
 | 
			
		||||
    if ($this->verifixCheckClass($value, $orig, $result)) return;
 | 
			
		||||
    if (is_string($value)) {
 | 
			
		||||
      $value = $this->verifixParse($value, $orig, $result);
 | 
			
		||||
    } else {
 | 
			
		||||
      $orig_desc = $this->getValueDesc($orig, false, $value);
 | 
			
		||||
      $value = self::result_invalid($result, "invalid", $orig, $orig_desc, false, false, $this->ppMessages, null);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										343
									
								
								nur_src/data/types/AbstractType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										343
									
								
								nur_src/data/types/AbstractType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,343 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
use Exception;
 | 
			
		||||
use nur\A;
 | 
			
		||||
use nur\b\IllegalAccessException;
 | 
			
		||||
use nur\b\params\IParametrable;
 | 
			
		||||
use nur\b\ValueException;
 | 
			
		||||
use nur\prop;
 | 
			
		||||
use nur\str;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class AbstractType: implémentation partagée
 | 
			
		||||
 */
 | 
			
		||||
abstract class AbstractType implements IType, IParametrable {
 | 
			
		||||
  use _Tparametrable1;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @var bool si une valeur chaine est fournie, doit-elle être trimée avant
 | 
			
		||||
   * analyse?
 | 
			
		||||
   */
 | 
			
		||||
  const TRIM = true;
 | 
			
		||||
 | 
			
		||||
  /** @var bool|null la chaine vide est-elle autorisée? */
 | 
			
		||||
  const ALLOW_EMPTY = true;
 | 
			
		||||
 | 
			
		||||
  /** @var bool la valeur false est-elle autorisée? */
 | 
			
		||||
  const ALLOW_FALSE = true;
 | 
			
		||||
 | 
			
		||||
  /** @var bool la valeur null est-elle autorisée? */
 | 
			
		||||
  const ALLOW_NULL = true;
 | 
			
		||||
 | 
			
		||||
  /** @var bool si une chaine est fournie, doit-elle correspondre entièrement? */
 | 
			
		||||
  const PARSE_ALL = true;
 | 
			
		||||
 | 
			
		||||
  /** @var array messages standards */
 | 
			
		||||
  const MESSAGES = [
 | 
			
		||||
    "empty" => "cette valeur ne doit pas être vide",
 | 
			
		||||
    "false" => "cette valeur ne doit pas être false",
 | 
			
		||||
    "null" => "cette valeur ne doit pas être null",
 | 
			
		||||
    "invalid" => "{value_desc}: cette valeur est invalide",
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  #############################################################################
 | 
			
		||||
 | 
			
		||||
  const PARAMETRABLE_PARAMS_SCHEMA = [
 | 
			
		||||
    "trim" => ["bool", null, "faut-il trimmer la valeur chaine avant analyse"],
 | 
			
		||||
    "allow_empty" => ["?bool", null, "la chaine vide est-elle autorisée?"],
 | 
			
		||||
    "allow_false" => ["bool", null, "la valeur false est-elle autorisée?"],
 | 
			
		||||
    "allow_null" => ["bool", null, "la valeur null est-elle autorisée?"],
 | 
			
		||||
    "parse_all" => ["bool", null, "la chaine fournie doit-elle correspondre entièrement?"],
 | 
			
		||||
    "messages" => ["array", null, "messages à retourner en cas d'erreur"],
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /** @var bool */
 | 
			
		||||
  protected $ppTrim;
 | 
			
		||||
 | 
			
		||||
  function isTrim(): bool {
 | 
			
		||||
    return $this->ppTrim;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @var bool */
 | 
			
		||||
  protected $ppAllowEmpty;
 | 
			
		||||
 | 
			
		||||
  function isAllowEmpty(): bool {
 | 
			
		||||
    $allowEmpty = $this->ppAllowEmpty;
 | 
			
		||||
    if ($allowEmpty !== null) return $allowEmpty;
 | 
			
		||||
    else return $this->ppAllowNull;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @var bool */
 | 
			
		||||
  protected $ppAllowFalse;
 | 
			
		||||
 | 
			
		||||
  function isAllowFalse(): bool {
 | 
			
		||||
    return $this->ppAllowFalse;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @var bool */
 | 
			
		||||
  protected $ppAllowNull;
 | 
			
		||||
 | 
			
		||||
  function isAllowNull(): bool {
 | 
			
		||||
    return $this->ppAllowNull;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @var bool */
 | 
			
		||||
  protected $ppParseAll;
 | 
			
		||||
 | 
			
		||||
  /** @var array messages à retourner en cas d'erreur */
 | 
			
		||||
  protected $ppMessages;
 | 
			
		||||
 | 
			
		||||
  function setMessages(array $messages): void {
 | 
			
		||||
    $this->ppMessages = $messages;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #############################################################################
 | 
			
		||||
 | 
			
		||||
  function __construct(?array $params=null) {
 | 
			
		||||
    A::replace_n($params, "trim", static::TRIM);
 | 
			
		||||
    A::replace_n($params, "allow_empty", static::ALLOW_EMPTY);
 | 
			
		||||
    A::replace_n($params, "allow_false", static::ALLOW_FALSE);
 | 
			
		||||
    A::replace_n($params, "allow_null", static::ALLOW_NULL);
 | 
			
		||||
    A::replace_n($params, "parse_all", static::PARSE_ALL);
 | 
			
		||||
    $this->initParametrableParams($params);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  abstract function getClass(): string;
 | 
			
		||||
 | 
			
		||||
  function getPhpType(bool $allowNullable=true): ?string {
 | 
			
		||||
    $phpType = $this->getClass();
 | 
			
		||||
    if ($phpType === "mixed") return null;
 | 
			
		||||
    if ($this->ppAllowNull && $allowNullable) $phpType = "?$phpType";
 | 
			
		||||
    return $phpType;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function isInstance($value, bool $strict=false): bool {
 | 
			
		||||
    if ($value === null) return $this->ppAllowNull;
 | 
			
		||||
    $class = $this->getClass();
 | 
			
		||||
    switch ($class) {
 | 
			
		||||
    case "mixed": return true;
 | 
			
		||||
    case "bool": return is_bool($value);
 | 
			
		||||
    case "int": return is_int($value) || (!$strict && is_float($value));
 | 
			
		||||
    case "float": return is_float($value) || (!$strict && is_int($value));
 | 
			
		||||
    case "string": return is_string($value);
 | 
			
		||||
    case "array": return is_array($value);
 | 
			
		||||
    case "iterable": return is_iterable($value);
 | 
			
		||||
    case "resource": return is_resource($value);
 | 
			
		||||
    default: return $value instanceof $class;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function getUndefValue() {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function isUndef($value, $key=null): bool {
 | 
			
		||||
    if ($key !== null) {
 | 
			
		||||
      if (!is_array($value)) return $key !== 0;
 | 
			
		||||
      if (!array_key_exists($key, $value)) return true;
 | 
			
		||||
      $value = $value[$key];
 | 
			
		||||
    }
 | 
			
		||||
    return $value === false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function is2States(): bool {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function get2States(): array {
 | 
			
		||||
    throw IllegalAccessException::not_implemented();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function is3States(): bool {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function get3States(): array {
 | 
			
		||||
    throw IllegalAccessException::not_implemented();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function canFormat(): bool {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function format($value): string {
 | 
			
		||||
    throw IllegalAccessException::not_implemented();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function canParse(): bool {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function parse(string &$input) {
 | 
			
		||||
    throw IllegalAccessException::not_implemented();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #############################################################################
 | 
			
		||||
 | 
			
		||||
  protected static function _set_error(array &$result, string $error_code, ?array $messages): void {
 | 
			
		||||
    $error = A::get($messages, $error_code);
 | 
			
		||||
    if ($error === null) $error = A::get(static::MESSAGES, $error_code);
 | 
			
		||||
    if ($error === null) $error = $error_code;
 | 
			
		||||
    foreach ($result as $key => $value) {
 | 
			
		||||
      switch ($key) {
 | 
			
		||||
      case "value":
 | 
			
		||||
      case "orig":
 | 
			
		||||
        $value = var_export($value, true);
 | 
			
		||||
        break;
 | 
			
		||||
      case "value_desc":
 | 
			
		||||
        if ($value === null) $value = $result["value"];
 | 
			
		||||
        $value = var_export($value, true);
 | 
			
		||||
        break;
 | 
			
		||||
      case "orig_desc":
 | 
			
		||||
        if ($value === null) $value = $result["orig"];
 | 
			
		||||
        $value = var_export($value, true);
 | 
			
		||||
        break;
 | 
			
		||||
      case "exception":
 | 
			
		||||
        $value = null;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      $error = str_replace("{".$key."}", $value, $error);
 | 
			
		||||
    }
 | 
			
		||||
    $result["error"] = $error;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected static function result_invalid(?array &$result, string $error_code, $orig, $orig_desc, $parsed, $remains, ?array $messages, ?Exception $exception=null) {
 | 
			
		||||
    $result = [
 | 
			
		||||
      "valid" => false,
 | 
			
		||||
      "value" => $orig,
 | 
			
		||||
      "value_desc" => $orig_desc,
 | 
			
		||||
      "error_code" => $error_code,
 | 
			
		||||
      "error" => null,
 | 
			
		||||
      "exception" => $exception,
 | 
			
		||||
      "orig" => $orig,
 | 
			
		||||
      "orig_desc" => $orig_desc,
 | 
			
		||||
      "parsed" => $parsed,
 | 
			
		||||
      "remains" => $remains,
 | 
			
		||||
    ];
 | 
			
		||||
    self::_set_error($result, $error_code, $messages);
 | 
			
		||||
    return $orig;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected static function result_valid(?array &$result, $value, $orig, $parsed=false, $remains=false) {
 | 
			
		||||
    $result = [
 | 
			
		||||
      "valid" => true,
 | 
			
		||||
      "value" => $value,
 | 
			
		||||
      "value_desc" => null,
 | 
			
		||||
      "error_code" => null,
 | 
			
		||||
      "error" => false,
 | 
			
		||||
      "exception" => null,
 | 
			
		||||
      "orig" => $orig,
 | 
			
		||||
      "orig_desc" => null,
 | 
			
		||||
      "parsed" => $parsed,
 | 
			
		||||
      "remains" => $remains,
 | 
			
		||||
    ];
 | 
			
		||||
    return $value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** fonction de support: trimmer la valeur */
 | 
			
		||||
  function verifixTrim(string $value): string {
 | 
			
		||||
    return trim($value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * fonction de support: corriger la valeur "" le cas échéant
 | 
			
		||||
   *
 | 
			
		||||
   * par défaut, remplacer "" par null si null est autorisé
 | 
			
		||||
   */
 | 
			
		||||
  function verifixReplaceEmpty(&$value): void {
 | 
			
		||||
    if ($this->ppAllowNull) $value = null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * fonction de support: corriger la valeur false le cas échéant
 | 
			
		||||
   *
 | 
			
		||||
   * par défaut, remplacer false par null si false n'est pas autorisé.
 | 
			
		||||
   */
 | 
			
		||||
  function verifixReplaceFalse(&$value): void {
 | 
			
		||||
    if (!$this->ppAllowFalse) $value = null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** fonction de support: corriger la valeur null le cas échéant */
 | 
			
		||||
  function verifixReplaceNull(&$value): void {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * fonction de support qui implémente la vérification des valeurs non chaine
 | 
			
		||||
   */
 | 
			
		||||
  protected function verifixNoParse(&$value, ?array &$result): bool {
 | 
			
		||||
    $orig = $value;
 | 
			
		||||
    if (is_string($value)) {
 | 
			
		||||
      if ($this->ppTrim) $value = $this->verifixTrim($value);
 | 
			
		||||
      if ($value === "") $this->verifixReplaceEmpty($value);
 | 
			
		||||
    }
 | 
			
		||||
    if ($value === false) $this->verifixReplaceFalse($value);
 | 
			
		||||
    if ($value === null) $this->verifixReplaceNull($value);
 | 
			
		||||
 | 
			
		||||
    if ($value === null) {
 | 
			
		||||
      if ($this->ppAllowNull) {
 | 
			
		||||
        self::result_valid($result, $value, $orig);
 | 
			
		||||
      } else {
 | 
			
		||||
        $value = self::result_invalid($result, "null", $orig, null, false, false, $this->ppMessages, null);
 | 
			
		||||
      }
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    if ($value === false) {
 | 
			
		||||
      if ($this->ppAllowFalse) {
 | 
			
		||||
        self::result_valid($result, $value, $orig);
 | 
			
		||||
      } else {
 | 
			
		||||
        $value = self::result_invalid($result, "false", $orig, null, false, false, $this->ppMessages, null);
 | 
			
		||||
      }
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    if ($value === "" && !$this->ppAllowEmpty) {
 | 
			
		||||
      $value = self::result_invalid($result, "empty", $orig, null, false, "", $this->ppMessages, null);
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected abstract function _verifix(&$value, array &$result=null): void;
 | 
			
		||||
 | 
			
		||||
  function verifix(&$value, array &$result=null, bool $throw=false): bool {
 | 
			
		||||
    $this->_verifix($value, $result);
 | 
			
		||||
    $valid = $result["valid"];
 | 
			
		||||
    if (!$valid && $throw) {
 | 
			
		||||
      $exception = $result["exception"];
 | 
			
		||||
      if ($exception !== null) throw $exception;
 | 
			
		||||
      else throw new ValueException($result["error"]);
 | 
			
		||||
    }
 | 
			
		||||
    return $valid;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function with($value) {
 | 
			
		||||
    $this->verifix($value, $result, true);
 | 
			
		||||
    return $value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #############################################################################
 | 
			
		||||
 | 
			
		||||
  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;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										176
									
								
								nur_src/data/types/BoolType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								nur_src/data/types/BoolType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,176 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
use nur\prop;
 | 
			
		||||
 | 
			
		||||
class BoolType extends AbstractSimpleType {
 | 
			
		||||
  use _Tparametrable;
 | 
			
		||||
 | 
			
		||||
  const TITLE = "valeur booléenne";
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @var bool
 | 
			
		||||
   *
 | 
			
		||||
   * par défaut, n'autoriser que deux état: true et false. remplacer par
 | 
			
		||||
   * true dans une classe dérivée pour avoir les 3 états true, false et null
 | 
			
		||||
   */
 | 
			
		||||
  const ALLOW_NULL = false;
 | 
			
		||||
 | 
			
		||||
  const OUINON_FORMAT = ["Oui", "Non", false];
 | 
			
		||||
  const OUINONNULL_FORMAT = ["Oui", "Non", ""];
 | 
			
		||||
  const ON_FORMAT = ["O", "N", false];
 | 
			
		||||
  const ONN_FORMAT = ["O", "N", ""];
 | 
			
		||||
  const XN_FORMAT = ["X", "", false];
 | 
			
		||||
  const OZ_FORMAT = ["1", "", false];
 | 
			
		||||
  const FORMATS = [
 | 
			
		||||
    "ouinon" => self::OUINON_FORMAT,
 | 
			
		||||
    "ouinonnull" => self::OUINONNULL_FORMAT,
 | 
			
		||||
    "on" => self::ON_FORMAT,
 | 
			
		||||
    "onn" => self::ONN_FORMAT,
 | 
			
		||||
    "xn" => self::XN_FORMAT,
 | 
			
		||||
    "oz" => self::OZ_FORMAT,
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  const FORMAT = self::OUINON_FORMAT;
 | 
			
		||||
 | 
			
		||||
  /** liste de valeurs chaines à considérer comme 'OUI' */
 | 
			
		||||
  const YES_VALUES = array(
 | 
			
		||||
    # IMPORTANT: ordonner par taille décroissante pour compatibilité avec parse()
 | 
			
		||||
    "true", "vrai", "yes", "oui",
 | 
			
		||||
    "t", "v", "y", "o", "1",
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  /** liste de valeurs chaines à considérer comme 'NON' */
 | 
			
		||||
  const NO_VALUES = array(
 | 
			
		||||
    # IMPORTANT: ordonner par taille décroissante pour compatibilité avec parse()
 | 
			
		||||
    "false", "faux", "non", "no",
 | 
			
		||||
    "f", "n", "0",
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  /** Vérifier si $value est 'OUI' */
 | 
			
		||||
  static final function is_yes($value): bool {
 | 
			
		||||
    if ($value === null) return false;
 | 
			
		||||
    if (is_bool($value)) return $value;
 | 
			
		||||
    $value = strtolower(trim(strval($value)));
 | 
			
		||||
    if (in_array($value, self::YES_VALUES, true)) return true;
 | 
			
		||||
    // n'importe quelle valeur numérique
 | 
			
		||||
    if (preg_match('/^[0-9]+$/', $value)) return $value != 0;
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** Vérifier si $value est 'NON' */
 | 
			
		||||
  static final function is_no($value): bool {
 | 
			
		||||
    if ($value === null) return true;
 | 
			
		||||
    if (is_bool($value)) return !$value;
 | 
			
		||||
    $value = strtolower(trim(strval($value)));
 | 
			
		||||
    return in_array($value, self::NO_VALUES, true);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static final function to_bool($value): bool {
 | 
			
		||||
    if (self::is_yes($value)) return true;
 | 
			
		||||
    elseif (self::is_no($value)) return false;
 | 
			
		||||
    else return boolval($value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const PARAMETRABLE_PARAMS_SCHEMA = [
 | 
			
		||||
    "format" => [null, null, "format à appliquer"],
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /** @var array */
 | 
			
		||||
  protected $ppFormat;
 | 
			
		||||
 | 
			
		||||
  function pp_setFormat($format): void {
 | 
			
		||||
    if ($format === null) $format = static::FORMAT;
 | 
			
		||||
    if (!is_array($format)) $format = static::FORMATS[$format];
 | 
			
		||||
    $this->ppFormat = $format;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function __construct(?array $params=null) {
 | 
			
		||||
    parent::__construct($params);
 | 
			
		||||
    if ($this->ppFormat === null) $this->pp_setFormat(null);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function getClass(): string {
 | 
			
		||||
    return "bool";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function isInstance($value, bool $strict=false): bool {
 | 
			
		||||
    return is_bool($value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function getUndefValue() {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function isUndef($value, $key=null): bool {
 | 
			
		||||
    if ($key !== null) {
 | 
			
		||||
      if (!is_array($value)) return $key !== 0;
 | 
			
		||||
      if (!array_key_exists($key, $value)) return true;
 | 
			
		||||
      return $value[$key] === null;
 | 
			
		||||
    }
 | 
			
		||||
    return $value === null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function is2States(): bool {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function get2States(): array {
 | 
			
		||||
    return [false, true];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function format($value): string {
 | 
			
		||||
    $format = $this->ppFormat;
 | 
			
		||||
    if ($value === null) {
 | 
			
		||||
      $fvalue = $format[2];
 | 
			
		||||
      if ($fvalue !== false) return $fvalue;
 | 
			
		||||
    }
 | 
			
		||||
    return !self::is_no($value)? $format[0]: $format[1];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function parse(string &$input) {
 | 
			
		||||
    if (preg_match('/^[0-9]+/', $input, $ms)) {
 | 
			
		||||
      $value = $ms[0];
 | 
			
		||||
      $length = strlen($value);
 | 
			
		||||
      $input = substr($input, $length);
 | 
			
		||||
      return $value;
 | 
			
		||||
    }
 | 
			
		||||
    foreach (self::YES_VALUES as $value) {
 | 
			
		||||
      $length = strlen($value);
 | 
			
		||||
      if (substr($input, 0, $length) === $value) {
 | 
			
		||||
        $input = substr($input, $length);
 | 
			
		||||
        return $value;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    foreach (self::NO_VALUES as $value) {
 | 
			
		||||
      $length = strlen($value);
 | 
			
		||||
      if (substr($input, 0, $length) === $value) {
 | 
			
		||||
        $input = substr($input, $length);
 | 
			
		||||
        return $value;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function verifixConvert(&$value): bool {
 | 
			
		||||
    if ($value === null && $this->ppAllowNull) return true;
 | 
			
		||||
    $value = self::to_bool($value);
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function _verifix(&$value, array &$result=null): void {
 | 
			
		||||
    $orig = $value;
 | 
			
		||||
    if (!is_bool($value)) {
 | 
			
		||||
      if ($value === null || $value === "") $value = null;
 | 
			
		||||
      elseif (self::is_yes($value)) $value = true;
 | 
			
		||||
      elseif (self::is_no($value)) $value = false;
 | 
			
		||||
      else $value = boolval($value);
 | 
			
		||||
    }
 | 
			
		||||
    if ($value === null && !$this->ppAllowNull) $value = false;
 | 
			
		||||
    self::result_valid($result, $value, $orig);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function getGetterName(string $name): string {
 | 
			
		||||
    return prop::get_getter_name($name, !$this->ppAllowNull);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								nur_src/data/types/CTimeslotType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								nur_src/data/types/CTimeslotType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
class CTimeslotType extends AbstractCompositeType {
 | 
			
		||||
  const TITLE = "plage horaire";
 | 
			
		||||
 | 
			
		||||
  const COMPONENTS = [
 | 
			
		||||
    "start" => [
 | 
			
		||||
      "type" => [SHourType::class],
 | 
			
		||||
      "key" => "ts_start",
 | 
			
		||||
      "title" => "heure de début (inclue)",
 | 
			
		||||
      "allow_parse_empty" => true,
 | 
			
		||||
    ],
 | 
			
		||||
    "end" => [
 | 
			
		||||
      "type" => [SHourType::class],
 | 
			
		||||
      "key" => "ts_end",
 | 
			
		||||
      "title" => "heure de fin (non inclue)",
 | 
			
		||||
      "allow_parse_empty" => true,
 | 
			
		||||
    ],
 | 
			
		||||
  ];
 | 
			
		||||
  const SEPARATOR = "-";
 | 
			
		||||
  const SEPARATOR_PATTERN = '/^\s*(?:-\s*)?/';
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								nur_src/data/types/ContentType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								nur_src/data/types/ContentType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,42 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
use nur\b\ui\IContent;
 | 
			
		||||
use nur\b\ui\IPrintable;
 | 
			
		||||
 | 
			
		||||
class ContentType extends AbstractSimpleType {
 | 
			
		||||
  const TITLE = "contenu à afficher";
 | 
			
		||||
 | 
			
		||||
  const TRIM = false;
 | 
			
		||||
 | 
			
		||||
  /** @var string description de la valeur */
 | 
			
		||||
  const KIND = "content";
 | 
			
		||||
 | 
			
		||||
  function getClass(): string {
 | 
			
		||||
    # on est malheureusement obligé de mettre mixed, mais en réalité, il peut
 | 
			
		||||
    # s'agir des types suivants: scalar (string, int, etc.), iterable,
 | 
			
		||||
    # IPrintable, IContent
 | 
			
		||||
    return "mixed";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function isInstance($value, bool $strict=false): bool {
 | 
			
		||||
    if (is_iterable($value)) {
 | 
			
		||||
      $values = $value;
 | 
			
		||||
      foreach ($values as $value) {
 | 
			
		||||
        if (!$this->isInstance($value)) return false;
 | 
			
		||||
      }
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    return $value === null ||
 | 
			
		||||
      is_scalar($value) ||
 | 
			
		||||
      $value instanceof IPrintable ||
 | 
			
		||||
      $value instanceof IContent;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function parse(string &$input) {
 | 
			
		||||
    return $input;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function verifixReplaceEmpty(&$value): void {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										51
									
								
								nur_src/data/types/FileType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								nur_src/data/types/FileType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class GenericType: type de données générique, qui n'est ni formatable ni
 | 
			
		||||
 * analysable
 | 
			
		||||
 */
 | 
			
		||||
class FileType extends AbstractType {
 | 
			
		||||
  const TITLE = "information de fichier uploadé récupéré depuis \$_FILES";
 | 
			
		||||
 | 
			
		||||
  function getClass(): string {
 | 
			
		||||
    return "array";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function isInstance($value, bool $strict=false): bool {
 | 
			
		||||
    if ($value === null) return $this->ppAllowNull;
 | 
			
		||||
    return is_array($value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function canFormat(): bool {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function _verifix(&$value, array &$result=null): void {
 | 
			
		||||
    $orig = $value;
 | 
			
		||||
    if ($value === false) $this->verifixReplaceFalse($value);
 | 
			
		||||
    if ($value === null) $this->verifixReplaceNull($value);
 | 
			
		||||
    if ($value === null) {
 | 
			
		||||
      if ($this->ppAllowNull) {
 | 
			
		||||
        self::result_valid($result, $value, $orig);
 | 
			
		||||
      } else {
 | 
			
		||||
        $value = self::result_invalid($result, "null", $orig, null, false, false, $this->ppMessages);
 | 
			
		||||
      }
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if ($value === false) {
 | 
			
		||||
      if ($this->ppAllowFalse) {
 | 
			
		||||
        self::result_valid($result, $value, $orig);
 | 
			
		||||
      } else {
 | 
			
		||||
        $value = self::result_invalid($result, "false", $orig, null, false, false, $this->ppMessages);
 | 
			
		||||
      }
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (is_array($value)) {
 | 
			
		||||
      self::result_valid($result, $value, $orig);
 | 
			
		||||
    } else {
 | 
			
		||||
      $value = self::result_invalid($result, "invalid", $orig, null, false, false, $this->ppMessages);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										48
									
								
								nur_src/data/types/FloatType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								nur_src/data/types/FloatType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,48 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
class FloatType extends AbstractSimpleType {
 | 
			
		||||
  const TITLE = "nombre à virgule";
 | 
			
		||||
 | 
			
		||||
  private static $instance;
 | 
			
		||||
  static final function to_zfloat($value) {
 | 
			
		||||
    $type = self::$instance;
 | 
			
		||||
    if ($type === null) self::$instance = $type = new self();
 | 
			
		||||
    return $type->with($value);
 | 
			
		||||
  }
 | 
			
		||||
  static final function to_float($value): float {
 | 
			
		||||
    return floatval(self::to_zfloat($value));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function getClass(): string {
 | 
			
		||||
    return "float";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function isInstance($value, bool $strict=false): bool {
 | 
			
		||||
    return $value === null || is_float($value) || (!$strict && is_int($value));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function parse(string &$input) {
 | 
			
		||||
    if (preg_match('/^[0-9]+(?:\.[0-9]*)?/', $input, $ms)) {
 | 
			
		||||
      $value = $ms[0];
 | 
			
		||||
      $length = strlen($value);
 | 
			
		||||
      $input = substr($input, $length);
 | 
			
		||||
      return $value;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function verifixCheckClass(&$value, $orig, array &$result=null): bool {
 | 
			
		||||
    # accepter float et int
 | 
			
		||||
    if (is_float($value)) $value = self::result_valid($result, $value, $orig);
 | 
			
		||||
    elseif (is_int($value)) $value = self::result_valid($result, floatval($value), $orig);
 | 
			
		||||
    else return false;
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function verifixConvert(&$value): bool {
 | 
			
		||||
    if ($value === null && $this->ppAllowNull) return true;
 | 
			
		||||
    $value = floatval($value);
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										91
									
								
								nur_src/data/types/GenericType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								nur_src/data/types/GenericType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,91 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
use nur\A;
 | 
			
		||||
use nur\b\ValueException;
 | 
			
		||||
use nur\func;
 | 
			
		||||
use Traversable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class GenericType: type de données générique, qui n'est ni formatable ni
 | 
			
		||||
 * analysable
 | 
			
		||||
 */
 | 
			
		||||
class GenericType extends AbstractSimpleType {
 | 
			
		||||
  const ARRAY = "array";
 | 
			
		||||
  const ARRAY_ARRAY = "array[]";
 | 
			
		||||
  const ITERABLE = "iterable";
 | 
			
		||||
  const RESOURCE = "resource";
 | 
			
		||||
 | 
			
		||||
  function __construct($class, ?array $params=null) {
 | 
			
		||||
    parent::__construct($params);
 | 
			
		||||
    func::fix_class_args($class, $args);
 | 
			
		||||
    $this->class = $class;
 | 
			
		||||
    $this->args = $args;
 | 
			
		||||
    #XXX faut-il vérifier que la classe est valide?
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @var string  */
 | 
			
		||||
  protected $class;
 | 
			
		||||
 | 
			
		||||
  /** @var array  */
 | 
			
		||||
  protected $args;
 | 
			
		||||
 | 
			
		||||
  function getClass(): string {
 | 
			
		||||
    $class = $this->class;
 | 
			
		||||
    switch ($class) {
 | 
			
		||||
    case self::ARRAY_ARRAY: $class = self::ARRAY; break;
 | 
			
		||||
    }
 | 
			
		||||
    return $class;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function isInstance($value, bool $strict=false): bool {
 | 
			
		||||
    if ($value === null) return $this->ppAllowNull;
 | 
			
		||||
    $class = $this->getClass();
 | 
			
		||||
    switch ($class) {
 | 
			
		||||
    case self::ARRAY:
 | 
			
		||||
    case self::ARRAY_ARRAY:
 | 
			
		||||
      return is_array($value);
 | 
			
		||||
    case self::ITERABLE: return is_iterable($value);
 | 
			
		||||
    case self::RESOURCE: return is_resource($value);
 | 
			
		||||
    default: return $value instanceof $class;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function canFormat(): bool {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function _verifix(&$value, array &$result=null): void {
 | 
			
		||||
    $orig = $value;
 | 
			
		||||
    if ($this->verifixNoParse($value, $result)) return;
 | 
			
		||||
 | 
			
		||||
    $class = $this->getClass();
 | 
			
		||||
    switch ($class) {
 | 
			
		||||
    case self::ARRAY:
 | 
			
		||||
      A::ensure_array($value);
 | 
			
		||||
      break;
 | 
			
		||||
    case self::ARRAY_ARRAY:
 | 
			
		||||
      A::ensure_array($value);
 | 
			
		||||
      foreach ($value as &$item) {
 | 
			
		||||
        A::ensure_array($item);
 | 
			
		||||
      }; unset($item);
 | 
			
		||||
      break;
 | 
			
		||||
    case self::ITERABLE:
 | 
			
		||||
      if (!($value instanceof Traversable)) A::ensure_array($value);
 | 
			
		||||
      break;
 | 
			
		||||
    case self::RESOURCE:
 | 
			
		||||
      if (!is_resource($value)) {
 | 
			
		||||
        $value = self::result_invalid($result, "invalid", $orig, null
 | 
			
		||||
          , false, false
 | 
			
		||||
          , $this->ppMessages, ValueException::unexpected_type(self::RESOURCE, $value));
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      $args = $this->args;
 | 
			
		||||
      A::merge($args, A::with($value));
 | 
			
		||||
      $value = func::cons($class, ...$args);
 | 
			
		||||
    }
 | 
			
		||||
    self::result_valid($result, $value, $orig);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								nur_src/data/types/IIncarnation.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								nur_src/data/types/IIncarnation.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Interface IIncarnation: un gestionnaire de types
 | 
			
		||||
 */
 | 
			
		||||
interface IIncarnation {
 | 
			
		||||
  function hasType($name);
 | 
			
		||||
 | 
			
		||||
  function getType($name, bool $required=true): ?IType;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										261
									
								
								nur_src/data/types/IType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										261
									
								
								nur_src/data/types/IType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,261 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
use Exception;
 | 
			
		||||
use nur\b\IllegalAccessException;
 | 
			
		||||
use nur\b\ValueException;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Interface IType: un format d'affichage et de saisie pour 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;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * vérifier si $value est de ce type
 | 
			
		||||
   *
 | 
			
		||||
   * si {@link getClass()} retourne "string" ou "array", on ne se contente pas
 | 
			
		||||
   * de vérifier que la donnée est au bon type: on vérifie aussi si le format de
 | 
			
		||||
   * la donnée est correct
 | 
			
		||||
   */
 | 
			
		||||
  function isInstance($value): bool;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * obtenir la valeur correspondant à "non-existant"
 | 
			
		||||
   *
 | 
			
		||||
   * si la valeur retournée est null, ce n'est peut-être pas la valeur undef. la
 | 
			
		||||
   * seule façon de le savoir est d'appeler {@link isUndef()}
 | 
			
		||||
   */
 | 
			
		||||
  function getUndefValue();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * vérifier si la valeur existe
 | 
			
		||||
   *
 | 
			
		||||
   * - si $key===null, alors indiquer si $value n'a pas la valeur "non-existant"
 | 
			
		||||
   * si cela a du sens pour ce type
 | 
			
		||||
   * - si $key!==null, alors indiquer si $value[$key] existe ou s'il n'a pas la
 | 
			
		||||
   * valeur "non-existant"
 | 
			
		||||
   *
 | 
			
		||||
   * d'une manière générale, "false" correspond à "non-existant", mais il peut
 | 
			
		||||
   * y avoir des exceptions d'où l'existence de cette méthode
 | 
			
		||||
   */
 | 
			
		||||
  function isUndef($value, $key=null): bool;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 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;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * cet objet est-il capable de formatter des valeurs pour affichage? sinon,
 | 
			
		||||
   * la méthode format() lance l'exception {@link IllegalAccessException}
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool true si les valeurs de ce type peuvent être formattées pour
 | 
			
		||||
   * affichage
 | 
			
		||||
   */
 | 
			
		||||
  function canFormat(): bool;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * formatter $value pour affichage.
 | 
			
		||||
   *
 | 
			
		||||
   * $value peut valoir null ou false si ces valeurs sont autorisées. sinon,
 | 
			
		||||
   * elle doit être du bon type (c'est à dire que {@link isInstance()} doit
 | 
			
		||||
   * retourner true). sinon le comportement n'est pas défini
 | 
			
		||||
   */
 | 
			
		||||
  function format($value): string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * cet objet est-il capable d'analyser des valeurs chaines? sinon, la méthode
 | 
			
		||||
   * {@link parse()} ne fait pas partie de l'API publique de cet objet. elle ne
 | 
			
		||||
   * sera alors généralement pas implémentée
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool true si cet objet est capable d'analyser une valeur chaine
 | 
			
		||||
   * fournie par l'utilisateur
 | 
			
		||||
   */
 | 
			
		||||
  function canParse(): bool;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * analyser $input:
 | 
			
		||||
   * - si elle commence par une valeur de ce type, retourner la chaine pour
 | 
			
		||||
   * traitement par {@link verifix()}. $input est mis à jour pour pointer sur la
 | 
			
		||||
   * suite de la chaine, après la valeur analysée
 | 
			
		||||
   * - sinon, retourner false. dans ce cas, $input n'est pas modifié
 | 
			
		||||
   *
 | 
			
		||||
   * NB: une implémentation peut décider de retourner autre chose qu'une chaine
 | 
			
		||||
   * mais dans ce cas, il faut documenter le format de la valeur de retour, et
 | 
			
		||||
   * probablement que {@link canParse()} doit retourner false
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $input la chaine à analyser
 | 
			
		||||
   * @return mixed|bool la partie de la chaine à traiter avec {@link verifix()}
 | 
			
		||||
   * @throws IllegalAccessException si {@link canParse()} retourne false
 | 
			
		||||
   */
 | 
			
		||||
  function parse(string &$input);
 | 
			
		||||
 | 
			
		||||
  /** avant d'analyser une chaine, faut-il la trimer? */
 | 
			
		||||
  function isTrim(): bool;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * la chaine vide est-elle autorisée?
 | 
			
		||||
   *
 | 
			
		||||
   * si une chaine est fournie et que la chaine vide n'est pas autorisée, la
 | 
			
		||||
   * méthode {@link verifixReplaceEmpty()} est appelée pour la remplacer par une
 | 
			
		||||
   * autre valeur. par défaut, elle est remplacée par null.
 | 
			
		||||
   */
 | 
			
		||||
  function isAllowEmpty(): bool;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * fonction de support: corriger la valeur "" le cas échéant
 | 
			
		||||
   *
 | 
			
		||||
   * par défaut, remplacer "" par null si null est autorisé
 | 
			
		||||
   */
 | 
			
		||||
  function verifixReplaceEmpty(&$value): void;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * la valeur false est-elle autorisée?
 | 
			
		||||
   *
 | 
			
		||||
   * si false n'est pas autorisée, la fonction {@link verifixReplaceFalse()}
 | 
			
		||||
   * est appelée pour la remplacer par une autre valeur. par défaut, elle est
 | 
			
		||||
   * remplacée par null.
 | 
			
		||||
   */
 | 
			
		||||
  function isAllowFalse(): bool;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * fonction de support: corriger la valeur false le cas échéant
 | 
			
		||||
   *
 | 
			
		||||
   * par défaut, remplacer false par null si false n'est pas autorisé.
 | 
			
		||||
   */
 | 
			
		||||
  function verifixReplaceFalse(&$value): void;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * la valeur null est-elle autorisée?
 | 
			
		||||
   *
 | 
			
		||||
   * si null n'est pas autorisée, la fonction {@link verifixReplaceNull()}
 | 
			
		||||
   * est appelée pour la remplacer par une autre valeur. par défaut, aucun
 | 
			
		||||
   * remplacement n'est fait, ce qui provoque donc une erreur
 | 
			
		||||
   */
 | 
			
		||||
  function isAllowNull(): bool;
 | 
			
		||||
 | 
			
		||||
  /** fonction de support: corriger la valeur null le cas échéant */
 | 
			
		||||
  function verifixReplaceNull(&$value): void;
 | 
			
		||||
 | 
			
		||||
  const VERIFIX_RESULT_SCHEMA = [
 | 
			
		||||
    "valid" => [null, false, "est-ce que la valeur fournie est valide?"],
 | 
			
		||||
    "value" => [null, null, "valeur corrigée si elle est valide, valeur originale si elle est invalide"],
 | 
			
		||||
    "error" => [null, false, "message d'erreur si la valeur fournie est invalide"],
 | 
			
		||||
    "exception" => [null, null, "exception associée à l'erreur le cas échaéant"],
 | 
			
		||||
    "orig" => [null, null, "valeur originale avant correction"],
 | 
			
		||||
    "parsed" => [null, false, "partie de la chaine originale qui a pu être analysée si valid==false"],
 | 
			
		||||
    "remains" => [null, false, "partie de la chaine originale non analysée"],
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * vérifier $value qui a été fourni par l'utilisateur:
 | 
			
		||||
   * - si elle déjà du bon type et au bon format, la laisser en l'état
 | 
			
		||||
   * - si elle est du bon type mais pas au bon format, elle est normalisée
 | 
			
		||||
   * - sinon, ce doit être une chaine de caractère, qui est analysée et doit
 | 
			
		||||
   * correspondre entièrement (sauf si l'implémentation autorise que l'analyse
 | 
			
		||||
   * soit incomplète)
 | 
			
		||||
   *
 | 
			
		||||
   * si la valeur est invalide elle est laissée inchangée. dans tous les cas,
 | 
			
		||||
   * $result est mis à jour avec les clés suivantes:
 | 
			
		||||
   * - "valid" => true ou false, indique si la valeur est valide
 | 
			
		||||
   * - "value" => valeur normalisée si elle est valide
 | 
			
		||||
   * - "error" => message d'erreur si la valeur est invalide, ou false si la
 | 
			
		||||
   * valeur est valide
 | 
			
		||||
   * - "exception" => si la valeur est invalide, exception éventuellement
 | 
			
		||||
   * associée à l'erreur
 | 
			
		||||
   * - "orig" => valeur originale de $value
 | 
			
		||||
   * - "parsed" => partie de la chaine originale qui a pu être analysée si
 | 
			
		||||
   * la valeur est invalide
 | 
			
		||||
   * - "remains" => le reste non analysé de la chaine si l'implémentation
 | 
			
		||||
   * autorise que la chaine en entrée ne soit pas analysée entièrement ou si
 | 
			
		||||
   * la valeur est invalide
 | 
			
		||||
   *
 | 
			
		||||
   * si plusieurs messages d'erreur sont possibles, $params["messages"] permet
 | 
			
		||||
   * de spécifier des messages différents des valeurs par défaut. les clés
 | 
			
		||||
   * valides sont documentées dans l'implémentation
 | 
			
		||||
   *
 | 
			
		||||
   * @param mixed &$value la valeur à analyser et normaliser
 | 
			
		||||
   * @param array $result le résultat de l'analyse
 | 
			
		||||
   * @param bool $throw faut-il lancer une exception si la valeur est invalide?
 | 
			
		||||
   * @return bool si la valeur est valide
 | 
			
		||||
   * @throws IllegalAccessException si {@link canParse()} retourne false
 | 
			
		||||
   * @throws Exception si la valeur est invalide, que $throw==true et que le
 | 
			
		||||
   * champ exception de $result a été renseigné
 | 
			
		||||
   * @throws ValueException si la valeur est invalide, que $throw==true et que
 | 
			
		||||
   * le champ exception de $result n'a pas été renseigné
 | 
			
		||||
   */
 | 
			
		||||
  function verifix(&$value, array &$result=null, bool $throw=false): bool;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Méthode de convenance pour retourner la valeur normalisée et lancer une
 | 
			
		||||
   * exception en cas d'erreur
 | 
			
		||||
   */
 | 
			
		||||
  function with($value);
 | 
			
		||||
 | 
			
		||||
  #############################################################################
 | 
			
		||||
 | 
			
		||||
  /** @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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										49
									
								
								nur_src/data/types/IntType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								nur_src/data/types/IntType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,49 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
class IntType extends AbstractSimpleType {
 | 
			
		||||
  const TITLE = "nombre entier";
 | 
			
		||||
 | 
			
		||||
  private static $instance;
 | 
			
		||||
  static final function to_zint($value) {
 | 
			
		||||
    $type = self::$instance;
 | 
			
		||||
    if ($type === null) self::$instance = $type = new self();
 | 
			
		||||
    return $type->with($value);
 | 
			
		||||
  }
 | 
			
		||||
  static final function to_int($value): int {
 | 
			
		||||
    return intval(self::to_zint($value));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function getClass(): string {
 | 
			
		||||
    return "int";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function isInstance($value, bool $strict=false): bool {
 | 
			
		||||
    return $value === null || is_int($value)
 | 
			
		||||
      || (!$strict && (is_float($value) || is_bool($value)));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function parse(string &$input) {
 | 
			
		||||
    if (preg_match('/^-?[0-9]+/', $input, $ms)) {
 | 
			
		||||
      $value = $ms[0];
 | 
			
		||||
      $input = substr($input, strlen($value));
 | 
			
		||||
      return $value;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function verifixCheckClass(&$value, $orig, array &$result=null): bool {
 | 
			
		||||
    # accepter int, float et true
 | 
			
		||||
    if (is_int($value)) $value = self::result_valid($result, $value, $orig);
 | 
			
		||||
    elseif (is_float($value)) $value = self::result_valid($result, intval($value), $orig);
 | 
			
		||||
    elseif ($value === true) $value = self::result_valid($result, intval($value), $orig);
 | 
			
		||||
    else return false;
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function verifixConvert(&$value): bool {
 | 
			
		||||
    if ($value === null && $this->ppAllowNull) return true;
 | 
			
		||||
    $value = intval($value);
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								nur_src/data/types/KeyType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								nur_src/data/types/KeyType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
class KeyType extends AbstractSimpleType {
 | 
			
		||||
  const TITLE = "clé d'un tableau";
 | 
			
		||||
 | 
			
		||||
  const TRIM = false;
 | 
			
		||||
 | 
			
		||||
  /** @var string description de la valeur */
 | 
			
		||||
  const KIND = "key";
 | 
			
		||||
 | 
			
		||||
  function getClass(): string {
 | 
			
		||||
    # on est malheureusement obligé de mettre mixed, mais en réalité, il ne peut
 | 
			
		||||
    # y avoir que int et string
 | 
			
		||||
    return "mixed";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function isInstance($value, bool $strict=false): bool {
 | 
			
		||||
    return $value === null || is_string($value) || is_int($value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function parse(string &$input) {
 | 
			
		||||
    return $input;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function verifixConvert(&$value): bool {
 | 
			
		||||
    if (is_string($value) && preg_match('/^-?[0-9]+$/', $value)) {
 | 
			
		||||
      $value = intval($value);
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function verifixReplaceEmpty(&$value): void {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								nur_src/data/types/MailType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								nur_src/data/types/MailType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
class MailType extends RegexpType {
 | 
			
		||||
  const PATTERN = '/^.+/';
 | 
			
		||||
  const PATTERN_NORMALIZED = '/^(\S+)$/';
 | 
			
		||||
 | 
			
		||||
  protected function extractParsedValue(array $ms) {
 | 
			
		||||
    $mail = $ms[0];
 | 
			
		||||
    $mail = filter_var($mail, FILTER_SANITIZE_EMAIL);
 | 
			
		||||
    $mail = filter_var($mail, FILTER_VALIDATE_EMAIL, FILTER_FLAG_EMAIL_UNICODE);
 | 
			
		||||
    if ($mail === false) return false;
 | 
			
		||||
    if (preg_match('/^([A-Za-z0-9.-]+)(\+[^@]*)?(@[A-Za-z0-9.-]+)$/', $mail, $ms)) {
 | 
			
		||||
      return [$ms[1], $ms[2], $ms[3]];
 | 
			
		||||
    } else {
 | 
			
		||||
      return false;
 | 
			
		||||
      #return $mail;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function verifixConvert(&$value): bool {
 | 
			
		||||
    if ($value === null) return true;
 | 
			
		||||
    elseif ($value === false) return false;
 | 
			
		||||
    if (is_array($value)) {
 | 
			
		||||
      [$local, $alias, $domain] = $value;
 | 
			
		||||
      if (strpos($local, ".") !== false) $local = strtolower($local);
 | 
			
		||||
      $domain = strtolower($domain);
 | 
			
		||||
      $value = "$local$alias$domain";
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										842
									
								
								nur_src/data/types/Metadata.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										842
									
								
								nur_src/data/types/Metadata.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,842 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
use nur\A;
 | 
			
		||||
use nur\b\params\IParametrable;
 | 
			
		||||
use nur\b\ValueException;
 | 
			
		||||
use nur\base;
 | 
			
		||||
use nur\md;
 | 
			
		||||
use nur\types;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class Metadata: gestion de données conformes à un schéma
 | 
			
		||||
 *
 | 
			
		||||
 * un schéma est un tableau d'éléments {$key => $sfield} où $sfield est un
 | 
			
		||||
 * tableau conforme au schéma {@link md::MSFIELD_SCHEMA}. Si $sfield n'est pas
 | 
			
		||||
 * un tableau, alors c'est la valeur par défaut
 | 
			
		||||
 *
 | 
			
		||||
 * l'élément {"" => $sinfos}, où $sinfos est un tableau conforme au schéma
 | 
			
		||||
 * {@link MSINFOS_SCHEMA}, est particulier: il définit des caractéristiques qui
 | 
			
		||||
 * concernent l'objet tout entier.
 | 
			
		||||
 *
 | 
			
		||||
 * --- ancienne doc à migrer --------------------------------------------------
 | 
			
		||||
 * Un méta-schéma sert à décrire les champs d'un schéma de données, e.g
 | 
			
		||||
 * ~~~
 | 
			
		||||
 * const FIELD_METASCHEMA = [
 | 
			
		||||
 *   "name" => [null, null, "nom de la colonne", true],
 | 
			
		||||
 *   "title" => [null, null, "libellé de la colonne", false],
 | 
			
		||||
 * ];
 | 
			
		||||
 * ~~~
 | 
			
		||||
 * Ce méta-schéma permettrait de décrire les champs d'un schéma comme celui-là:
 | 
			
		||||
 * ~~~
 | 
			
		||||
 * const DATA_SCHEMA = [
 | 
			
		||||
 *   "nom" => ["name" => "sn", "title" => "nom"],
 | 
			
		||||
 *   "prenom" => ["name" => "givenName", "title" => "prénom"],
 | 
			
		||||
 *   "pass" => ["name" => "password", "title" => "mot de passe"],
 | 
			
		||||
 * ]
 | 
			
		||||
 * ~~~
 | 
			
		||||
 *
 | 
			
		||||
 * Bien entendu, un méta-schéma peut servir aussi de schéma simple pour un
 | 
			
		||||
 * tableau de données
 | 
			
		||||
 */
 | 
			
		||||
class Metadata implements IParametrable {
 | 
			
		||||
  use _Tparametrable1;
 | 
			
		||||
 | 
			
		||||
  private static function itype($type): IType {
 | 
			
		||||
    return $type;
 | 
			
		||||
  }
 | 
			
		||||
  private static function citype($type): AbstractCompositeType {
 | 
			
		||||
    return $type;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static final function with($schema): Metadata {
 | 
			
		||||
    if ($schema instanceof Metadata) return $schema;
 | 
			
		||||
    elseif (is_array($schema)) return new static($schema);
 | 
			
		||||
    else throw ValueException::unexpected_type([Metadata::class, "array"], $schema);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const ENSURE_KEYS = true;
 | 
			
		||||
  const ORDER_KEYS = true;
 | 
			
		||||
  const ENSURE_TYPES = true;
 | 
			
		||||
  const CHECK_REQUIRED = true;
 | 
			
		||||
 | 
			
		||||
  const PARAMETRABLE_PARAMS_SCHEMA = [
 | 
			
		||||
    "ensure_keys" => ["bool", null, "toutes les clés doivent-elles être présentes?"],
 | 
			
		||||
    "order_keys" => ["bool", null, "les clés doivent-elle être dans l'ordre du schéma?"],
 | 
			
		||||
    "ensure_types" => ["bool", null, "les valeurs doivent-elles être du bon type?",
 | 
			
		||||
      "desc" => "cela ne concerne que les types simples standard",
 | 
			
		||||
    ],
 | 
			
		||||
    "check_required" => ["bool", null, "vérifier que les valeurs requises sont présentes?"],
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  function __construct(array $schema, ?array $params=null, ?IIncarnation $incarnation=null) {
 | 
			
		||||
    if ($incarnation === null) $incarnation = types::manager();
 | 
			
		||||
    $this->incarnation = $incarnation;
 | 
			
		||||
    $this->schema = self::_normalize_schema($schema);
 | 
			
		||||
 | 
			
		||||
    $this->initParametrableParams($params);
 | 
			
		||||
    base::update_n($this->ppEnsureKeys, static::ENSURE_KEYS);
 | 
			
		||||
    base::update_n($this->ppOrderKeys, static::ORDER_KEYS);
 | 
			
		||||
    base::update_n($this->ppEnsureTypes, static::ENSURE_TYPES);
 | 
			
		||||
    base::update_n($this->ppCheckRequired, static::CHECK_REQUIRED);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @var bool */
 | 
			
		||||
  protected $ppEnsureKeys;
 | 
			
		||||
 | 
			
		||||
  /** @var bool */
 | 
			
		||||
  protected $ppOrderKeys;
 | 
			
		||||
 | 
			
		||||
  /** @var bool */
 | 
			
		||||
  protected $ppEnsureTypes;
 | 
			
		||||
 | 
			
		||||
  /** @var bool */
 | 
			
		||||
  protected $ppCheckRequired;
 | 
			
		||||
 | 
			
		||||
  const SINFOS_SCHEMA = [
 | 
			
		||||
    "ctypes" => [
 | 
			
		||||
      "type" => "?array",
 | 
			
		||||
      "default" => null,
 | 
			
		||||
      "title" => "liste de types composites à appliquer à l'objet",
 | 
			
		||||
      "required" => false,
 | 
			
		||||
      "key" => "ctypes",
 | 
			
		||||
      "header" => "ctypes",
 | 
			
		||||
      "desc" => null,
 | 
			
		||||
      "schema" => null,
 | 
			
		||||
      "composite" => false,
 | 
			
		||||
    ],
 | 
			
		||||
    "apply2items" => [
 | 
			
		||||
      "type" => "bool",
 | 
			
		||||
      "default" => false,
 | 
			
		||||
      "title" => "le schéma s'applique-t-il aux éléments d'une liste séquentielle",
 | 
			
		||||
      "required" => false,
 | 
			
		||||
      "key" => "apply2items",
 | 
			
		||||
      "header" => "apply2items",
 | 
			
		||||
      "desc" => null,
 | 
			
		||||
      "schema" => null,
 | 
			
		||||
      "composite" => false,
 | 
			
		||||
    ],
 | 
			
		||||
  ];
 | 
			
		||||
  const SINFOS_INDEXES = ["ctypes" => 0, "apply2items" => 1];
 | 
			
		||||
 | 
			
		||||
  protected static function _normalize_schema(array $schema): array {
 | 
			
		||||
    if (count($schema) == 1 && array_key_exists(0, $schema)) {
 | 
			
		||||
      $apply2items = true;
 | 
			
		||||
      $schema = $schema[0];
 | 
			
		||||
    } else {
 | 
			
		||||
      $apply2items = false;
 | 
			
		||||
    }
 | 
			
		||||
    $sfields = [];
 | 
			
		||||
    $sinfos = ["ctypes" => null, "apply2items" => $apply2items]; # doit être conforme au schéma ci-dessus
 | 
			
		||||
    $cokeys = [];
 | 
			
		||||
    $cikeys = [];
 | 
			
		||||
    foreach ($schema as $key => $sfield) {
 | 
			
		||||
      if ($key !== "") {
 | 
			
		||||
        md::_ensure_msfield($sfield, $key);
 | 
			
		||||
 | 
			
		||||
        $type = $sfield["type"];
 | 
			
		||||
        $is_array_type = in_array($type, md::ARRAY_TYPES);
 | 
			
		||||
        if ($sfield["schema"] !== null) {
 | 
			
		||||
          if ($is_array_type) {
 | 
			
		||||
            $sfield["schema"] = self::_normalize_schema($sfield["schema"]);
 | 
			
		||||
            if (in_array($type, md::APPLY2ITEMS_TYPES)) {
 | 
			
		||||
              $sfield["schema"]["sinfos"]["apply2items"] = true;
 | 
			
		||||
            }
 | 
			
		||||
          } else {
 | 
			
		||||
            $sfield["schema"] = null;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        if ($sfield["composite"]) $cokeys[] = $key;
 | 
			
		||||
        $sfields[$key] = $sfield;
 | 
			
		||||
      } else {
 | 
			
		||||
        $sinfos = $sfield;
 | 
			
		||||
        md::_ensure_schema($sinfos, self::SINFOS_SCHEMA, self::SINFOS_INDEXES);
 | 
			
		||||
        $sinfos["apply2items"] = $apply2items;
 | 
			
		||||
        $citypes = $sinfos["ctypes"];
 | 
			
		||||
        if ($citypes !== null) $cikeys = array_keys($citypes);
 | 
			
		||||
      }
 | 
			
		||||
    }; unset($sfield);
 | 
			
		||||
    return [
 | 
			
		||||
      "sfields" => $sfields,
 | 
			
		||||
      "sinfos" => $sinfos,
 | 
			
		||||
      "indexes" => array_flip(array_keys($sfields)),
 | 
			
		||||
      "cokeys" => $cokeys,
 | 
			
		||||
      "cikeys" => $cikeys,
 | 
			
		||||
      "types" => null,
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @var IIncarnation */
 | 
			
		||||
  protected $incarnation;
 | 
			
		||||
 | 
			
		||||
  const schema_SCHEMA = [
 | 
			
		||||
    "sfields" => ["array", null, "liste des schémas des champs"],
 | 
			
		||||
    "sinfos" => ["array", null, "informations sur l'objet entier",
 | 
			
		||||
      "schema" => self::SINFOS_SCHEMA,
 | 
			
		||||
    ],
 | 
			
		||||
    "indexes" => ["array", null, "liste des indexes des clés, de la forme {key => index}"],
 | 
			
		||||
    "cokeys" => ["array", null, "liste des clés de champs composantes"],
 | 
			
		||||
    "cikeys" => ["array", null, "liste des clés de champs composites"],
 | 
			
		||||
    "types" => ["?array", null, "liste des types pour chaque élément de [skeys]"],
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /** @var array */
 | 
			
		||||
  protected $schema;
 | 
			
		||||
 | 
			
		||||
  /** obtenir le schéma normalisé */
 | 
			
		||||
  function getSchema(): array {
 | 
			
		||||
    $schema = ["" => $this->schema["sinfos"]];
 | 
			
		||||
    $schema = array_merge($schema, $this->schema["sfields"]);
 | 
			
		||||
    if ($this->schema["sinfos"]["apply2items"]) $schema = [$schema];
 | 
			
		||||
    return $schema;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** retourner la définition des champs */
 | 
			
		||||
  function getSfields(): array {
 | 
			
		||||
    return $this->schema["sfields"];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** retourner les clés des champs non composantes */
 | 
			
		||||
  function getSikeys(): array {
 | 
			
		||||
    $allKeys = array_keys($this->schema["sfields"]);
 | 
			
		||||
    return array_diff($allKeys, $this->schema["cokeys"]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** retourner les clés des champs composantes */
 | 
			
		||||
  function getCokeys(): array {
 | 
			
		||||
    return $this->schema["cokeys"];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** retourner toutes les clés simples */
 | 
			
		||||
  function getKeys(): array {
 | 
			
		||||
    return array_keys($this->schema["sfields"]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** tester si $key est une clé simple valide */
 | 
			
		||||
  function isKey(string $key): bool {
 | 
			
		||||
    return in_array($key, $this->getKeys());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** retourner les clés des champs composites */
 | 
			
		||||
  function getCikeys(): array {
 | 
			
		||||
    return $this->schema["cikeys"];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** retourner toutes les clés (les clés simples et les clés composites) */
 | 
			
		||||
  function getAllKeys(): array {
 | 
			
		||||
    $schema = $this->schema;
 | 
			
		||||
    return array_merge(array_keys($schema["sfields"]), $schema["cikeys"]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** obtenir l'instance de type correspondant au champ spécifié */
 | 
			
		||||
  function getType(string $key, bool $required=true): ?IType {
 | 
			
		||||
    $types = $this->_getTypes($this->schema);
 | 
			
		||||
    $type = A::get($types, $key);
 | 
			
		||||
    if ($type === null && $required) {
 | 
			
		||||
      throw ValueException::invalid_value($key, "type");
 | 
			
		||||
    }
 | 
			
		||||
    return $type;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function _getTypes(array &$schema): array {
 | 
			
		||||
    if ($schema["types"] === null) {
 | 
			
		||||
      $types = [];
 | 
			
		||||
      foreach ($schema["sfields"] as $key => &$sfield) {
 | 
			
		||||
        $types[$key] = $this->_buildType($sfield["type"], $sfield);
 | 
			
		||||
        if ($sfield["schema"] !== null) {
 | 
			
		||||
          $this->_getTypes($sfield["schema"]);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      $ctypes = $schema["sinfos"]["ctypes"];
 | 
			
		||||
      if ($ctypes !== null) {
 | 
			
		||||
        foreach ($ctypes as $key => $type) {
 | 
			
		||||
          $types[$key] = $this->_buildType($type);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      $schema["types"] = $types;
 | 
			
		||||
    }
 | 
			
		||||
    return $schema["types"];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private function _buildType($type, $sfield=null): IType {
 | 
			
		||||
    if (!$this->incarnation->hasType($type) && is_array($type)) {
 | 
			
		||||
      $tkey = array_key_first($type);
 | 
			
		||||
      if (count($type) == 1) {
 | 
			
		||||
        # syntaxe [tkey => Type], dans ce cas c'est la définition du
 | 
			
		||||
        # champ qui est passé en paramètres au type
 | 
			
		||||
        $type = [$tkey => $type[$tkey], $sfield];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $this->incarnation->getType($type);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #############################################################################
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * s'assurer que $item est conforme à la structure du schéma, c'est à dire que
 | 
			
		||||
   * tous les champs sont présents dans l'ordre.
 | 
			
		||||
   *
 | 
			
		||||
   * de plus, par défaut, si les champs ont des types connus, ils sont honorés.
 | 
			
		||||
   * les autres champs sont laissés en l'état.
 | 
			
		||||
   *
 | 
			
		||||
   * $item peut être un tableau séquentiel ou associatif (ou un mix des deux).
 | 
			
		||||
   * s'assurer que toutes les clés définies dans le schéma existent dans $item
 | 
			
		||||
   * en les créant avec la valeur par défaut le cas échéant.
 | 
			
		||||
   *
 | 
			
		||||
   * soit une clé $key du schéma:
 | 
			
		||||
   * - avec le type par défaut, si $item[$key] === false alors on considère que
 | 
			
		||||
   * cette clé n'existe pas et elle est remplacée par la valeur par défaut.
 | 
			
		||||
   * Si un type est défini, le comportement peut être différent. cf la doc de
 | 
			
		||||
   * {@link md::MSFIELD_SCHEMA} pour les détails
 | 
			
		||||
   * - si la clé n'existe pas dans $item (même pas avec la valeur false),
 | 
			
		||||
   * alors chercher la première valeur séquentielle qui n'a pas encore été
 | 
			
		||||
   * analysée, et prendre cela comme valeur. bien entendu, si la valeur est
 | 
			
		||||
   * false, les mêmes règles que ci-dessus s'appliquent
 | 
			
		||||
   *
 | 
			
		||||
   * après l'appel de cette fonction, $item est un tableau associatif. les
 | 
			
		||||
   * clés séquentielles qui n'ont pas été utilisées sont gardées mais
 | 
			
		||||
   * renumérotées
 | 
			
		||||
   * ~~~
 | 
			
		||||
   * $item = [1, 2, 3];
 | 
			
		||||
   * $md = new Metadata(["a" => "x", "b" => "y"]);
 | 
			
		||||
   * $md->ensureSchema($item);
 | 
			
		||||
   * // maintenant, $item vaut ["a" => 1, "b" => 2, 3];
 | 
			
		||||
   * ~~~
 | 
			
		||||
   *
 | 
			
		||||
   * si $recursive==true, alors les éléments ayant un sous-schéma sont examinés
 | 
			
		||||
   * et corrigés aussi le cas échéant.
 | 
			
		||||
   *
 | 
			
		||||
   * Comme cas particulier, si $schema contient un unique élément de type array
 | 
			
		||||
   * avec l'index 0, alors $sfield est transformé en tableau et chacun de ses
 | 
			
		||||
   * éléments est corrigé pour être conforme au schema $schema[0]. Pour cela,
 | 
			
		||||
   * il faut que $recursive==true
 | 
			
		||||
   *
 | 
			
		||||
   * $key est utilisé dans le cas où les données proviennent d'un tableau de la
 | 
			
		||||
   * forme [..., $key => $sfield, ...]
 | 
			
		||||
   * si $key n'est pas null, alors on considère que c'est la première valeur de
 | 
			
		||||
   * du tableau $sfield. Par exemple, les deux ensembles de commandes suivants
 | 
			
		||||
   * sont équivalents:
 | 
			
		||||
   * ~~~
 | 
			
		||||
   * $schema = ["a" => "x", "b" => "y"];
 | 
			
		||||
   * # sans $key
 | 
			
		||||
   * $sfield = [1, 2];
 | 
			
		||||
   * metaschema::ensure_schema($sfield, $schema);
 | 
			
		||||
   * // $sfield vaut maintenant ["a" => 1, "b" => 2]
 | 
			
		||||
   * # avec $key
 | 
			
		||||
   * $sfield = [2];
 | 
			
		||||
   * metaschema::ensure_schema($sfield, $schema, 1);
 | 
			
		||||
   * // $sfield vaut maintenant ["a" => 1, "b" => 2]
 | 
			
		||||
   * ~~~
 | 
			
		||||
   */
 | 
			
		||||
  function ensureSchema(&$item, $key=null, ?array $params=null): void {
 | 
			
		||||
    $ensureKeys = A::get($params, "ensure_keys", $this->ppEnsureKeys);
 | 
			
		||||
    $orderKeys = A::get($params, "order_keys", $this->ppOrderKeys);
 | 
			
		||||
    $ensureTypes = A::get($params, "ensure_types", $this->ppEnsureTypes);
 | 
			
		||||
    $checkRequired = A::get($params, "check_required", $this->ppCheckRequired);
 | 
			
		||||
    $recursive = A::get($params, "recursive", true);
 | 
			
		||||
 | 
			
		||||
    $schema = $this->schema;
 | 
			
		||||
    $this->_ensureArrayItem($item, $schema, $key);
 | 
			
		||||
    $this->_ensureSchema($item, $schema, $ensureKeys, $ensureTypes, $recursive);
 | 
			
		||||
    if ($orderKeys) $this->orderKeys($item, $recursive);
 | 
			
		||||
    if ($checkRequired) $this->checkRequired($item, $recursive);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private function _ensureArrayItem(&$item, array $schema, $itemKey) {
 | 
			
		||||
    if ($itemKey !== null) {
 | 
			
		||||
      if (is_array($item)) {
 | 
			
		||||
        # n'utiliser key que si la première clé du schéma n'existe pas
 | 
			
		||||
        $sfields = $schema["sfields"];
 | 
			
		||||
        $first_key = array_key_first($sfields);
 | 
			
		||||
        if (!array_key_exists($first_key, $item)) {
 | 
			
		||||
          $tmp = [$itemKey];
 | 
			
		||||
          A::merge3($tmp, $item);
 | 
			
		||||
          $item = $tmp;
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        $item = [$itemKey, $item];
 | 
			
		||||
      }
 | 
			
		||||
    } elseif ($item !== null && !is_array($item)) {
 | 
			
		||||
      $item = [$item];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private function _ensureSchema(?array &$item, array $schema, bool $ensureKeys, bool $ensureTypes, bool $recursive, ?bool $apply2items=null): void {
 | 
			
		||||
    [
 | 
			
		||||
      "sfields" => $sfields,
 | 
			
		||||
      "sinfos" => $sinfos,
 | 
			
		||||
      "indexes" => $indexes,
 | 
			
		||||
      #"cokeys" => $cokeys,
 | 
			
		||||
      #"cikeys" => $cikeys,
 | 
			
		||||
      #"types" => $types,
 | 
			
		||||
    ] = $schema;
 | 
			
		||||
    if ($apply2items === null) $apply2items = $sinfos["apply2items"];
 | 
			
		||||
    if ($apply2items) {
 | 
			
		||||
      $items =& $item; unset($item);
 | 
			
		||||
      $index = 0;
 | 
			
		||||
      foreach ($items as $key => &$item) {
 | 
			
		||||
        if ($key === $index) {
 | 
			
		||||
          $index++;
 | 
			
		||||
          $key = null;
 | 
			
		||||
        }
 | 
			
		||||
        $this->_ensureArrayItem($item, $schema, $key);
 | 
			
		||||
        $this->_ensureSchema($item, $schema, $ensureKeys, $ensureTypes, $recursive, false);
 | 
			
		||||
      }; unset($item);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($item === null) $item = [];
 | 
			
		||||
    $src = $item;
 | 
			
		||||
    $keys = array_keys($sfields);
 | 
			
		||||
    $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, $sfields);
 | 
			
		||||
        if ($ensureTypes && $is_schema_key) {
 | 
			
		||||
          self::_ensure_type($sfields[$key], $value, 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]) {
 | 
			
		||||
            if ($ensureTypes) {
 | 
			
		||||
              self::_ensure_type($sfields[$key], $value, 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
 | 
			
		||||
    if ($ensureKeys) {
 | 
			
		||||
      foreach ($dones as $dindex => $done) {
 | 
			
		||||
        if (!$done) {
 | 
			
		||||
          $key = $keys[$dindex];
 | 
			
		||||
          $sfield = $sfields[$key];
 | 
			
		||||
          $value = $sfield["default"];
 | 
			
		||||
          if ($ensureTypes) {
 | 
			
		||||
            self::_ensure_type($sfield, $value, false);
 | 
			
		||||
          }
 | 
			
		||||
          $item[$key] = $value;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($recursive) {
 | 
			
		||||
      foreach ($sfields as $key => $sfield) {
 | 
			
		||||
        $schema2 = $sfield["schema"];
 | 
			
		||||
        if ($schema2 === null) continue;
 | 
			
		||||
        switch ($sfield["type"]) {
 | 
			
		||||
        case "?array":
 | 
			
		||||
          if ($item[$key] === null) continue 2;
 | 
			
		||||
        case "array":
 | 
			
		||||
          $this->_ensureArrayItem($item[$key], $schema2, null);
 | 
			
		||||
          $this->_ensureSchema($item[$key], $schema2, $ensureKeys, $ensureTypes, true, false);
 | 
			
		||||
          break;
 | 
			
		||||
        case "?array[]":
 | 
			
		||||
          if ($item[$key] === null) continue 2;
 | 
			
		||||
        case "array[]":
 | 
			
		||||
          $this->_ensureSchema($item[$key], $schema2, $ensureKeys, $ensureTypes, true, true);
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static function _ensure_type($sfield, &$value, bool $exists): void {
 | 
			
		||||
    $type = $sfield["type"];
 | 
			
		||||
    $default = $sfield["default"];
 | 
			
		||||
    if (md::_check_known_type($type, $value, $default, $exists)) {
 | 
			
		||||
      md::_convert_value($type, $value);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #############################################################################
 | 
			
		||||
 | 
			
		||||
  /** ordonner les champs de item selon l'ordre du schéma */
 | 
			
		||||
  function orderKeys(array &$item, bool $recursive=true): void {
 | 
			
		||||
    $sfields = $this->schema["sfields"];
 | 
			
		||||
    $indexes = $this->schema["indexes"];
 | 
			
		||||
    if ($this->schema["sinfos"]["apply2items"]) {
 | 
			
		||||
      $this->_eachOrderKeys($sfields, $indexes, $item, $recursive);
 | 
			
		||||
    } else {
 | 
			
		||||
      $this->_orderKeys($sfields, $indexes, $item, $recursive);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function _eachOrderKeys(array $sfields, array $indexes, array &$items, bool $recursive) {
 | 
			
		||||
    foreach ($items as &$item) {
 | 
			
		||||
      $this->_orderKeys($sfields, $indexes, $item, $recursive);
 | 
			
		||||
    }; unset($item);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function _orderKeys(array $sfields, array $indexes, array &$item, bool $recursive) {
 | 
			
		||||
    $maxIndex = count($sfields);
 | 
			
		||||
    $index = 0;
 | 
			
		||||
    $ordered = true;
 | 
			
		||||
    foreach (array_keys($item) as $key) {
 | 
			
		||||
      if (!key_exists($key, $sfields) || $indexes[$key] !== $index) {
 | 
			
		||||
        $ordered = false;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      $index++;
 | 
			
		||||
      if ($index == $maxIndex) break;
 | 
			
		||||
    }
 | 
			
		||||
    if ($ordered) return;
 | 
			
		||||
 | 
			
		||||
    $remaining = $item;
 | 
			
		||||
    $ordered = [];
 | 
			
		||||
    foreach (array_keys($sfields) as $key) {
 | 
			
		||||
      if (array_key_exists($key, $remaining)) {
 | 
			
		||||
        $ordered[$key] = $remaining[$key];
 | 
			
		||||
        unset($remaining[$key]);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    $item = [];
 | 
			
		||||
    A::merge2($item, $ordered, $remaining);
 | 
			
		||||
 | 
			
		||||
    if ($recursive) {
 | 
			
		||||
      foreach ($sfields as $key => $sfield) {
 | 
			
		||||
        $schema2 = $sfield["schema"];
 | 
			
		||||
        if ($schema2 === null || $item[$key] === null) continue;
 | 
			
		||||
        $sfields2 = $schema2["sfields"];
 | 
			
		||||
        $indexes2 = $schema2["indexes"];
 | 
			
		||||
        if ($schema2["sinfos"]["apply2items"]) {
 | 
			
		||||
          $this->_eachOrderKeys($sfields2, $indexes2, $item[$key], $recursive);
 | 
			
		||||
        } else {
 | 
			
		||||
          $this->_orderKeys($sfields2, $indexes2, $item[$key], $recursive);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 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}
 | 
			
		||||
   *
 | 
			
		||||
   * $item n'a pas besoin d'être conforme au schéma au préalable
 | 
			
		||||
   */
 | 
			
		||||
  function checkRequired(array $item, bool $recursive=true): void {
 | 
			
		||||
    $sfields = $this->schema["sfields"];
 | 
			
		||||
    if ($this->schema["sinfos"]["apply2items"]) {
 | 
			
		||||
      $this->_eachCheckRequired($sfields, "", $item, $recursive);
 | 
			
		||||
    } else {
 | 
			
		||||
      $this->_checkRequired($sfields, "", $item, $recursive);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private function _eachCheckRequired(array $sfields, $keyPrefix, array $items, bool $recursive): void {
 | 
			
		||||
    foreach ($items as $key => $item) {
 | 
			
		||||
      $this->_checkRequired($sfields, "$keyPrefix$key.", $item, $recursive);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private function _checkRequired(array $sfields, $keyPrefix, array $item, bool $recursive, ?bool $apply2items=null): void {
 | 
			
		||||
    foreach ($sfields as $key => $sfield) {
 | 
			
		||||
      if ($sfield["required"]) {
 | 
			
		||||
        if (!array_key_exists($key, $item) || $item[$key] === null) {
 | 
			
		||||
          throw new ValueException("$keyPrefix$key is required");
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      $schema2 = $sfield["schema"];
 | 
			
		||||
      if ($schema2 !== null && $recursive) {
 | 
			
		||||
        A::ensure_array($item[$key]);
 | 
			
		||||
        $sfields2 = $schema2["sfields"];
 | 
			
		||||
        $keyPrefix = "$keyPrefix$key.";
 | 
			
		||||
        if ($schema2["sinfos"]["apply2items"]) {
 | 
			
		||||
          $this->_eachCheckRequired($sfields2, $keyPrefix, $item[$key], $recursive);
 | 
			
		||||
        } else {
 | 
			
		||||
          $this->_checkRequired($sfields2, $keyPrefix, $item[$key], $recursive);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #############################################################################
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Vérifier si la clé $key existe dans $item, en tenant compte des
 | 
			
		||||
   * informations 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 séquentiel
 | 
			
		||||
   *
 | 
			
		||||
   * les clés composites ne sont pas supportées (le résultat est toujours false
 | 
			
		||||
   * même si le tableau contient une clé du nom de la clé composite)
 | 
			
		||||
   *
 | 
			
		||||
   * XXX si le type n'est pas bool, interpréter false comme "non présent"
 | 
			
		||||
   */
 | 
			
		||||
  function has($item, $key): bool {
 | 
			
		||||
    $cikeys = $this->schema["cikeys"];
 | 
			
		||||
    $sfields = $this->schema["sfields"];
 | 
			
		||||
    if (in_array($key, $cikeys)) {
 | 
			
		||||
      # valeur composite
 | 
			
		||||
      return false;
 | 
			
		||||
    } elseif (!array_key_exists($key, $sfields)) {
 | 
			
		||||
      return is_array($item) && array_key_exists($key, $item);
 | 
			
		||||
    } else {
 | 
			
		||||
      if (!is_array($item)) $item = [$item];
 | 
			
		||||
      if (array_key_exists($key, $item)) return true;
 | 
			
		||||
      $index = $this->schema["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 séquentiel
 | 
			
		||||
   *
 | 
			
		||||
   * les clés composites sont supportées et sont retournées sous forme de
 | 
			
		||||
   * tableau
 | 
			
		||||
   */
 | 
			
		||||
  function get($item, $key, $default=null, bool $ensure_type=true) {
 | 
			
		||||
    $cikeys = $this->schema["cikeys"];
 | 
			
		||||
    $sfields = $this->schema["sfields"];
 | 
			
		||||
    if (in_array($key, $cikeys)) {
 | 
			
		||||
      # valeur composite
 | 
			
		||||
      if (!is_array($item)) $item = [$item];
 | 
			
		||||
      $values = [];
 | 
			
		||||
      $type = self::citype($this->getType($key));
 | 
			
		||||
      foreach ($type->getComponents() as $cvalue) {
 | 
			
		||||
        $key = $cvalue["key"];
 | 
			
		||||
        $sfield = $sfields[$key];
 | 
			
		||||
        $values[$key] = $this->_get($sfield, $item, $key, null, $ensure_type);
 | 
			
		||||
      }
 | 
			
		||||
      return $values;
 | 
			
		||||
    } elseif (!array_key_exists($key, $sfields)) {
 | 
			
		||||
      if (is_array($item) && array_key_exists($key, $item)) {
 | 
			
		||||
        return $item[$key];
 | 
			
		||||
      } else {
 | 
			
		||||
        return $default;
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      if (!is_array($item)) $item = [$item];
 | 
			
		||||
      $sfield = $sfields[$key];
 | 
			
		||||
      return $this->_get($sfield, $item, $key, $default, $ensure_type);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private function _get(array $sfield, ?array $item, $key, $default=null, bool $ensureType=true) {
 | 
			
		||||
    if (array_key_exists($key, $item)) {
 | 
			
		||||
      $exists = true;
 | 
			
		||||
      $value = $item[$key];
 | 
			
		||||
    } else {
 | 
			
		||||
      $index = $this->schema["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 ($ensureType) {
 | 
			
		||||
      self::_ensure_type($sfield, $value, $exists);
 | 
			
		||||
      $schema2 = $sfield["schema"];
 | 
			
		||||
      if ($schema2 !== null) $this->_ensureSchema($value, $schema2, $this->ppEnsureKeys, $this->ppEnsureTypes, true);
 | 
			
		||||
    }
 | 
			
		||||
    return $value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * spécifier la valeur de la clé $key dans $item en tenant compte des
 | 
			
		||||
   * informations du schéma.
 | 
			
		||||
   *
 | 
			
		||||
   * les clés composites sont supportées et doivent être fournies sous forme de
 | 
			
		||||
   * tableau
 | 
			
		||||
   */
 | 
			
		||||
  function set(?array &$item, $key, $value): void {
 | 
			
		||||
    $cikeys = $this->schema["cikeys"];
 | 
			
		||||
    $sfields = $this->schema["sfields"];
 | 
			
		||||
    if (in_array($key, $cikeys)) {
 | 
			
		||||
      # valeur composite
 | 
			
		||||
      if ($value === null) return;
 | 
			
		||||
      $type = self::citype($this->getType($key));
 | 
			
		||||
      if (!is_array($value)) {
 | 
			
		||||
        throw ValueException::unexpected_type("array", $value);
 | 
			
		||||
      }
 | 
			
		||||
      foreach ($type->getComponents() as $cname => $cvalue) {
 | 
			
		||||
        $ctype = $type->getCtype($cname);
 | 
			
		||||
        $key = $cvalue["key"];
 | 
			
		||||
        if (array_key_exists($key, $value)) {
 | 
			
		||||
          $item[$key] = $ctype->with($this->get($value, $key));
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } elseif (!array_key_exists($key, $sfields)) {
 | 
			
		||||
      $item[$key] = $value;
 | 
			
		||||
    } else {
 | 
			
		||||
      $type = $this->getType($key);
 | 
			
		||||
      $item[$key] = $type->with($value);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * sélectionner dans $items les valeurs du schéma, et les retourner dans
 | 
			
		||||
   * l'ordre du schéma sous forme de tableau associatif.
 | 
			
		||||
   *
 | 
			
		||||
   * $item doit aussi ê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, $ensureType==true permet
 | 
			
		||||
   * de s'assurer aussi que les valeurs sont dans le bon type
 | 
			
		||||
   */
 | 
			
		||||
  function getValues(?array $item, bool $ensureType=false): array {
 | 
			
		||||
    if ($item === null) return [];
 | 
			
		||||
    $values = [];
 | 
			
		||||
    foreach ($this->schema["sfields"] as $key => $sfield) {
 | 
			
		||||
      if (!array_key_exists($key, $item)) continue;
 | 
			
		||||
      $value = $item[$key];
 | 
			
		||||
      if ($ensureType) {
 | 
			
		||||
        self::_ensure_type($sfield, $value, true);
 | 
			
		||||
      }
 | 
			
		||||
      $values[$key] = $value;
 | 
			
		||||
    }
 | 
			
		||||
    return $values;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * complément de {@link getValues()}: retourner les clés qui n'ont pas été
 | 
			
		||||
   * sélectionnées
 | 
			
		||||
   */
 | 
			
		||||
  function getOthers(?array $item): array {
 | 
			
		||||
    if ($item === null) return [];
 | 
			
		||||
    $sfields = $this->schema["sfields"];
 | 
			
		||||
    $others = [];
 | 
			
		||||
    foreach ($item as $key => $value) {
 | 
			
		||||
      if (array_key_exists($key, $sfields)) continue;
 | 
			
		||||
      $others[$key] = $value;
 | 
			
		||||
    }
 | 
			
		||||
    return $others;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * formatter chacun des champs de $item. on assume que $item est déjà conforme
 | 
			
		||||
   * au schéma
 | 
			
		||||
   */
 | 
			
		||||
  function format($item): array {
 | 
			
		||||
    $cikeys = $this->schema["cikeys"];
 | 
			
		||||
    $types = $this->_getTypes($this->schema);
 | 
			
		||||
    $result = [];
 | 
			
		||||
    foreach ($this->getAllKeys() as $key) {
 | 
			
		||||
      $type = self::itype($types[$key]);
 | 
			
		||||
      if (in_array($key, $cikeys)) {
 | 
			
		||||
        $value = $type->format($item);
 | 
			
		||||
      } else {
 | 
			
		||||
        $value = $type->format($item[$key]);
 | 
			
		||||
      }
 | 
			
		||||
      $result[$key] = $value;
 | 
			
		||||
    }
 | 
			
		||||
    return $result;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * vérifier et corriger chaque champ de $item. on assume que $item est déjà
 | 
			
		||||
   * conforme au moins à la structure du schéma
 | 
			
		||||
   */
 | 
			
		||||
  function verifix(&$item, ?array &$results=null, bool $throw=false, bool $recursive=false): void {
 | 
			
		||||
    $this->_getTypes($this->schema);
 | 
			
		||||
    $this->_verifix($item, $results, $throw, $this->schema, $recursive);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function _verifix(&$item, ?array &$results, bool $throw, array $schema, bool $recursive): void {
 | 
			
		||||
    foreach ($schema["sfields"] as $key => $sfield) {
 | 
			
		||||
      if (!$sfield["composite"]) {
 | 
			
		||||
        $type = self::itype($schema["types"][$key]);
 | 
			
		||||
        $type->verifix($item[$key], $results[$key], $throw);
 | 
			
		||||
      }
 | 
			
		||||
      if ($recursive && $sfield["schema"] !== null) {
 | 
			
		||||
        $this->_verifix($item[$key], $results[$key], $throw, $sfield["schema"], true);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    foreach ($schema["cikeys"] as $key) {
 | 
			
		||||
      $type = self::itype($schema["types"][$key]);
 | 
			
		||||
      $type->verifix($item, $results[$key], $throw);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * méthode de convenance pour corriger de façon récursive tous les types, et
 | 
			
		||||
   * lancer une exception en cas d'erreur
 | 
			
		||||
   */
 | 
			
		||||
  function ensureTypes(&$item): void {
 | 
			
		||||
    $this->verifix($item, $results, true, true);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #############################################################################
 | 
			
		||||
 | 
			
		||||
  function eachEnsureSchema(?iterable &$items, ?array $params=null): void {
 | 
			
		||||
    if ($items === null) return;
 | 
			
		||||
    $index = 0;
 | 
			
		||||
    foreach ($items as $key => &$item) {
 | 
			
		||||
      if ($key === $index++) $key = null;
 | 
			
		||||
      $this->ensureSchema($item, $key, $params);
 | 
			
		||||
    }; unset($item);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function eachEnsureTypes(?iterable &$items): void {
 | 
			
		||||
    if ($items === null) return;
 | 
			
		||||
    foreach ($items as &$item) {
 | 
			
		||||
      $this->ensureTypes($item);
 | 
			
		||||
    }; unset($item);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function eachCheckRequired(?iterable $items, bool $recursive=true): void {
 | 
			
		||||
    if ($items === null) return;
 | 
			
		||||
    foreach ($items as $item) {
 | 
			
		||||
      $this->checkRequired($item, $recursive);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function eachFormat(?iterable $items): ?array {
 | 
			
		||||
    if ($items === null) return null;
 | 
			
		||||
    $result = [];
 | 
			
		||||
    foreach ($items as $item) {
 | 
			
		||||
      $result[] = $this->format($item);
 | 
			
		||||
    }
 | 
			
		||||
    return $result;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function eachVerifix(?iterable &$items, ?array &$itemResults=null, bool $throw=false, bool $recursive=false): void {
 | 
			
		||||
    if ($items === null) return;
 | 
			
		||||
    $itemResults = [];
 | 
			
		||||
    foreach ($items as $key => &$item) {
 | 
			
		||||
      $this->verifix($item, $results, $throw, $recursive);
 | 
			
		||||
      $itemResults[$key] = $results;
 | 
			
		||||
    }; unset($item);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								nur_src/data/types/MixedType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								nur_src/data/types/MixedType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class MixedType: n'importe quel type: on ne fait aucun effort pour déterminer
 | 
			
		||||
 * le type effectif ni pour chercher à convertir ou normaliser les valeurs
 | 
			
		||||
 */
 | 
			
		||||
class MixedType extends AbstractSimpleType {
 | 
			
		||||
  const TITLE = "type indifférent";
 | 
			
		||||
 | 
			
		||||
  const ALLOW_NULL = true;
 | 
			
		||||
 | 
			
		||||
  function getClass(): string {
 | 
			
		||||
    return "mixed";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function isInstance($value, bool $strict=false): bool {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function parse(string &$input) {
 | 
			
		||||
    $value = $input;
 | 
			
		||||
    $input = "";
 | 
			
		||||
    return $value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function _verifix(&$value, array &$result=null): void {
 | 
			
		||||
    self::result_valid($result, $value, $value);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										213
									
								
								nur_src/data/types/PhpIncarnation.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								nur_src/data/types/PhpIncarnation.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,213 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
use nur\A;
 | 
			
		||||
use nur\func;
 | 
			
		||||
use nur\ref\ref_type;
 | 
			
		||||
 | 
			
		||||
class PhpIncarnation implements IIncarnation {
 | 
			
		||||
  function __construct() {
 | 
			
		||||
    # non nullable
 | 
			
		||||
    $this->types[ref_type::MIXED] = new MixedType(["allow_null" => false]);
 | 
			
		||||
    $this->types[ref_type::BOOL] = new BoolType(["allow_null" => false]);
 | 
			
		||||
    $this->types[ref_type::INT] = new IntType(["allow_null" => false]);
 | 
			
		||||
    $this->types[ref_type::FLOAT] = new FloatType(["allow_null" => false]);
 | 
			
		||||
    $this->types[ref_type::RAWSTRING] = new RawStringType(["allow_null" => false]);
 | 
			
		||||
    $this->types[ref_type::STRING] = new StringType(["allow_null" => false]);
 | 
			
		||||
    $this->types[ref_type::TEXT] = new TextType(["allow_null" => false]);
 | 
			
		||||
    $this->types[ref_type::KEY] = new KeyType(["allow_null" => false]);
 | 
			
		||||
    $this->types[ref_type::CONTENT] = new ContentType(["allow_null" => false]);
 | 
			
		||||
    $this->types[ref_type::FILE] = new FileType(["allow_null" => false]);
 | 
			
		||||
    $this->types[ref_type::DATETIME] = new SDatetimeType(["allow_null" => false]);
 | 
			
		||||
    $this->types[ref_type::DATE] = new SDateType(["allow_null" => false]);
 | 
			
		||||
    $this->types[ref_type::TIME] = new STimeType(["allow_null" => false]);
 | 
			
		||||
    $this->types[ref_type::HOUR] = new SHourType(["allow_null" => false]);
 | 
			
		||||
    # nullable
 | 
			
		||||
    $this->types[ref_type::NMIXED] = new MixedType(["allow_null" => true]);
 | 
			
		||||
    $this->types[ref_type::NBOOL] = new BoolType(["allow_null" => true]);
 | 
			
		||||
    $this->types[ref_type::TRIBOOL] = new TriboolType(["allow_null" => true]);
 | 
			
		||||
    $this->types[ref_type::NTRIBOOL] = new TriboolType(["allow_null" => true]);
 | 
			
		||||
    $this->types[ref_type::NINT] = new IntType(["allow_null" => true]);
 | 
			
		||||
    $this->types[ref_type::NFLOAT] = new FloatType(["allow_null" => true]);
 | 
			
		||||
    $this->types[ref_type::NRAWSTRING] = new RawStringType(["allow_null" => true]);
 | 
			
		||||
    $this->types[ref_type::NSTRING] = new StringType(["allow_null" => true]);
 | 
			
		||||
    $this->types[ref_type::NTEXT] = new TextType(["allow_null" => true]);
 | 
			
		||||
    $this->types[ref_type::NKEY] = new KeyType(["allow_null" => true]);
 | 
			
		||||
    $this->types[ref_type::NCONTENT] = new ContentType(["allow_null" => true]);
 | 
			
		||||
    $this->types[ref_type::NFILE] = new FileType(["allow_null" => true]);
 | 
			
		||||
    $this->types[ref_type::NDATETIME] = new SDatetimeType(["allow_null" => true]);
 | 
			
		||||
    $this->types[ref_type::NDATE] = new SDateType(["allow_null" => true]);
 | 
			
		||||
    $this->types[ref_type::NTIME] = new STimeType(["allow_null" => true]);
 | 
			
		||||
    $this->types[ref_type::NHOUR] = new SHourType(["allow_null" => true]);
 | 
			
		||||
    # generic
 | 
			
		||||
    $this->types[ref_type::NARRAY] = new GenericType(GenericType::ARRAY, ["allow_null" => true]);
 | 
			
		||||
    $this->types[ref_type::ARRAY] = new GenericType(GenericType::ARRAY, ["allow_null" => false]);
 | 
			
		||||
    $this->types[ref_type::NARRAY_ARRAY] = new GenericType(GenericType::ARRAY_ARRAY, ["allow_null" => true]);
 | 
			
		||||
    $this->types[ref_type::ARRAY_ARRAY] = new GenericType(GenericType::ARRAY_ARRAY, ["allow_null" => false]);
 | 
			
		||||
    $this->types[ref_type::NITERABLE] = new GenericType(GenericType::ITERABLE, ["allow_null" => true]);
 | 
			
		||||
    $this->types[ref_type::ITERABLE] = new GenericType(GenericType::ITERABLE, ["allow_null" => false]);
 | 
			
		||||
    $this->types[ref_type::NRESOURCE] = new GenericType(GenericType::RESOURCE, ["allow_null" => true]);
 | 
			
		||||
    $this->types[ref_type::RESOURCE] = new GenericType(GenericType::RESOURCE, ["allow_null" => false]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const CLASS_ALIASES = [
 | 
			
		||||
    ref_type::BOOL => BoolType::class,
 | 
			
		||||
    ref_type::TRIBOOL => TriboolType::class,
 | 
			
		||||
    ref_type::INT => IntType::class,
 | 
			
		||||
    ref_type::FLOAT => FloatType::class,
 | 
			
		||||
    ref_type::RAWSTRING => RawStringType::class,
 | 
			
		||||
    ref_type::STRING => StringType::class,
 | 
			
		||||
    ref_type::TEXT => TextType::class,
 | 
			
		||||
    ref_type::KEY => KeyType::class,
 | 
			
		||||
    ref_type::CONTENT => ContentType::class,
 | 
			
		||||
    ref_type::FILE => FileType::class,
 | 
			
		||||
    ref_type::DATETIME => SDatetimeType::class,
 | 
			
		||||
    ref_type::DATE => SDateType::class,
 | 
			
		||||
    ref_type::TIME => STimeType::class,
 | 
			
		||||
    ref_type::HOUR => SHourType::class,
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /** @var array liste d'associations {nom type => classe} */
 | 
			
		||||
  protected $typeClasses = [];
 | 
			
		||||
 | 
			
		||||
  /** @var array liste d'associations {nom type => instance de types} */
 | 
			
		||||
  protected $types = [];
 | 
			
		||||
 | 
			
		||||
  /** @var array liste de définitions de types avec leurs arguments */
 | 
			
		||||
  protected $dynamicTypes = [];
 | 
			
		||||
 | 
			
		||||
  private static function fix_name(&$name) {
 | 
			
		||||
    if ($name === null) $name = "mixed";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function hasType($name): bool {
 | 
			
		||||
    self::fix_name($name);
 | 
			
		||||
 | 
			
		||||
    if (is_array($name)) {
 | 
			
		||||
      $key = array_key_first($name);
 | 
			
		||||
      if ($key !== 0) {
 | 
			
		||||
        # type dynamique nommé
 | 
			
		||||
        $name = $key;
 | 
			
		||||
      } else {
 | 
			
		||||
        # type dynamique
 | 
			
		||||
        foreach ($this->dynamicTypes as $dtype) {
 | 
			
		||||
          if ($name === $dtype) return true;
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
    } elseif (!array_key_exists($name, $this->types)) {
 | 
			
		||||
      $name = A::get(ref_type::ALIASES, $name, $name);
 | 
			
		||||
    }
 | 
			
		||||
    # type statique ou dynamique nommé
 | 
			
		||||
    $type = A::get($this->types, $name);
 | 
			
		||||
    if ($type !== null) return true;
 | 
			
		||||
    return A::get($this->typeClasses, $name) !== null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function getType($name, bool $required=true): ?IType {
 | 
			
		||||
    self::fix_name($name);
 | 
			
		||||
 | 
			
		||||
    ## vérifier si le type est déjà défini
 | 
			
		||||
    $dclass = null;
 | 
			
		||||
    $dtype = null;
 | 
			
		||||
    if (is_array($name)) {
 | 
			
		||||
      $dtype = $name;
 | 
			
		||||
      $key = array_key_first($dtype);
 | 
			
		||||
      if ($key !== 0) {
 | 
			
		||||
        # type dynamique nommé
 | 
			
		||||
        $name = $key;
 | 
			
		||||
        $dclass = $dtype[$name];
 | 
			
		||||
        unset($dtype[$name]);
 | 
			
		||||
        array_unshift($dtype, $dclass);
 | 
			
		||||
      } else {
 | 
			
		||||
        # type dynamique anonyme
 | 
			
		||||
        $name = null;
 | 
			
		||||
      }
 | 
			
		||||
    } elseif (!array_key_exists($name, $this->types)) {
 | 
			
		||||
      $name = A::get(ref_type::ALIASES, $name, $name);
 | 
			
		||||
    }
 | 
			
		||||
    if ($name === null) {
 | 
			
		||||
      ## type dynamique anonyme
 | 
			
		||||
      foreach ($this->dynamicTypes as $dname => $dynamicType) {
 | 
			
		||||
        if ($dtype === $dynamicType) {
 | 
			
		||||
          $name = $dname;
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if ($name !== null) {
 | 
			
		||||
      $type = A::get($this->types, $name);
 | 
			
		||||
      if ($type !== null) return $type;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ## il faut créer le type
 | 
			
		||||
    if ($name === null) {
 | 
			
		||||
      # type dynamique
 | 
			
		||||
      $name = count($this->dynamicTypes) + 1;
 | 
			
		||||
      $this->dynamicTypes[$name] = $dtype;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($dtype !== null) {
 | 
			
		||||
      $class = $dtype[0];
 | 
			
		||||
      $class = A::get(ref_type::ALIASES, $class, $class);
 | 
			
		||||
      $class = A::get(self::CLASS_ALIASES, $class, $class);
 | 
			
		||||
      $args = array_slice($dtype, 1);
 | 
			
		||||
      if (!is_subclass_of($class, IType::class)) {
 | 
			
		||||
        # si la classe n'implémente pas IType, prendre le type générique
 | 
			
		||||
        $class = [GenericType::class, $class];
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      $class = A::get($this->typeClasses, $name);
 | 
			
		||||
      $args = [];
 | 
			
		||||
      if ($class !== null) func::fix_class_args($class, $args);
 | 
			
		||||
      if ($class === null) {
 | 
			
		||||
        # assumer que $name est une classe
 | 
			
		||||
        if (is_subclass_of($name, IType::class)) $class = $name;
 | 
			
		||||
        elseif ($required) $class = [GenericType::class, $name];
 | 
			
		||||
        else return null;
 | 
			
		||||
      } elseif (!is_subclass_of($class, IType::class)) {
 | 
			
		||||
        # si la classe n'implémente pas IType, prendre le type générique
 | 
			
		||||
        $class = [GenericType::class, $class];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    func::fix_class_args($class, $args);
 | 
			
		||||
    $type = func::cons($class, ...$args);
 | 
			
		||||
 | 
			
		||||
    return $this->types[$name] = $type;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function addType($typeOrClass, ?string $name=null): void {
 | 
			
		||||
    if ($typeOrClass instanceof IType) {
 | 
			
		||||
      if ($name === null) $name = get_class($typeOrClass);
 | 
			
		||||
      $this->types[$name] = $typeOrClass;
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (is_array($typeOrClass)) {
 | 
			
		||||
      # normaliser si nécessaire
 | 
			
		||||
      $key = array_key_first($typeOrClass);
 | 
			
		||||
      if ($key === 0) {
 | 
			
		||||
        if ($name === null) {
 | 
			
		||||
          foreach ($this->dynamicTypes as $dname => $dynamicType) {
 | 
			
		||||
            if ($typeOrClass === $dynamicType) {
 | 
			
		||||
              # type dynamique existant
 | 
			
		||||
              # c'est le seul cas où le type n'écrase pas celui existant
 | 
			
		||||
              return;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          # nouveau type dynamique
 | 
			
		||||
          $name = count($this->dynamicTypes) + 1;
 | 
			
		||||
          $this->dynamicTypes[$name] = $typeOrClass;
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        # type dynamique nommé
 | 
			
		||||
        if ($name === null) $name = $key;
 | 
			
		||||
        $class = $typeOrClass[$key];
 | 
			
		||||
        unset($typeOrClass[$key]);
 | 
			
		||||
        array_unshift($typeOrClass, $class);
 | 
			
		||||
      }
 | 
			
		||||
    } elseif ($name === null) {
 | 
			
		||||
      $name = $typeOrClass;
 | 
			
		||||
    }
 | 
			
		||||
    $this->typeClasses[$name] = $typeOrClass;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										142
									
								
								nur_src/data/types/RawStringType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								nur_src/data/types/RawStringType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,142 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
use nur\b\ValueException;
 | 
			
		||||
use nur\str;
 | 
			
		||||
 | 
			
		||||
class RawStringType extends AbstractSimpleType {
 | 
			
		||||
  use _Tparametrable;
 | 
			
		||||
 | 
			
		||||
  const TITLE = "chaine de caractères verbatim";
 | 
			
		||||
 | 
			
		||||
  const TRIM = false;
 | 
			
		||||
  const ALLOW_EMPTY = null;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @var bool si on doit trimmer la valeur, faut-il normaliser les caractères
 | 
			
		||||
   * de fin de ligne?
 | 
			
		||||
   */
 | 
			
		||||
  const NORM_LINES = true;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @var bool si on doit trimmer la valeur, faut-il trimmer chaque ligne de
 | 
			
		||||
   * façon indépendante?
 | 
			
		||||
   */
 | 
			
		||||
  const TRIM_LINES = false;
 | 
			
		||||
 | 
			
		||||
  /** @var string description de la valeur */
 | 
			
		||||
  const KIND = "value";
 | 
			
		||||
 | 
			
		||||
  const PARAMETRABLE_PARAMS_SCHEMA = [
 | 
			
		||||
    "allowed_values" => ["array", null, "valeurs autorisées"],
 | 
			
		||||
    "change_case" => ["?string", null, "faut-il transformer la chaine: upper, upper1, lower"],
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /** @var ?array */
 | 
			
		||||
  protected $ppAllowedValues;
 | 
			
		||||
 | 
			
		||||
  function pp_setAllowedValues(array $allowedValues): self {
 | 
			
		||||
    # classer les chaines par ordre inverse de taille
 | 
			
		||||
    $sizes = [];
 | 
			
		||||
    foreach ($allowedValues as $value) {
 | 
			
		||||
      $sizes[$value] = strlen($value);
 | 
			
		||||
    }
 | 
			
		||||
    arsort($sizes);
 | 
			
		||||
    $this->ppAllowedValues = array_keys($sizes);
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @var ?string */
 | 
			
		||||
  protected $ppChangeCase;
 | 
			
		||||
 | 
			
		||||
  function getClass(): string {
 | 
			
		||||
    return "string";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function beforeCheckInstance(&$value): bool {
 | 
			
		||||
    if (is_array($value)) $value = str::join3($value);
 | 
			
		||||
    if ($this->ppChangeCase !== null && is_string($value)) {
 | 
			
		||||
      switch ($this->ppChangeCase) {
 | 
			
		||||
      case "upper":
 | 
			
		||||
      case "uc":
 | 
			
		||||
        $value = str::upper($value);
 | 
			
		||||
        break;
 | 
			
		||||
      case "upper1":
 | 
			
		||||
      case "u1":
 | 
			
		||||
        $value = str::upper1($value);
 | 
			
		||||
        break;
 | 
			
		||||
      case "upperw":
 | 
			
		||||
      case "uw":
 | 
			
		||||
        $value = str::upperw($value);
 | 
			
		||||
        break;
 | 
			
		||||
      case "lower":
 | 
			
		||||
      case "lc":
 | 
			
		||||
        $value = str::lower($value);
 | 
			
		||||
        break;
 | 
			
		||||
      case "lower1":
 | 
			
		||||
      case "l1":
 | 
			
		||||
        $value = str::lower1($value);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function isInstance($value, bool $strict=false): bool {
 | 
			
		||||
    if ($value !== null && !is_string($value)) return false;
 | 
			
		||||
    if ($value !== null && $this->ppAllowedValues !== null) {
 | 
			
		||||
      if (!in_array($value, $this->ppAllowedValues)) return false;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static function verifix_trim(string $value, bool $norm_lines, bool $trim_lines): string {
 | 
			
		||||
    if ($trim_lines) {
 | 
			
		||||
      $lines = [];
 | 
			
		||||
      foreach (str::split_nl($value) as $line) {
 | 
			
		||||
        $lines[] = trim($line);
 | 
			
		||||
      }
 | 
			
		||||
      return implode("\n", $lines);
 | 
			
		||||
    } elseif ($norm_lines) {
 | 
			
		||||
      return str::norm_nl(trim($value));
 | 
			
		||||
    } else {
 | 
			
		||||
      return trim($value);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** trim normalise aussi les caractères de fin de ligne */
 | 
			
		||||
  function verifixTrim(string $value): string {
 | 
			
		||||
    return self::verifix_trim($value, static::NORM_LINES, static::TRIM_LINES);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function parse(string &$input) {
 | 
			
		||||
    $allowedValues = $this->ppAllowedValues;
 | 
			
		||||
    if ($allowedValues) {
 | 
			
		||||
      $found = false;
 | 
			
		||||
      foreach ($allowedValues as $allowedValue) {
 | 
			
		||||
        if ($input === $allowedValue) {
 | 
			
		||||
          $found = true;
 | 
			
		||||
          $value = $allowedValue;
 | 
			
		||||
          $input = "";
 | 
			
		||||
          break;
 | 
			
		||||
        } elseif (str::_starts_with($allowedValue, $input)) {
 | 
			
		||||
          $found = true;
 | 
			
		||||
          $value = $allowedValue;
 | 
			
		||||
          $input = substr($input, strlen($value) + 1);
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (!$found) {
 | 
			
		||||
        throw ValueException::unexpected_value($input, $allowedValues, static::KIND);
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      $value = $input;
 | 
			
		||||
      $input = "";
 | 
			
		||||
    }
 | 
			
		||||
    return $value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function verifixReplaceEmpty(&$value): void {
 | 
			
		||||
    if (!$this->ppAllowEmpty && $this->ppAllowNull) $value = null;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										94
									
								
								nur_src/data/types/RegexpType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								nur_src/data/types/RegexpType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,94 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
use nur\A;
 | 
			
		||||
use nur\base;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class PatternType: une chaine qui correspond à une regexp
 | 
			
		||||
 */
 | 
			
		||||
class RegexpType extends AbstractSimpleType {
 | 
			
		||||
  use _Tparametrable;
 | 
			
		||||
 | 
			
		||||
  const ALLOW_NULL = true;
 | 
			
		||||
  const CHECK_CLASS = false;
 | 
			
		||||
 | 
			
		||||
  /** @var array|string */
 | 
			
		||||
  const PATTERN = ".*";
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @var string un pattern qui matche *entièrement* une chaine normalisée de ce
 | 
			
		||||
   * type. de plus, ce pattern peut définir des groupes pour isoler certaines
 | 
			
		||||
   * parties de la valeur.
 | 
			
		||||
   * par défaut, utiliser {@link PATTERN} si cette constante est nulle.
 | 
			
		||||
   */
 | 
			
		||||
  const PATTERN_NORMALIZED = null;
 | 
			
		||||
 | 
			
		||||
  const PARAMETRABLE_PARAMS_SCHEMA = [
 | 
			
		||||
    "pattern" => ["array", null, "patterns valides"]
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /** @var array */
 | 
			
		||||
  protected $patterns;
 | 
			
		||||
 | 
			
		||||
  function pp_setPattern(array $patterns): self {
 | 
			
		||||
    $this->patterns = $patterns;
 | 
			
		||||
    return $this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function __construct(?array $params=null) {
 | 
			
		||||
    parent::__construct($params);
 | 
			
		||||
    base::update_n($this->patterns, A::with(static::PATTERN));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function getClass(): string {
 | 
			
		||||
    return "string";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function isInstance($value, bool $strict=true): bool {
 | 
			
		||||
    if ($value === null) return true;
 | 
			
		||||
    if (!is_string($value)) return false;
 | 
			
		||||
    $patterns = null;
 | 
			
		||||
    if ($strict) {
 | 
			
		||||
      $patterns = static::PATTERN_NORMALIZED;
 | 
			
		||||
      if ($patterns !== null) $patterns = [$patterns];
 | 
			
		||||
    }
 | 
			
		||||
    if ($patterns === null) $patterns = $this->patterns;
 | 
			
		||||
    foreach ($patterns as $pattern) {
 | 
			
		||||
      if (preg_match($pattern, $value)) {
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function extractParsedValue(array $ms) {
 | 
			
		||||
    return $ms[0];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function parse(string &$input) {
 | 
			
		||||
    foreach ($this->patterns as $pattern) {
 | 
			
		||||
      if (preg_match($pattern, $input, $ms)) {
 | 
			
		||||
        $value = $this->extractParsedValue($ms);
 | 
			
		||||
        $input = substr($input, strlen($ms[0]));
 | 
			
		||||
        return $value;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return $this->ppAllowParseEmpty? null: false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * normaliser la valeur qui a été analysée par {@link parse()}
 | 
			
		||||
   *
 | 
			
		||||
   * NB: si $this->allowParseEmpty==true, alors $value peut être null. il faut
 | 
			
		||||
   * donc toujours tester ce cas, parce que la classe peut-être instanciée avec
 | 
			
		||||
   * ce paramètre
 | 
			
		||||
   *
 | 
			
		||||
   * En fonction de l'implémentation, il est possible aussi que {@link parse()}
 | 
			
		||||
   * aie analysé une chaine syntaxiquement correcte mais invalide. dans ce cas,
 | 
			
		||||
   * cette fonction doit retourner false
 | 
			
		||||
   */
 | 
			
		||||
  function verifixConvert(&$value): bool {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										43
									
								
								nur_src/data/types/SDateType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								nur_src/data/types/SDateType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,43 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
use nur\b\date\Date;
 | 
			
		||||
 | 
			
		||||
class SDateType extends RegexpType {
 | 
			
		||||
  const TITLE = "date";
 | 
			
		||||
 | 
			
		||||
  private static $instance;
 | 
			
		||||
  static final function to_date($value) {
 | 
			
		||||
    $type = self::$instance;
 | 
			
		||||
    if ($type === null) self::$instance = $type = new self();
 | 
			
		||||
    return $type->with($value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const PATTERN = [
 | 
			
		||||
    '/^(?<d>\d{1,2})[-\/.]+(?<m>\d{1,2})(?:[-\/.]+(?<y>\d{2,4}))?(?:\s+0{1,2}[hH:.,]0{1,2}(?:[:.,]0{1,2})?)?/',
 | 
			
		||||
    '/^(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})(?:\s*00:00:00)?/',
 | 
			
		||||
    '/^(?<d>\d{2})(?<m>\d{2})(?<y>\d{2,4})?/',
 | 
			
		||||
  ];
 | 
			
		||||
  const PATTERN_NORMALIZED = Date::PATTERN;
 | 
			
		||||
 | 
			
		||||
  /** @return array un tableau de la forme [$y, $m, $d] */
 | 
			
		||||
  protected function extractParsedValue(array $ms) {
 | 
			
		||||
    $d = intval($ms["d"]);
 | 
			
		||||
    $m = intval($ms["m"]);
 | 
			
		||||
    $y = isset($ms["y"])? intval($ms["y"]): null;
 | 
			
		||||
    if ($y === null) $y = intval(date("Y"));
 | 
			
		||||
    else $y = Date::fix_any_year($y);
 | 
			
		||||
    return [$y, $m, $d];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function verifixConvert(&$value): bool {
 | 
			
		||||
    if ($value === null) return true;
 | 
			
		||||
    $value = (new Date($value))->format();
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function formatDate(?string $date, string $format="Y-m-d"): ?string {
 | 
			
		||||
    if ($date === null) return null;
 | 
			
		||||
    return date($format, Date::parse_date($date));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										50
									
								
								nur_src/data/types/SDatetimeType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								nur_src/data/types/SDatetimeType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
use nur\b\date\Date;
 | 
			
		||||
use nur\b\date\Datetime;
 | 
			
		||||
 | 
			
		||||
class SDatetimeType extends RegexpType {
 | 
			
		||||
  const TITLE = "date et heure";
 | 
			
		||||
 | 
			
		||||
  private static $instance;
 | 
			
		||||
  static final function to_datetime($value) {
 | 
			
		||||
    $type = self::$instance;
 | 
			
		||||
    if ($type === null) self::$instance = $type = new self();
 | 
			
		||||
    return $type->with($value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const PATTERN = [
 | 
			
		||||
    '/^(?<d>\d{1,2})[-\/]+(?<m>\d{1,2})(?:[-\/]+(?<y>\d{2,4}))?\s+(?<H>[0-9]{1,2})[hH:.,](?<M>[0-9]{1,2})(?:[:.,](?<S>[0-9]{1,2}))?/',
 | 
			
		||||
    '/^(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2}) (?<H>\d{2}):(?<M>\d{2}):(?<S>\d{2})/',
 | 
			
		||||
    '/^(?<d>\d{2})(?<m>\d{2})(?<y>\d{2,4})?\s+(?<H>[0-9]{1,2})[hH:.,](?<M>[0-9]{1,2})(?:[:.,](?<S>[0-9]{1,2}))?/',
 | 
			
		||||
    '/^(?<d>\d{1,2})[-\/]+(?<m>\d{1,2})(?:[-\/]+(?<y>\d{2,4}))?/',
 | 
			
		||||
    '/^(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/',
 | 
			
		||||
    '/^(?<d>\d{2})(?<m>\d{2})(?<y>\d{2,4})?/',
 | 
			
		||||
  ];
 | 
			
		||||
  const PATTERN_NORMALIZED = Datetime::PATTERN;
 | 
			
		||||
 | 
			
		||||
  /** @return array un tableau de la forme [$y, $m, $d, $H, $M, $S] */
 | 
			
		||||
  protected function extractParsedValue(array $ms) {
 | 
			
		||||
    $d = intval($ms["d"]);
 | 
			
		||||
    $m = intval($ms["m"]);
 | 
			
		||||
    $y = isset($ms["y"]) && $ms["y"]? intval($ms["y"]): null;
 | 
			
		||||
    if ($y === null) $y = intval(date("Y"));
 | 
			
		||||
    else $y = Date::fix_any_year($y);
 | 
			
		||||
    $H = isset($ms["H"])? intval($ms["H"]): 0;
 | 
			
		||||
    $M = isset($ms["M"])? intval($ms["M"]): 0;
 | 
			
		||||
    $S = isset($ms["S"])? intval($ms["S"]): 0;
 | 
			
		||||
    return [$y, $m, $d, $H, $M, $S];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function verifixConvert(&$value): bool {
 | 
			
		||||
    if ($value === null) return true;
 | 
			
		||||
    $value = (new Datetime($value))->format();
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function formatDatetime(?string $datetime, string $format="Y-m-d H:i:s"): ?string {
 | 
			
		||||
    if ($datetime === null) return null;
 | 
			
		||||
    return date($format, Datetime::parse_datetime($datetime));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								nur_src/data/types/SHourType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								nur_src/data/types/SHourType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
use nur\b\date\Hour;
 | 
			
		||||
 | 
			
		||||
class SHourType extends STimeType {
 | 
			
		||||
  const TITLE = "heure du jour";
 | 
			
		||||
 | 
			
		||||
  private static $instance;
 | 
			
		||||
  static final function to_hour($value) {
 | 
			
		||||
    $type = self::$instance;
 | 
			
		||||
    if ($type === null) self::$instance = $type = new self();
 | 
			
		||||
    return $type->with($value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function verifixConvert(&$value): bool {
 | 
			
		||||
    if ($value === null) return true;
 | 
			
		||||
    $value = (new Hour($value))->format();
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								nur_src/data/types/STimeType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								nur_src/data/types/STimeType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
use nur\b\date\Time;
 | 
			
		||||
 | 
			
		||||
class STimeType extends RegexpType {
 | 
			
		||||
  const TITLE = "heure du jour";
 | 
			
		||||
 | 
			
		||||
  private static $instance;
 | 
			
		||||
  static final function to_time($value) {
 | 
			
		||||
    $type = self::$instance;
 | 
			
		||||
    if ($type === null) self::$instance = $type = new self();
 | 
			
		||||
    return $type->with($value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const PATTERN = '/^(\d+)\s*(?:[hH:.,]\s*(?:(\d+)\s*(?:[:.,]\s*(\d+)\s*)?)?)?/';
 | 
			
		||||
  const PATTERN_NORMALIZED = Time::PATTERN;
 | 
			
		||||
 | 
			
		||||
  /** @return array un tableau de la forme [$H, $M, $S] */
 | 
			
		||||
  protected function extractParsedValue(array $ms) {
 | 
			
		||||
    $h = intval($ms[1]);
 | 
			
		||||
    $m = isset($ms[2])? intval($ms[2]): 0;
 | 
			
		||||
    $s = isset($ms[3])? intval($ms[3]): 0;
 | 
			
		||||
    return [$h, $m, $s];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function verifixConvert(&$value): bool {
 | 
			
		||||
    if ($value === null) return true;
 | 
			
		||||
    $value = (new Time($value))->format();
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								nur_src/data/types/STimeslotType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								nur_src/data/types/STimeslotType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
class STimeslotType extends AbstractComplexType {
 | 
			
		||||
  const TITLE = "plage horaire";
 | 
			
		||||
 | 
			
		||||
  const COMPONENTS = [
 | 
			
		||||
    "start" => [
 | 
			
		||||
      "type" => [SHourType::class],
 | 
			
		||||
      "key" => "ts_start",
 | 
			
		||||
      "title" => "heure de début (inclue)",
 | 
			
		||||
      "allow_parse_empty" => true,
 | 
			
		||||
    ],
 | 
			
		||||
    "end" => [
 | 
			
		||||
      "type" => [SHourType::class],
 | 
			
		||||
      "key" => "ts_end",
 | 
			
		||||
      "title" => "heure de fin (non inclue)",
 | 
			
		||||
      "allow_parse_empty" => true,
 | 
			
		||||
    ],
 | 
			
		||||
  ];
 | 
			
		||||
  const SEPARATOR = "-";
 | 
			
		||||
  const SEPARATOR_PATTERN = '/^\s*(?:-\s*)?/';
 | 
			
		||||
 | 
			
		||||
  function getClass(): string {
 | 
			
		||||
    return "array";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								nur_src/data/types/StringType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								nur_src/data/types/StringType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
class StringType extends RawStringType {
 | 
			
		||||
  const TITLE = "chaine de caractères trimmée et normalisée";
 | 
			
		||||
 | 
			
		||||
  const TRIM = true;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										354
									
								
								nur_src/data/types/TelephoneType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										354
									
								
								nur_src/data/types/TelephoneType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,354 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
class TelephoneType extends RegexpType {
 | 
			
		||||
  const PATTERN = '/^[0-9+, .()-]+/';
 | 
			
		||||
  const PATTERN_NORMALIZED = '/^(\+?[0-9 ,]+)$/';
 | 
			
		||||
 | 
			
		||||
  const COD_PAYS = [
 | 
			
		||||
    '1242' => ['1242', null, 'Bahamas'],
 | 
			
		||||
    '1246' => ['1246', null, 'Barbade'],
 | 
			
		||||
    '1264' => ['1264', null, 'Anguilla'],
 | 
			
		||||
    '1268' => ['1268', null, 'Antigua-et-Barbuda'],
 | 
			
		||||
    '1284' => ['1284', null, 'Îles Vierges britanniques'],
 | 
			
		||||
    '1340' => ['1340', null, 'Îles Vierges des États-Unis'],
 | 
			
		||||
    '1345' => ['1345', null, 'îles Caïmans'],
 | 
			
		||||
    '1441' => ['1441', null, 'Bermudes'],
 | 
			
		||||
    '1473' => ['1473', null, 'Grenade'],
 | 
			
		||||
    '1649' => ['1649', null, 'Îles Turques-et-Caïques'],
 | 
			
		||||
    '1664' => ['1664', null, 'Montserrat'],
 | 
			
		||||
    '1670' => ['1670', null, 'Îles Mariannes du Nord'],
 | 
			
		||||
    '1671' => ['1671', null, 'Guam'],
 | 
			
		||||
    '1684' => ['1684', null, 'Samoa américaines'],
 | 
			
		||||
    '1758' => ['1758', null, 'Sainte-Lucie'],
 | 
			
		||||
    '1767' => ['1767', null, 'Dominique'],
 | 
			
		||||
    '1784' => ['1784', null, 'Saint-Vincent-et-les-Grenadines'],
 | 
			
		||||
    '1868' => ['1868', null, 'Trinité-et-Tobago'],
 | 
			
		||||
    '1869' => ['1869', null, 'Saint-Christophe-et-Niévès'],
 | 
			
		||||
    '1876' => ['1876', null, 'Jamaïque'],
 | 
			
		||||
    '1' => ['1', null, 'États-Unis'],
 | 
			
		||||
    ['1', null, 'Canada'],
 | 
			
		||||
    ['1', null, 'Porto Rico'],
 | 
			
		||||
    ['1', null, 'République dominicaine'],
 | 
			
		||||
    '20' => ['20', ['1'], 'Égypte'],
 | 
			
		||||
    '211' => ['211', null, 'Soudan du Sud'],
 | 
			
		||||
    '212' => ['212', '6' => ['6', '7'], 'Maroc'],
 | 
			
		||||
    '213' => ['213', '5' => ['5', '6', '7', '9'], 'Algérie'],
 | 
			
		||||
    '216' => ['216', '9' => ['9', '2', '5', '3', '4'], 'Tunisie'],
 | 
			
		||||
    '218' => ['218', null, 'Libye'],
 | 
			
		||||
    '220' => ['220', null, 'Gambie'],
 | 
			
		||||
    '221' => ['221', null, 'Sénégal'],
 | 
			
		||||
    '222' => ['222', null, 'Mauritanie'],
 | 
			
		||||
    '223' => ['223', null, 'Mali'],
 | 
			
		||||
    '224' => ['224', null, 'Guinée'],
 | 
			
		||||
    '225' => ['225', null, 'Côte d\'Ivoire'],
 | 
			
		||||
    '226' => ['226', null, 'Burkina Faso'],
 | 
			
		||||
    '227' => ['227', null, 'Niger'],
 | 
			
		||||
    '228' => ['228', null, 'Togo'],
 | 
			
		||||
    '229' => ['229', null, 'Bénin'],
 | 
			
		||||
    '230' => ['230', ['5'], 'Maurice'],
 | 
			
		||||
    '231' => ['231', null, 'Libéria Liberia'],
 | 
			
		||||
    '232' => ['232', null, 'Sierra Leone'],
 | 
			
		||||
    '233' => ['233', null, 'Ghana'],
 | 
			
		||||
    '234' => ['234', null, 'Nigeria'],
 | 
			
		||||
    '235' => ['235', null, 'Tchad'],
 | 
			
		||||
    '236' => ['236', null, 'République centrafricaine'],
 | 
			
		||||
    '237' => ['237', ['2 ou 6'], 'Cameroun'],
 | 
			
		||||
    '238' => ['238', null, 'Cap-Vert'],
 | 
			
		||||
    '239' => ['239', null, 'Sao Tomé-et-Principe'],
 | 
			
		||||
    '240' => ['240', null, 'Guinée équatoriale'],
 | 
			
		||||
    '241' => ['241', null, 'Gabon'],
 | 
			
		||||
    '242' => ['242', null, 'république du Congo'],
 | 
			
		||||
    '243' => ['243', null, 'république démocratique du Congo'],
 | 
			
		||||
    '244' => ['244', ['9'], 'Angola'],
 | 
			
		||||
    '245' => ['245', null, 'Guinée-Bissau'],
 | 
			
		||||
    '246' => ['246', null, 'Diego Garcia'],
 | 
			
		||||
    '247' => ['247', null, 'Ascension'],
 | 
			
		||||
    '248' => ['248', null, 'Seychelles'],
 | 
			
		||||
    '249' => ['249', null, 'Soudan'],
 | 
			
		||||
    '250' => ['250', null, 'Rwanda'],
 | 
			
		||||
    '251' => ['251', null, 'Éthiopie'],
 | 
			
		||||
    '252' => ['252', null, 'Somalie'],
 | 
			
		||||
    '253' => ['253', null, 'Djibouti'],
 | 
			
		||||
    '254' => ['254', null, 'Kenya'],
 | 
			
		||||
    '255' => ['255', null, 'Tanzanie'],
 | 
			
		||||
    '256' => ['256', null, 'Ouganda'],
 | 
			
		||||
    '257' => ['257', null, 'Burundi'],
 | 
			
		||||
    '258' => ['258', null, 'Mozambique'],
 | 
			
		||||
    '260' => ['260', null, 'Zambie'],
 | 
			
		||||
    '261' => ['261', '32' => ['32', '33', '34', '39'], 'Madagascar'],
 | 
			
		||||
    '262' => ['262', '692' => ['692', '693'], 'La Réunion'],
 | 
			
		||||
    ['262', ['639'], 'Mayotte'],
 | 
			
		||||
    '263' => ['263', null, 'Zimbabwe'],
 | 
			
		||||
    '264' => ['264', null, 'Namibie'],
 | 
			
		||||
    '265' => ['265', null, 'Malawi'],
 | 
			
		||||
    '266' => ['266', null, 'Lesotho'],
 | 
			
		||||
    '267' => ['267', null, 'Botswana'],
 | 
			
		||||
    '268' => ['268', null, 'Eswatini'],
 | 
			
		||||
    '269' => ['269', null, 'Comores'],
 | 
			
		||||
    '27' => ['27', null, 'Afrique du Sud'],
 | 
			
		||||
    '290' => ['290', null, 'Sainte-Hélène, Ascension et Tristan da Cunha, île'],
 | 
			
		||||
    '291' => ['291', null, 'Érythrée'],
 | 
			
		||||
    '297' => ['297', null, 'Aruba'],
 | 
			
		||||
    '298' => ['298', null, 'Îles Féroé'],
 | 
			
		||||
    '299' => ['299', null, 'Groenland'],
 | 
			
		||||
    '30' => ['30', ['6'], 'Grèce'],
 | 
			
		||||
    '31' => ['31', ['6'], 'Pays-Bas'],
 | 
			
		||||
    '32' => ['32', '46' => ['46', '47', '48', '49'], 'Belgique'],
 | 
			
		||||
    '33' => ['33', '6' => ['6', '7'], 'France'],
 | 
			
		||||
    '34' => ['34', '6' => ['6', '7'], 'Espagne'],
 | 
			
		||||
    '350' => ['350', null, 'Gibraltar'],
 | 
			
		||||
    '351' => ['351', ['9'], 'Portugal'],
 | 
			
		||||
    '352' => ['352', '621' => ['621', '661', '671', '691'], 'Luxembourg'],
 | 
			
		||||
    '353' => ['353', '82' => ['82', '83', '84', '85', '86', '87', '88', '89'], 'Irlande'],
 | 
			
		||||
    '354' => ['354', '6' => ['6', '7', '8'], 'Islande'],
 | 
			
		||||
    '355' => ['355', ['6'], 'Albanie'],
 | 
			
		||||
    '356' => ['356', null, 'Malte'],
 | 
			
		||||
    '357' => ['357', ['9'], 'Chypre'],
 | 
			
		||||
    '358' => ['358', '4' => ['4', '50'], 'Finlande'],
 | 
			
		||||
    '359' => ['359', '87' => ['87', '88', '89'], 'Bulgarie'],
 | 
			
		||||
    '36' => ['36', '20' => ['20', '30', '31', '70'], 'Hongrie'],
 | 
			
		||||
    '370' => ['370', ['6'], 'Lituanie'],
 | 
			
		||||
    '371' => ['371', ['2'], 'Lettonie'],
 | 
			
		||||
    '372' => ['372', '5' => ['5', '81', '82'], 'Estonie'],
 | 
			
		||||
    '373' => ['373', null, 'Moldavie'],
 | 
			
		||||
    '374' => ['374', null, 'Arménie'],
 | 
			
		||||
    '375' => ['375', null, 'Biélorussie'],
 | 
			
		||||
    '376' => ['376', '3' => ['3', '4', '6'], 'Andorre'],
 | 
			
		||||
    '377' => ['377', ['4'], 'Monaco'],
 | 
			
		||||
    '378' => ['378', null, 'Saint-Marin'],
 | 
			
		||||
    '380' => ['380', null, 'Ukraine'],
 | 
			
		||||
    '381' => ['381', '6' => ['6', '44', '45', '43', '49'], 'Serbie'],
 | 
			
		||||
    '382' => ['382', null, 'Monténégro'],
 | 
			
		||||
    '383' => ['383', null, 'Kosovo'],
 | 
			
		||||
    '385' => ['385', ['9'], 'Croatie'],
 | 
			
		||||
    '386' => ['386', '30' => ['30', '31', '40', '41', '43', '49', '51', '64', '68', '70', '71'], 'Slovénie'],
 | 
			
		||||
    '387' => ['387', ['6'], 'Bosnie-Herzégovine'],
 | 
			
		||||
    '389' => ['389', ['7'], 'Macédoine du Nord'],
 | 
			
		||||
    '39' => ['39', ['3'], 'Italie'],
 | 
			
		||||
    ['39', ['379'], 'Vatican'],
 | 
			
		||||
    '40' => ['40', ['7'], 'Roumanie'],
 | 
			
		||||
    '41' => ['41', '74' => ['74', '75', '76', '77', '78', '79'], 'Suisse'],
 | 
			
		||||
    '420' => ['420', null, 'Tchéquie République tchèque'],
 | 
			
		||||
    '421' => ['421', ['9'], 'Slovaquie'],
 | 
			
		||||
    '423' => ['423', null, 'Liechtenstein'],
 | 
			
		||||
    '43' => ['43', ['6'], 'Autriche'],
 | 
			
		||||
    '44' => ['44', '74' => ['74', '75', '7624', '77', '78', '79'], 'Royaume-Uni'],
 | 
			
		||||
    '45' => ['45', '2' => ['2', '30', '31', '40', '41', '42', '50', '51', '52', '53', '60', '61', '71', '81', '9'], 'Danemark'],
 | 
			
		||||
    '46' => ['46', '70' => ['70', '71', '72', '73', '76'], 'Suède'],
 | 
			
		||||
    '47' => ['47', '4' => ['4', '9'], 'Norvège'],
 | 
			
		||||
    '48' => ['48', '50' => ['50', '51', '53', '60', '66', '69', '72', '73', '78', '79', '88'], 'Pologne'],
 | 
			
		||||
    '49' => ['49', '15' => ['15', '16', '17'], 'Allemagne'],
 | 
			
		||||
    '500' => ['500', null, 'Îles Malouines'],
 | 
			
		||||
    '501' => ['501', null, 'Belize'],
 | 
			
		||||
    '502' => ['502', null, 'Guatemala'],
 | 
			
		||||
    '503' => ['503', null, 'Salvador'],
 | 
			
		||||
    '504' => ['504', null, 'Honduras'],
 | 
			
		||||
    '505' => ['505', null, 'Nicaragua'],
 | 
			
		||||
    '506' => ['506', '7' => ['7', '8'], 'Costa Rica'],
 | 
			
		||||
    '507' => ['507', null, 'Panama'],
 | 
			
		||||
    '508' => ['508', null, 'Saint-Pierre-et-Miquelon'],
 | 
			
		||||
    '509' => ['509', null, 'Haïti'],
 | 
			
		||||
    '51' => ['51', ['9'], 'Pérou'],
 | 
			
		||||
    '52' => ['52', ['1'], 'Mexique'],
 | 
			
		||||
    '53' => ['53', null, 'Cuba'],
 | 
			
		||||
    '54' => ['54', ['9'], 'Argentine'],
 | 
			
		||||
    '55' => ['55', ['xx6', 'xx7', 'xx8', 'xx9'], 'Brésil'],
 | 
			
		||||
    '56' => ['56', ['9'], 'Chili'],
 | 
			
		||||
    '57' => ['57', ['3'], 'Colombie'],
 | 
			
		||||
    '58' => ['58', ['4'], 'Venezuela'],
 | 
			
		||||
    '590' => ['590', ['690'], 'Guadeloupe'],
 | 
			
		||||
    '591' => ['591', '6' => ['6', '7'], 'Bolivie'],
 | 
			
		||||
    '592' => ['592', null, 'Guyana'],
 | 
			
		||||
    '593' => ['593', ['9'], 'Équateur'],
 | 
			
		||||
    '594' => ['594', ['694'], 'Guyane'],
 | 
			
		||||
    '595' => ['595', null, 'Paraguay'],
 | 
			
		||||
    '596' => ['596', ['696'], 'Martinique'],
 | 
			
		||||
    '597' => ['597', ['8'], 'Suriname'],
 | 
			
		||||
    '598' => ['598', null, 'Uruguay'],
 | 
			
		||||
    '599' => ['599', null, 'Curaçao et Pays-Bas caribéens'],
 | 
			
		||||
    '60' => ['60', ['1'], 'Malaisie'],
 | 
			
		||||
    '61' => ['61', '1' => ['1', '4'], 'Australie'],
 | 
			
		||||
    '62' => ['62', null, 'Indonésie'],
 | 
			
		||||
    '63' => ['63', null, 'Philippines'],
 | 
			
		||||
    '64' => ['64', ['2'], 'Nouvelle-Zélande'],
 | 
			
		||||
    '65' => ['65', '8' => ['8', '9'], 'Singapour'],
 | 
			
		||||
    '66' => ['66', ['8'], 'Thaïlande'],
 | 
			
		||||
    '670' => ['670', null, 'Timor oriental'],
 | 
			
		||||
    '672' => ['672', null, 'Christmas, Cocos, Heard-et-MacDonald, Îles'],
 | 
			
		||||
    '673' => ['673', null, 'Brunei'],
 | 
			
		||||
    '674' => ['674', null, 'Nauru'],
 | 
			
		||||
    '675' => ['675', null, 'Papouasie-Nouvelle-Guinée'],
 | 
			
		||||
    '676' => ['676', null, 'Tonga'],
 | 
			
		||||
    '677' => ['677', null, 'Îles Salomon'],
 | 
			
		||||
    '678' => ['678', null, 'Vanuatu'],
 | 
			
		||||
    '679' => ['679', null, 'Fidji'],
 | 
			
		||||
    '680' => ['680', null, 'Palaos'],
 | 
			
		||||
    '681' => ['681', null, 'Wallis-et-Futuna'],
 | 
			
		||||
    '682' => ['682', null, 'Îles Cook'],
 | 
			
		||||
    '683' => ['683', null, 'Niue'],
 | 
			
		||||
    '685' => ['685', null, 'Samoa'],
 | 
			
		||||
    '686' => ['686', null, 'Kiribati'],
 | 
			
		||||
    '687' => ['687', null, 'Nouvelle-Calédonie'],
 | 
			
		||||
    '688' => ['688', null, 'Tuvalu'],
 | 
			
		||||
    '689' => ['689', null, 'Polynésie française'],
 | 
			
		||||
    '690' => ['690', null, 'Tokelau'],
 | 
			
		||||
    '691' => ['691', null, 'États fédérés de Micronésie'],
 | 
			
		||||
    '692' => ['692', null, 'Îles Marshall'],
 | 
			
		||||
    '7' => ['7', ['9'], 'Russie'],
 | 
			
		||||
    ['7', '70' => ['70', '77'], 'Kazakhstan'],
 | 
			
		||||
    '81' => ['81', '070' => ['070', '080', '090'], 'Japon'],
 | 
			
		||||
    '82' => ['82', ['1'], 'Corée du Sud'],
 | 
			
		||||
    '84' => ['84', null, 'République socialiste du Viêt Nam'],
 | 
			
		||||
    '850' => ['850', null, 'Corée du Nord'],
 | 
			
		||||
    '852' => ['852', '5' => ['5', '6', '9'], 'Hong Kong'],
 | 
			
		||||
    '853' => ['853', null, 'Macao'],
 | 
			
		||||
    '855' => ['855', null, 'Cambodge'],
 | 
			
		||||
    '856' => ['856', null, 'Laos'],
 | 
			
		||||
    '86' => ['86', ['1'], 'République populaire de Chine'],
 | 
			
		||||
    '880' => ['880', null, 'Bangladesh'],
 | 
			
		||||
    '881' => ['881', null, 'Système mobile mondial par satellite GMSS'],
 | 
			
		||||
    '886' => ['886', ['9'], 'Taïwan'],
 | 
			
		||||
    '90' => ['90', ['5'], 'Turquie'],
 | 
			
		||||
    '91' => ['91', '7' => ['7', '8', '9'], 'Inde'],
 | 
			
		||||
    '92' => ['92', null, 'Pakistan'],
 | 
			
		||||
    '93' => ['93', ['7'], 'Afghanistan'],
 | 
			
		||||
    '94' => ['94', null, 'Sri Lanka'],
 | 
			
		||||
    '95' => ['95', null, 'Birmanie'],
 | 
			
		||||
    '960' => ['960', null, 'Maldives'],
 | 
			
		||||
    '961' => ['961', '3' => ['3', '70', '71'], 'Liban'],
 | 
			
		||||
    '962' => ['962', null, 'Jordanie'],
 | 
			
		||||
    '963' => ['963', null, 'Syrie'],
 | 
			
		||||
    '964' => ['964', null, 'Irak'],
 | 
			
		||||
    '965' => ['965', null, 'Koweït'],
 | 
			
		||||
    '966' => ['966', ['5'], 'Arabie saoudite'],
 | 
			
		||||
    '967' => ['967', null, 'Yémen'],
 | 
			
		||||
    '968' => ['968', null, 'Oman'],
 | 
			
		||||
    '970' => ['970', null, 'Palestine'],
 | 
			
		||||
    '971' => ['971', ['5'], 'Émirats arabes unis'],
 | 
			
		||||
    '972' => ['972', ['5'], 'Israël'],
 | 
			
		||||
    '973' => ['973', null, 'Bahreïn'],
 | 
			
		||||
    '974' => ['974', null, 'Qatar'],
 | 
			
		||||
    '975' => ['975', null, 'Bhoutan'],
 | 
			
		||||
    '976' => ['976', null, 'Mongolie'],
 | 
			
		||||
    '977' => ['977', null, 'Népal'],
 | 
			
		||||
    '98' => ['98', null, 'Iran'],
 | 
			
		||||
    '992' => ['992', null, 'Tadjikistan'],
 | 
			
		||||
    '993' => ['993', null, 'Turkménistan'],
 | 
			
		||||
    '994' => ['994', null, 'Azerbaïdjan'],
 | 
			
		||||
    '995' => ['995', null, 'Géorgie'],
 | 
			
		||||
    '996' => ['996', null, 'Kirghizistan'],
 | 
			
		||||
    '998' => ['998', null, 'Ouzbékistan'],
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  private static function format_local(string $tel): string {
 | 
			
		||||
    return substr($tel, 0, 4)
 | 
			
		||||
      ." ".substr($tel, 4, 2)
 | 
			
		||||
      ." ".substr($tel, 6, 2)
 | 
			
		||||
      ." ".substr($tel, 8, 2);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static function format_foreign(string $tel): string {
 | 
			
		||||
    $parts = [];
 | 
			
		||||
    while(strlen($tel) > 4) {
 | 
			
		||||
      $parts[] = substr($tel, -3);
 | 
			
		||||
      $tel = substr($tel, 0, -3);
 | 
			
		||||
    }
 | 
			
		||||
    $parts[] = $tel;
 | 
			
		||||
    $parts = array_reverse($parts);
 | 
			
		||||
    return implode(" ", $parts);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected function extractParsedValue(array $ms) {
 | 
			
		||||
    $tel = $ms[0];
 | 
			
		||||
    $tel = preg_replace('/[ .()-]/', "", $tel);
 | 
			
		||||
    $tel = preg_replace('/\+/', "00", $tel);
 | 
			
		||||
    $ntel = preg_replace('/[^0-9]/', "", $tel);
 | 
			
		||||
    # tel est le numéro avec chiffres et virgule
 | 
			
		||||
    # ntel est le numéro avec uniquement des chiffres
 | 
			
		||||
    $size = strlen($ntel);
 | 
			
		||||
    if ($size == 4) {
 | 
			
		||||
      # numéro de poste, le retourner tel quel
 | 
			
		||||
      return $tel;
 | 
			
		||||
    } elseif ($size == 6) {
 | 
			
		||||
      # numéro local sans préfixe
 | 
			
		||||
      $tel = "00262262$tel";
 | 
			
		||||
    } elseif ($size == 10 && preg_match('/^0[1-9]/', $ntel)) {
 | 
			
		||||
      # numéro local avec préfixe
 | 
			
		||||
      $tel = "00262".substr($tel, 1);
 | 
			
		||||
    }
 | 
			
		||||
    # erreurs courantes
 | 
			
		||||
    if ($size == 9 && substr($ntel, 0, 1) != "0") {
 | 
			
		||||
      $tel = "00262$tel";
 | 
			
		||||
    } elseif ($size == 12 && strpos($ntel, "262262") === 0) {
 | 
			
		||||
      $tel = "00$tel";
 | 
			
		||||
    } elseif ($size == 12 && strpos($ntel, "262692") === 0) {
 | 
			
		||||
      $tel = "00$tel";
 | 
			
		||||
    } elseif ($size == 12 && strpos($ntel, "262693") === 0) {
 | 
			
		||||
      $tel = "00$tel";
 | 
			
		||||
    } elseif ($size == 12 && strpos($ntel, "000262") === 0) {
 | 
			
		||||
      $tel = "00262".substr($tel, 3);
 | 
			
		||||
    } elseif ($size == 12 && strpos($ntel, "000692") === 0) {
 | 
			
		||||
      $tel = "00262".substr($tel, 3);
 | 
			
		||||
    } elseif ($size == 12 && strpos($ntel, "000693") === 0) {
 | 
			
		||||
      $tel = "00262".substr($tel, 3);
 | 
			
		||||
    } elseif ($size == 11 && strpos($ntel, "00262") === 0) {
 | 
			
		||||
      $tel = "00262".substr($tel, 2);
 | 
			
		||||
    } elseif ($size == 11 && strpos($ntel, "00692") === 0) {
 | 
			
		||||
      $tel = "00262".substr($tel, 2);
 | 
			
		||||
    } elseif ($size == 11 && strpos($ntel, "00693") === 0) {
 | 
			
		||||
      $tel = "00262".substr($tel, 2);
 | 
			
		||||
    }
 | 
			
		||||
    # est-ce un numéro français?
 | 
			
		||||
    if (substr($tel, 0, 5) == "00262") {
 | 
			
		||||
      return self::format_local("0".substr($tel, 5));
 | 
			
		||||
    } elseif (substr($tel, 0, 4) == "0033") {
 | 
			
		||||
      return self::format_local("0".substr($tel, 4));
 | 
			
		||||
    }
 | 
			
		||||
    # chercher la partie internationale
 | 
			
		||||
    $prefix = false;
 | 
			
		||||
    foreach (self::COD_PAYS as $cod_pay) {
 | 
			
		||||
      $cod_pay = $cod_pay[0];
 | 
			
		||||
      $plen = strlen($cod_pay);
 | 
			
		||||
      if (substr($tel, 0, $plen + 2) == "00$cod_pay") {
 | 
			
		||||
        $prefix = "+$cod_pay";
 | 
			
		||||
        $tel = substr($tel, $plen + 2);
 | 
			
		||||
        break;
 | 
			
		||||
      } elseif (substr($tel, 0, $plen) == $cod_pay) {
 | 
			
		||||
        $prefix = "+$cod_pay";
 | 
			
		||||
        $tel = substr($tel, $plen);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if ($prefix === false) return $tel;
 | 
			
		||||
    return "$prefix ".self::format_foreign($tel);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * formatter le numéro pour qu'il soit toujours au format international.
 | 
			
		||||
   * le numéro doit avoir déjà été formatté au préalable
 | 
			
		||||
   */
 | 
			
		||||
  function ensureInternational(?string $tel): ?string {
 | 
			
		||||
    if ($tel === null) return null;
 | 
			
		||||
    else if ($tel[0] == "+") return $tel;
 | 
			
		||||
    switch (substr($tel, 0, 4)) {
 | 
			
		||||
    case "0262":
 | 
			
		||||
    case "0692":
 | 
			
		||||
    case "0693":
 | 
			
		||||
      return "+262 ".substr($tel, 1);
 | 
			
		||||
    default:
 | 
			
		||||
      return "+33 ".substr($tel, 1);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * formatter le numéro pour qu'il soit toujours au format local si possible.
 | 
			
		||||
   * c'est le "contraire" de {@link ensureInternational()}
 | 
			
		||||
   */
 | 
			
		||||
  function ensureLocal(?string $tel): ?string {
 | 
			
		||||
    if ($tel === null) return null;
 | 
			
		||||
    elseif (substr($tel, 0, 5) == "+262 ") return "0".substr($tel, 5);
 | 
			
		||||
    elseif (substr($tel, 0, 4) == "+33 ") return "0".substr($tel, 4);
 | 
			
		||||
    else return $tel;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								nur_src/data/types/TextType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								nur_src/data/types/TextType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
use nur\str;
 | 
			
		||||
use nur\txt;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class TextType: un texte encodé en utf-8
 | 
			
		||||
 */
 | 
			
		||||
class TextType extends StringType {
 | 
			
		||||
  function beforeCheckInstance(&$value): bool {
 | 
			
		||||
    if (is_array($value)) $value = str::join3($value);
 | 
			
		||||
    if ($this->ppChangeCase !== null && is_string($value)) {
 | 
			
		||||
      switch ($this->ppChangeCase) {
 | 
			
		||||
      case "upper":
 | 
			
		||||
      case "uc":
 | 
			
		||||
        $value = txt::upper($value);
 | 
			
		||||
        break;
 | 
			
		||||
      case "upper1":
 | 
			
		||||
      case "u1":
 | 
			
		||||
        $value = txt::upper1($value);
 | 
			
		||||
        break;
 | 
			
		||||
      case "upperw":
 | 
			
		||||
      case "uw":
 | 
			
		||||
        $value = txt::upperw($value);
 | 
			
		||||
        break;
 | 
			
		||||
      case "lower":
 | 
			
		||||
      case "lc":
 | 
			
		||||
        $value = txt::lower($value);
 | 
			
		||||
        break;
 | 
			
		||||
      case "lower1":
 | 
			
		||||
      case "l1":
 | 
			
		||||
        $value = txt::lower1($value);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										128
									
								
								nur_src/data/types/TimeType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								nur_src/data/types/TimeType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,128 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
use nur\b\date\Time;
 | 
			
		||||
 | 
			
		||||
class TimeType extends AbstractSimpleType {
 | 
			
		||||
  const TITLE = "heure du jour";
 | 
			
		||||
 | 
			
		||||
  function getClass(): string {
 | 
			
		||||
    return Time::class;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function isInstance($value, bool $strict=false): bool {
 | 
			
		||||
    return $value === null || $value instanceof Time;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const TIME_PATTERN = '/^(\d+)\s*(?:[hH:.]\s*(?:(\d+)\s*(?:[:.]\s*(\d+)\s*)?)?)?/';
 | 
			
		||||
 | 
			
		||||
  /** @return array|false un tableau de la forme [$h, $m, $s] */
 | 
			
		||||
  function parse(string &$input) {
 | 
			
		||||
    if (preg_match(self::TIME_PATTERN, $input, $ms)) {
 | 
			
		||||
      $h = intval($ms[1]);
 | 
			
		||||
      $m = isset($ms[2])? intval($ms[2]): 0;
 | 
			
		||||
      $s = isset($ms[3])? intval($ms[3]): 0;
 | 
			
		||||
      $input = substr($input, strlen($ms[0]));
 | 
			
		||||
      return [$h, $m, $s];
 | 
			
		||||
    } else {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const ALLOW_NULL = true;
 | 
			
		||||
  const ALLOW_FALSE = false;
 | 
			
		||||
 | 
			
		||||
  function verifixReplaceNull(&$value): void {
 | 
			
		||||
    if (!$this->ppAllowNull) $value = Time::null();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function verifixReplaceFalse(&$value): void {
 | 
			
		||||
    if (!$this->ppAllowFalse) $value = Time::undef();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function verifixReplaceEmpty(&$value): void {
 | 
			
		||||
    if (!$this->ppAllowFalse) $value = Time::undef();
 | 
			
		||||
    else $value = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function verifixConvert(&$value): bool {
 | 
			
		||||
    if ($value === null) return true;
 | 
			
		||||
    $value = new Time($value[0] * 3600 + $value[1] * 60 + $value[2]);
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function dump($value) {
 | 
			
		||||
    if ($value instanceof Time) {
 | 
			
		||||
      if (!$this->ppAllowNull && $value->isNull()) $value = null;
 | 
			
		||||
      if (!$this->ppAllowFalse && $value->isUndef()) $value = false;
 | 
			
		||||
    }
 | 
			
		||||
    return $value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function load($value) {
 | 
			
		||||
    if ($value === null && !$this->ppAllowNull) $value = Time::null();
 | 
			
		||||
    elseif ($value === false && !$this->ppAllowFalse) $value = Time::undef();
 | 
			
		||||
    return $value;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  #############################################################################
 | 
			
		||||
  # Méthodes utilitaires
 | 
			
		||||
  
 | 
			
		||||
  function isNone($time): bool {
 | 
			
		||||
    if ($time instanceof Time) return $time->isNull();
 | 
			
		||||
    else return $time === null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function isUndef($time, $key=null): bool {
 | 
			
		||||
    if ($key !== null) {
 | 
			
		||||
      if (!is_array($time)) return $key !== 0;
 | 
			
		||||
      if (!array_key_exists($key, $time)) return true;
 | 
			
		||||
      $time = $time[$key];
 | 
			
		||||
    }
 | 
			
		||||
    if ($time instanceof Time) return $time->isUndef();
 | 
			
		||||
    else return $time === false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function add($time, $add): ?Time {
 | 
			
		||||
    $time = $this->with($time);
 | 
			
		||||
    $add = $this->with($add);
 | 
			
		||||
    if ($add === null) return $time;
 | 
			
		||||
    elseif ($time === null) return $add;
 | 
			
		||||
    return $time->add($add);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function sub($time, $sub): ?Time {
 | 
			
		||||
    $time = $this->with($time);
 | 
			
		||||
    $sub = $this->with($sub);
 | 
			
		||||
    if ($sub === null) return $time;
 | 
			
		||||
    elseif ($time === null) $time = $sub->newu(0);
 | 
			
		||||
    return $time->sub($sub);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function cmp($time, $other): int {
 | 
			
		||||
    $time = $this->with($time);
 | 
			
		||||
    $other = $this->with($other);
 | 
			
		||||
    if ($time === null && $other === null) return 0;
 | 
			
		||||
    elseif ($time === null) return -1;
 | 
			
		||||
    elseif ($other === null) return 1;
 | 
			
		||||
    return $time->cmp($other);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function before($time, $other): bool {
 | 
			
		||||
    $time = $this->with($time);
 | 
			
		||||
    $other = $this->with($other);
 | 
			
		||||
    if ($time === null && $other === null) return true;
 | 
			
		||||
    elseif ($time === null) return true;
 | 
			
		||||
    elseif ($other === null) return false;
 | 
			
		||||
    return $time->before($other);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function after($time, $other): bool {
 | 
			
		||||
    $time = $this->with($time);
 | 
			
		||||
    $other = $this->with($other);
 | 
			
		||||
    if ($time === null && $other === null) return true;
 | 
			
		||||
    elseif ($time === null) return false;
 | 
			
		||||
    elseif ($other === null) return true;
 | 
			
		||||
    return $time->after($other);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								nur_src/data/types/Tmd.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								nur_src/data/types/Tmd.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
trait Tmd {
 | 
			
		||||
  /** @var Metadata */
 | 
			
		||||
  private static $md;
 | 
			
		||||
 | 
			
		||||
  protected function md(): ?Metadata {
 | 
			
		||||
    $schema = self::SCHEMA;
 | 
			
		||||
    if ($schema === null) return null;
 | 
			
		||||
    else return md_utils::ensure_md($md, $schema);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										34
									
								
								nur_src/data/types/TriboolType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								nur_src/data/types/TriboolType.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
class TriboolType extends BoolType {
 | 
			
		||||
  const TITLE = "valeur triléenne";
 | 
			
		||||
 | 
			
		||||
  const ALLOW_NULL = true;
 | 
			
		||||
  const FORMAT = self::OUINONNULL_FORMAT;
 | 
			
		||||
 | 
			
		||||
  static final function to_tribool($value): ?bool {
 | 
			
		||||
    return $value !== null? self::to_bool($value): null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function isInstance($value, bool $strict=false): bool {
 | 
			
		||||
    return $value === null || is_bool($value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function isUndef($value, $key=null): bool {
 | 
			
		||||
    if ($key !== null) {
 | 
			
		||||
      if (!is_array($value)) return $key === 0;
 | 
			
		||||
      return array_key_exists($key, $value);
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function is3States(): bool {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function get3States(): array {
 | 
			
		||||
    return [false, true, null];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								nur_src/data/types/_Tparametrable.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								nur_src/data/types/_Tparametrable.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
use nur\b\params\parametrable_utils;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * variante de {@link Tparametrable} qui n'utilise pas {@link Metadata} pour
 | 
			
		||||
 * éviter une boucle infinie.
 | 
			
		||||
 */
 | 
			
		||||
trait _Tparametrable {
 | 
			
		||||
  use _Tparametrable0;
 | 
			
		||||
 | 
			
		||||
  function setParametrableParams(?array $params): void {
 | 
			
		||||
    parent::setParametrableParams($params);
 | 
			
		||||
    $parametrables = $this->getParametrableParamsParametrables();
 | 
			
		||||
    parametrable_utils::set_params($parametrables, $this, $params, self::PARAMETRABLE_PARAMS_SCHEMA);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function initParametrableParams(?array $params, bool $setParametrableParams=true): void {
 | 
			
		||||
    parent::initParametrableParams(null);
 | 
			
		||||
    $parametrables = $this->getParametrableParamsParametrables();
 | 
			
		||||
    parametrable_utils::set_defaults($parametrables, $this, self::PARAMETRABLE_PARAMS_SCHEMA);
 | 
			
		||||
    if ($setParametrableParams) $this->setParametrableParams($params);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								nur_src/data/types/_Tparametrable0.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								nur_src/data/types/_Tparametrable0.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
use nur\b\params\parametrable_utils;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Trait _Tparametrable0: implémentation partagée entre {@link _Tparametrable1}
 | 
			
		||||
 * et {@link _Tparametrable} qui doit toujours être écrasée dans les classes
 | 
			
		||||
 * dérivées (i.e si les méthodes définies ici sont surchargées, elle ne seront
 | 
			
		||||
 * pas disponibles dans les classes dérivées parce qu'écrasées par la directive
 | 
			
		||||
 * `use _Tparametrable`)
 | 
			
		||||
 */
 | 
			
		||||
trait _Tparametrable0 {
 | 
			
		||||
  function setParametrableParams(?array $params): void {
 | 
			
		||||
    $parametrables = $this->getParametrableParamsParametrables();
 | 
			
		||||
    parametrable_utils::set_params($parametrables, $this, $params, self::PARAMETRABLE_PARAMS_SCHEMA);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function initParametrableParams(?array $params, bool $setParametrableParams=true): void {
 | 
			
		||||
    $parametrables = $this->getParametrableParamsParametrables();
 | 
			
		||||
    parametrable_utils::set_defaults($parametrables, $this, self::PARAMETRABLE_PARAMS_SCHEMA);
 | 
			
		||||
    if ($setParametrableParams) $this->setParametrableParams($params);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								nur_src/data/types/_Tparametrable1.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								nur_src/data/types/_Tparametrable1.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
use nur\b\params\parametrable_utils;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * variante de {@link Tparametrable1} qui n'utilise pas {@link Metadata} pour
 | 
			
		||||
 * éviter une boucle infinie.
 | 
			
		||||
 *
 | 
			
		||||
 * NB: on ne met ici que les méthodes qui doivent pourvoir être surchargées.
 | 
			
		||||
 * toutes les autres méthodes doivent être dans {@link _Tparametrable0}
 | 
			
		||||
 */
 | 
			
		||||
trait _Tparametrable1 {
 | 
			
		||||
  use _Tparametrable0;
 | 
			
		||||
 | 
			
		||||
  /** obtenir la destination de certains paramètres si elle diffère de $this */
 | 
			
		||||
  protected function getParametrableParamsParametrables(): ?array {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								nur_src/data/types/md_utils.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								nur_src/data/types/md_utils.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
<?php
 | 
			
		||||
namespace nur\data\types;
 | 
			
		||||
 | 
			
		||||
class md_utils {
 | 
			
		||||
  static function ensure_md(?Metadata &$md, array $schema, ?array $params=null): Metadata {
 | 
			
		||||
    if ($md === null) $md = new Metadata($schema, $params);
 | 
			
		||||
    return $md;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static function ensure_md_func(?Metadata &$md, callable $get_md): Metadata {
 | 
			
		||||
    if ($md === null) $md = $get_md();
 | 
			
		||||
    return $md;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static function ensure_schema(&$item, ?Metadata &$md, array $schema, ?array $params=null): void {
 | 
			
		||||
    self::ensure_md($md, $schema, $params)->ensureSchema($item);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user