ajout nur/data
This commit is contained in:
parent
09cd738458
commit
8dc8d12ab3
|
@ -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) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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); }
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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*)?/';
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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";
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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…
Reference in New Issue