modifs.mineures sans commentaires

This commit is contained in:
Jephté Clain 2023-11-28 08:20:33 +04:00
parent 2da6cef41a
commit 94790bef34
18 changed files with 262 additions and 68 deletions

View File

@ -1,30 +1,21 @@
<?php <?php
namespace nur\sery\schema; namespace nur\sery\schema;
use nur\sery\schema\_assoc\AssocResult;
use nur\sery\schema\_list\ListResult;
use nur\sery\schema\_scalar\ScalarResult;
/** /**
* Class Result: résultat de l'analyse ou de la normalisation d'une valeur * Class Result: résultat de l'analyse ou de la normalisation d'une valeur
*
* @property-read bool $valid
* @property-read string|int|null $key si la valeur est dans un tableau, clé de
* la valeur dans le tableau
* @property-read string|null $orig valeur originale avant analyse
* @property-read string|null $message message d'erreur si la valeur n'est pas
* valide
*/ */
class Result { abstract class Result {
const KEYS = ["valid", "key", "orig", "message"]; function __construct() {
$this->reset();
function __construct(?array $result=null) {
if ($result === null) $result = ["valid" => false];
foreach (static::KEYS as $key) {
if (!array_key_exists($key, $result)) $result[$key] = null;
}
#XXX interpoler [message] avec les clés de $result
$this->result = $result;
} }
/** @var array */ function isAssoc(?AssocResult &$assoc=null): bool { return false; }
protected $result; function isList(?ListResult &$list=null): bool { return false; }
function isScalar(?ScalarResult &$scalar=null): bool { return false; }
/** /**
* Obtenir la liste des clés valides pour les valeurs accessibles via cet * Obtenir la liste des clés valides pour les valeurs accessibles via cet
@ -35,7 +26,5 @@ class Result {
/** obtenir un objet pour gérer la valeur spécifiée */ /** obtenir un objet pour gérer la valeur spécifiée */
abstract function getResult($key=null): Result; abstract function getResult($key=null): Result;
function __get($name) { abstract function reset(): void;
return $this->result[$name];
}
} }

View File

@ -13,14 +13,14 @@ abstract class Schema implements ArrayAccess {
* créer si besoin une nouvelle instance de {@link Schema} à partir d'une * créer si besoin une nouvelle instance de {@link Schema} à partir d'une
* définition de schéma * définition de schéma
*/ */
static function ns(?Schema &$schema, $definition): self { static function ns(?Schema &$schema, $definition, $definitionKey=null): self {
if ($schema === null) { if ($schema === null) {
if (AssocSchema::isa_definition($definition)) { if (AssocSchema::isa_definition($definition)) {
$schema = new AssocSchema($definition); $schema = new AssocSchema($definition, $definitionKey);
} elseif (ListSchema::isa_definition($definition)) { } elseif (ListSchema::isa_definition($definition)) {
$schema = new ListSchema($definition); $schema = new ListSchema($definition, $definitionKey);
} elseif (ScalarSchema::isa_definition($definition)) { } elseif (ScalarSchema::isa_definition($definition)) {
$schema = new ScalarSchema($definition); $schema = new ScalarSchema($definition, $definitionKey);
} else { } else {
throw SchemaException::invalid_schema(); throw SchemaException::invalid_schema();
} }

View File

@ -10,15 +10,13 @@ class SchemaException extends Exception {
return new static($invalid_schema); return new static($invalid_schema);
} }
static final function invalid_type($value, ?string $message=null): self { static final function invalid_kind(string $kind, $value, ?string $message=null): self {
$invalid_type = var_export($value, true).": invalid type"; $invalid_kind = var_export($value, true).": invalid $kind";
if ($message !== null) $invalid_type .= ": $message"; if ($message !== null) $invalid_kind .= ": $message";
return new static($invalid_type); return new static($invalid_kind);
} }
static final function invalid_callable($value, ?string $message=null): self { static final function invalid_type($value, ?string $message=null): self {
$invalid_callable = var_export($value, true).": invalid callable"; return self::invalid_kind("type", $value, $message);
if ($message !== null) $invalid_callable .= ": $message";
return new static($invalid_callable);
} }
} }

View File

@ -3,9 +3,16 @@ namespace nur\sery\schema;
use ArrayAccess; use ArrayAccess;
use IteratorAggregate; use IteratorAggregate;
use nur\sery\schema\_assoc\AssocValue;
use nur\sery\schema\_list\ListValue;
use nur\sery\schema\_scalar\ScalarValue;
use nur\sery\schema\types\IType; use nur\sery\schema\types\IType;
abstract class Value implements ArrayAccess, IteratorAggregate { abstract class Value implements ArrayAccess, IteratorAggregate {
function isAssoc(?AssocValue &$assoc=null): bool { return false; }
function isList(?ListValue &$list=null): bool { return false; }
function isScalar(?ScalarValue &$scalar=null): bool { return false; }
/** spécifier la valeur destination gérée par cet objet */ /** spécifier la valeur destination gérée par cet objet */
abstract function reset(&$dest, $destKey=null, bool $verifix=true): self; abstract function reset(&$dest, $destKey=null, bool $verifix=true): self;
@ -28,7 +35,7 @@ abstract class Value implements ArrayAccess, IteratorAggregate {
abstract function getType(): IType; abstract function getType(): IType;
/** retourner true si la valeur existe */ /** retourner true si la valeur existe */
abstract function exists(): bool; abstract function available(): bool;
/** supprimer la valeur */ /** supprimer la valeur */
abstract function unset(): void; abstract function unset(): void;

View File

@ -0,0 +1,20 @@
<?php
namespace nur\sery\schema;
use Exception;
class ValueException extends Exception {
static final function invalid_kind(string $kind, $value, ?string $message=null): self {
$invalid_kind = var_export($value, true).": invalid $kind";
if ($message !== null) $invalid_kind .= ": $message";
return new static($invalid_kind);
}
static final function invalid_key($value, ?string $message=null): self {
return self::invalid_kind("key", $value, $message);
}
static final function invalid_value($value, ?string $message=null): self {
return self::invalid_kind("value", $value, $message);
}
}

View File

@ -4,4 +4,5 @@ namespace nur\sery\schema\_assoc;
use nur\sery\schema\Result; use nur\sery\schema\Result;
class AssocResult extends Result { class AssocResult extends Result {
function isAssoc(?AssocResult &$assoc=null): bool { $assoc = $this; return true;}
} }

View File

@ -31,12 +31,12 @@ class AssocSchema extends Schema {
return !cl::have_num_keys($definition); return !cl::have_num_keys($definition);
} }
static function normalize($definition): array { static function normalize($definition, $definitionKey=null): array {
} }
function __construct($definition=null, bool $normalize=true) { function __construct($definition=null, $definitionKey=null, bool $normalize=true) {
if ($definition === null) $definition = static::SCHEMA; if ($definition === null) $definition = static::SCHEMA;
if ($normalize) $definition = self::normalize($definition); if ($normalize) $definition = self::normalize($definition, $definitionKey);
$this->definition = $definition; $this->definition = $definition;
} }

View File

@ -4,4 +4,5 @@ namespace nur\sery\schema\_list;
use nur\sery\schema\Result; use nur\sery\schema\Result;
class ListResult extends Result { class ListResult extends Result {
function isList(?ListResult &$list=null): bool { $list = $this; return true;}
} }

View File

@ -31,12 +31,12 @@ class ListSchema extends Schema {
return $count == 1 && $haveIndex0 && is_array($definition[0]); return $count == 1 && $haveIndex0 && is_array($definition[0]);
} }
static function normalize($definition): array { static function normalize($definition, $definitionKey=null): array {
} }
function __construct($definition=null, bool $normalize=true) { function __construct($definition=null, $definitionKey=null, bool $normalize=true) {
if ($definition === null) $definition = static::SCHEMA; if ($definition === null) $definition = static::SCHEMA;
if ($normalize) $definition = self::normalize($definition); if ($normalize) $definition = self::normalize($definition, $definitionKey);
$this->definition = $definition; $this->definition = $definition;
} }

View File

@ -1,7 +1,82 @@
<?php <?php
namespace nur\sery\schema\_scalar; namespace nur\sery\schema\_scalar;
use nulib\cl;
use nur\sery\schema\ref\ref_analyze;
use nur\sery\schema\Result; use nur\sery\schema\Result;
use nur\sery\schema\ValueException;
/**
* Class ScalarResult: résultat de l'analyse ou de la normalisation d'une valeur
*
* @property bool $missing la valeur est-elle inexistante?
* @property bool $null si elle existe, la valeur est-nulle?
* @property bool $valid si elle existe, la valeur est-elle valide?
* @property bool $normalized si la valeur est valide, est-elle normalisée?
* @property string|null $orig valeur originale avant analyse avec parse()
* @property string|null $message message si la valeur n'est pas valide
*/
class ScalarResult extends Result { class ScalarResult extends Result {
const KEYS = ["missing", "null", "valid", "normalized", "orig", "message"];
function isScalar(?ScalarResult &$scalar=null): bool { $scalar = $this; return true; }
function getKeys(): array {
return [null];
}
function getResult($key=null): Result {
if ($key === null) return $this;
else throw ValueException::invalid_key($key);
}
/** @var array */
protected $result;
function reset(): void {
$this->result = array_merge(
array_fill_keys(static::KEYS, null), [
"missing" => true,
"null" => false,
"valid" => false,
"normalized" => false,
]);
}
function __get(string $name) {
return $this->result[$name];
}
function __set(string $name, $value): void {
$this->result[$name] = $value;
}
function setMissing(ScalarSchema $schema, ?string $key=null): int {
$this->missing = true;
if (!$schema->required) {
$this->null = true;
$this->valid = true;
$this->normalized = true;
return ref_analyze::NORMALIZED;
} else {
$message = cl::get($schema->messages, "missing");
$message = str_replace("{key}", $key, $message);
$this->message = $message;
return ref_analyze::MISSING;
}
}
function setNull(ScalarSchema $schema, ?string $key=null): int {
$this->missing = true;
if ($schema->nullable) {
$this->valid = true;
$this->normalized = true;
return ref_analyze::NORMALIZED;
} else {
$message = cl::get($schema->messages, "null");
$message = str_replace("{key}", $key, $message);
$this->message = $message;
return ref_analyze::NULL;
}
}
} }

View File

@ -169,9 +169,9 @@ class ScalarSchema extends Schema {
return $definition; return $definition;
} }
function __construct($definition=null, bool $normalize=true) { function __construct($definition=null, $definitionKey=null, bool $normalize=true) {
if ($definition === null) $definition = static::SCHEMA; if ($definition === null) $definition = static::SCHEMA;
if ($normalize) $definition = self::normalize($definition); if ($normalize) $definition = self::normalize($definition, $definitionKey);
$this->definition = $definition; $this->definition = $definition;
} }

View File

@ -2,13 +2,16 @@
namespace nur\sery\schema\_scalar; namespace nur\sery\schema\_scalar;
use nur\sery\schema\input\Input; use nur\sery\schema\input\Input;
use nur\sery\schema\ref\ref_analyze;
use nur\sery\schema\Result; use nur\sery\schema\Result;
use nur\sery\schema\types\IType; use nur\sery\schema\types\IType;
use nur\sery\schema\Value; use nur\sery\schema\Value;
use nur\sery\schema\ValueException;
class ScalarValue extends Value { class ScalarValue extends Value {
function __construct(ScalarSchema $schema, &$dest=null, $key=null, bool $verifix=true) { function __construct(ScalarSchema $schema, &$dest=null, $key=null, bool $verifix=true) {
$this->schema = $schema; $this->schema = $schema;
$this->result = new ScalarResult();
$this->reset($dest, $key, $verifix); $this->reset($dest, $key, $verifix);
} }
@ -23,7 +26,10 @@ class ScalarValue extends Value {
/** @var string|int clé de la valeur dans le tableau destination */ /** @var string|int clé de la valeur dans le tableau destination */
protected $destKey; protected $destKey;
/** @var ?Result résultat de l'analyse de la valeur */ /** @var IType */
protected $type;
/** @var ?ScalarResult résultat de l'analyse de la valeur */
protected $result; protected $result;
function reset(&$dest, $destKey=null, bool $verifix=true): Value { function reset(&$dest, $destKey=null, bool $verifix=true): Value {
@ -34,28 +40,53 @@ class ScalarValue extends Value {
$this->result = null; $this->result = null;
#XXX résoudre les types ici? #XXX résoudre les types ici?
if ($verifix) $this->verifix(); if ($verifix) $this->verifix();
else $this->_analyze();
return $this; return $this;
} }
function getKeys(): array {
return [null];
}
function getValue($key=null): Value {
if ($key === null) return $this;
throw ValueException::invalid_key($key);
}
/** analyser la valeur et résoudre son type */
function _analyze(): int {
$schema = $this->schema;
$input = $this->input;
$destKey = $this->destKey;
$result = $this->result;
$result->reset();
if (!$input->available()) return $result->setMissing($schema, $destKey);
$value = $input->get($destKey);
if ($value === null) return $result->setNull($schema, $destKey);
#XXX résoudre le type
}
function exists(): bool { function exists(): bool {
return $this->input->exists($this->destKey); return $this->input->exits($this->destKey);
}
function available(): bool {
return $this->input->available($this->destKey);
} }
function get($default=null) { function get($default=null) {
$destKey = $this->destKey; $destKey = $this->destKey;
$input = $this->input; $input = $this->input;
if ($input->exists($destKey)) return $input->get($destKey); if ($input->available($destKey)) return $input->get($destKey);
else return $default; else return $default;
} }
function set($value): self { function set($value, bool $verifix=true): Value {
$this->input->set($value, $this->destKey); $this->input->set($value, $this->destKey);
if ($verifix) $this->verifix();
return $this; return $this;
} }
/** @var IType */
protected $type;
function getType(): IType { function getType(): IType {
if ($this->type === null) $this->type = $this->schema->getType($this->destKey); if ($this->type === null) $this->type = $this->schema->getType($this->destKey);
return $this->type; return $this->type;

View File

@ -12,15 +12,36 @@ namespace nur\sery\schema\input;
* *
*/ */
class FormInput extends Input { class FormInput extends Input {
const ALLOW_EMPTY = false;
function exists($key=null): bool { function exists($key=null): bool {
if ($key === null) return false; if ($key === null) return false;
return array_key_exists($key, $_POST) || array_key_exists($key, $_GET); return array_key_exists($key, $_POST) || array_key_exists($key, $_GET);
} }
function available($key=null): bool {
if ($key === null) return false;
if (array_key_exists($key, $_POST)) {
return $this->allowEmpty || $_POST[$key] !== "";
} elseif (array_key_exists($key, $_GET)) {
return $this->allowEmpty || $_GET[$key] !== "";
} else {
return false;
}
}
function get($key=null) { function get($key=null) {
if ($key === null) return null; if ($key === null) return null;
if (array_key_exists($key, $_POST)) return $_POST[$key]; if (array_key_exists($key, $_POST)) {
elseif (array_key_exists($key, $_GET)) return $_GET[$key]; $value = $_POST[$key];
else return null; if ($value === "" && !$this->allowEmpty) return null;
return $value;
} elseif (array_key_exists($key, $_GET)) {
$value = $_GET[$key];
if ($value === "" && !$this->allowEmpty) return null;
return $value;
} else {
return null;
}
} }
} }

View File

@ -13,9 +13,23 @@ class GetInput extends FormInput {
return array_key_exists($key, $_GET); return array_key_exists($key, $_GET);
} }
function available($key=null): bool {
if ($key === null) return false;
if (array_key_exists($key, $_GET)) {
return $this->allowEmpty || $_GET[$key] !== "";
} else {
return false;
}
}
function get($key=null) { function get($key=null) {
if ($key === null) return null; if ($key === null) return null;
if (array_key_exists($key, $_GET)) return $_GET[$key]; if (array_key_exists($key, $_GET)) {
else return null; $value = $_GET[$key];
if ($value === "" && !$this->allowEmpty) return null;
return $value;
} else {
return null;
}
} }
} }

View File

@ -4,29 +4,52 @@ namespace nur\sery\schema\input;
#XXX available permet de préciser qu'une valeur doit exister et être non vide #XXX available permet de préciser qu'une valeur doit exister et être non vide
# pour être considérée. en faire un paramètre, à moins que ce soit au niveau du # pour être considérée. en faire un paramètre, à moins que ce soit au niveau du
# type qu'il faille le gérer? # type qu'il faille le gérer?
use nulib\cl;
/** /**
* Class Input: accès à une valeur * Class Input: accès à une valeur
* *
* cette implémentation lit depuis et écrit dans une référence * cette implémentation lit depuis et écrit dans une référence
*/ */
class Input { class Input {
function __construct(&$dest=null) { const ALLOW_EMPTY = true;
function __construct(&$dest=null, ?array $params=null) {
$this->dest =& $dest; $this->dest =& $dest;
$allowEmpty = cl::get($params, "allow_empty");
if ($allowEmpty === null) $allowEmpty = static::ALLOW_EMPTY;
$this->allowEmpty = $allowEmpty;
} }
protected $dest; protected $dest;
/** tester si la valeur existe */ /** tester si la valeur existe sans tenir compte de $allowEmpty */
function exists($key=null): bool { function exits($key=null): bool {
if ($key === null) return true; if ($key === null) return true;
return $this->dest !== null && array_key_exists($key, $this->dest); $dest = $this->dest;
return $dest !== null && array_key_exists($key, $dest);
}
/**
* @var bool comment considérer une chaine vide: "" si allowEmpty, null sinon
*/
protected $allowEmpty;
/** tester si la valeur est disponible en tenant compte de $allowEmpty */
function available($key=null): bool {
if ($key === null) return true;
$dest = $this->dest;
if ($dest === null || !array_key_exists($key, $dest)) return false;
return $this->allowEmpty || $dest[$key] !== "";
} }
function get($key=null) { function get($key=null) {
if ($key === null) return $this->dest; $dest = $this->dest;
elseif ($this->dest === null) return null; if ($key === null) return $dest;
elseif (!array_key_exists($key, $this->dest)) return null; if ($dest === null || !array_key_exists($key, $dest)) return null;
else return $this->dest[$key]; $value = $dest[$key];
if ($value === "" && !$this->allowEmpty) return null;
return $value;
} }
function set($value, $key=null): void { function set($value, $key=null): void {

View File

@ -13,9 +13,23 @@ class PostInput extends FormInput {
return array_key_exists($key, $_POST); return array_key_exists($key, $_POST);
} }
function available($key=null): bool {
if ($key === null) return false;
if (array_key_exists($key, $_POST)) {
return $this->allowEmpty || $_POST[$key] !== "";
} else {
return false;
}
}
function get($key=null) { function get($key=null) {
if ($key === null) return null; if ($key === null) return null;
if (array_key_exists($key, $_POST)) return $_POST[$key]; if (array_key_exists($key, $_POST)) {
else return null; $value = $_POST[$key];
if ($value === "" && !$this->allowEmpty) return null;
return $value;
} else {
return null;
}
} }
} }

View File

@ -41,7 +41,7 @@ class ref_schema {
]; ];
const MESSAGES = [ const MESSAGES = [
"absent" => "{key}: Vous devez spécifier cette valeur", "missing" => "{key}: Vous devez spécifier cette valeur",
"null" => "{key}: Cette valeur ne doit pas être nulle", "null" => "{key}: Cette valeur ne doit pas être nulle",
"empty" => "{key}: Cette valeur ne doit pas être vide", "empty" => "{key}: Cette valeur ne doit pas être vide",
"invalid" => "{key}: {orig}: cette valeur est invalide", "invalid" => "{key}: {orig}: cette valeur est invalide",

View File

@ -1,11 +1,11 @@
<?php <?php
namespace nur\sery\schema\types; namespace nur\sery\schema\types;
use nur\sery\schema\SchemaException; use nur\sery\schema\ValueException;
class tcallable implements IType { class tcallable implements IType {
static function ensure_callable(&$callable): void { static function ensure_callable(&$callable): void {
if (!is_callable($callable)) throw SchemaException::invalid_callable($callable); if (!is_callable($callable)) throw ValueException::invalid_value($callable);
} }
static function ensure_ncallable(&$callable): void { static function ensure_ncallable(&$callable): void {