modifs.mineures sans commentaires

This commit is contained in:
Jephté Clain 2023-12-28 19:33:13 +04:00
parent 75654a6515
commit 753a6dc75d
20 changed files with 225 additions and 117 deletions

View File

@ -3,6 +3,7 @@ namespace nur\sery\schema;
use ArrayAccess; use ArrayAccess;
use LogicException; use LogicException;
use nulib\AccessException;
use nulib\cl; use nulib\cl;
use nur\sery\schema\_assoc\AssocSchema; use nur\sery\schema\_assoc\AssocSchema;
use nur\sery\schema\_list\ListSchema; use nur\sery\schema\_list\ListSchema;
@ -12,8 +13,17 @@ 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
*
* - si $schema est une instance de schéma, la retourner
* - si $schema est un array, c'est une définition, et elle est remplacée par
* l'instance de Schema nouvelle créée
* - sinon, prendre $definition comme définition
*/ */
static function ns(?Schema &$schema, $definition, $definitionKey=null): self { static function ns(&$schema, $definition=null, $definitionKey=null): self {
if (is_array($schema)) {
$definition = $schema;
$schema = null;
}
if ($schema === null) { if ($schema === null) {
if (AssocSchema::isa_definition($definition)) { if (AssocSchema::isa_definition($definition)) {
$schema = new AssocSchema($definition, $definitionKey); $schema = new AssocSchema($definition, $definitionKey);
@ -30,15 +40,16 @@ abstract class Schema implements ArrayAccess {
/** /**
* Créer si besoin une nouvelle instance de {@link Value} qui référence la * Créer si besoin une nouvelle instance de {@link Value} qui référence la
* variable $dest * variable $dest (si $destKey===null) ou $dest[$destKey] si $destKey n'est
* pas null
*/ */
static function nv(?Value &$value=null, &$dest=null, $key=null, ?Schema &$schema=null, $definition=null): Value { static function nv(?Value &$value=null, &$dest=null, $destKey=null, &$schema=null, $definition=null): Value {
if ($definition === null) { if ($definition === null) {
# bien que techniquement, $definition peut être null (il s'agit alors du # bien que techniquement, $definition peut être null (il s'agit alors du
# schéma d'un scalaire quelconque), on ne l'autorise pas ici # schéma d'un scalaire quelconque), on ne l'autorise pas ici
throw SchemaException::invalid_schema("definition is required"); throw SchemaException::invalid_schema("definition is required");
} }
return self::ns($schema, $definition)->newValue($value, $dest, $key); return self::ns($schema, $definition)->newValue($value, $dest, $destKey);
} }
/** /**
@ -70,10 +81,10 @@ abstract class Schema implements ArrayAccess {
else return $this->definition[$offset]; else return $this->definition[$offset];
} }
function offsetSet($offset, $value): void { function offsetSet($offset, $value): void {
throw new LogicException("read-only"); throw AccessException::read_only(null, $offset);
} }
function offsetUnset($offset): void { function offsetUnset($offset): void {
throw new LogicException("read-only"); throw AccessException::read_only(null, $offset);
} }
const _PROPERTY_PKEYS = []; const _PROPERTY_PKEYS = [];

View File

@ -5,7 +5,7 @@
on pourrait avoir d'une manière générale quelque chose comme: on pourrait avoir d'une manière générale quelque chose comme:
~~~ ~~~
Schema::ensure(&$schema, ?array $def=null, $key=null): Schema; Schema::ensure(&$schema, ?array $def=null, $defKey=null): Schema;
~~~ ~~~
* si $schema est une instance de Schema, la retourner * si $schema est une instance de Schema, la retourner
* si c'est un array, c'est une définition et il faut la remplacer par l'instance de Schema correspondant * si c'est un array, c'est une définition et il faut la remplacer par l'instance de Schema correspondant

View File

@ -14,7 +14,7 @@ abstract class Value implements ArrayAccess, IteratorAggregate {
function isScalar(?ScalarValue &$scalar=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=null): self;
/** /**
* 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
@ -31,33 +31,55 @@ abstract class Value implements ArrayAccess, IteratorAggregate {
} }
} }
/** retourner le type associé à la valeur */ /**
abstract function getType(): IType; * obtenir le résultat de l'appel d'une des fonctions {@link set()} ou
* {@link unset()}
*/
abstract function getResult(): Result;
/** retourner true si la valeur existe */ /** retourner true si la valeur existe */
abstract function isPresent(): bool; abstract function isPresent(): bool;
/** retourner le type associé à la valeur */
abstract function getType(): IType;
/** retourner true si la valeur est disponible */ /** retourner true si la valeur est disponible */
abstract function isAvailable(): bool; abstract function isAvailable(): bool;
/** supprimer la valeur */
abstract function unset(): void;
/** remplacer la valeur */
abstract function set($value): self;
/** obtenir le résultat de l'appel de la fonction {@link set()} */
abstract function getResult(): Result;
/** retourner true si la valeur est valide */ /** retourner true si la valeur est valide */
abstract function isValid(): bool; abstract function isValid(): bool;
/** obtenir la valeur */
abstract function get($default=null);
/** retourner true si la valeur est dans sa forme normalisée */ /** retourner true si la valeur est dans sa forme normalisée */
abstract function isNormalized(): bool; abstract function isNormalized(): bool;
/** obtenir la valeur */
abstract function get($default=null);
/** remplacer la valeur */
abstract function set($value): self;
/** supprimer la valeur */
abstract function unset(): void;
/** formatter la valeur pour affichage */ /** formatter la valeur pour affichage */
abstract function format($format=null): string; abstract function format($format=null): string;
#############################################################################
# key & properties
function offsetExists($offset): bool {
return in_array($offset, $this->getKeys());
}
function offsetGet($offset) {
return $this->getValue($offset);
}
function offsetSet($offset, $value): void {
$this->getValue($offset)->set($value);
}
function offsetUnset($offset): void {
$this->getValue($offset)->unset();
}
} }

View File

@ -11,8 +11,8 @@ use nur\sery\schema\Result;
* *
* @property bool $present la valeur existe-t-elle? * @property bool $present la valeur existe-t-elle?
* @property bool $available si la valeur existe, est-elle disponible? * @property bool $available si la valeur existe, est-elle disponible?
* @property bool $null si la valeur existe, est-elle nulle? * @property bool $null si la valeur est disponible, est-elle nulle?
* @property bool $valid si la valeur existe, est-elle valide? * @property bool $valid si la valeur est disponible, est-elle valide?
* @property bool $normalized si la valeur est valide, est-elle normalisée? * @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 $orig valeur originale avant analyse avec parse()
* @property string|null $message message si la valeur n'est pas valide * @property string|null $message message si la valeur n'est pas valide
@ -105,4 +105,36 @@ class ScalarResult extends Result {
return ref_analyze::NULL; return ref_analyze::NULL;
} }
} }
function setInvalid(ScalarSchema $schema): int {
$this->present = true;
$this->available = true;
$this->null = false;
$this->valid = false;
$message = cl::get($schema->messages, "invalid");
self::replace_key($message, $schema->name);
$this->message = $message;
return ref_analyze::INVALID;
}
function setValid(): int {
$this->present = true;
$this->available = true;
$this->null = false;
$this->valid = true;
return ref_analyze::VALID;
}
function setNormalized(): int {
$this->present = true;
$this->available = true;
$this->null = false;
$this->valid = true;
$this->normalized = true;
return ref_analyze::NORMALIZED;
}
function throw(bool $throw): void {
if ($throw) throw new ValueException($this->message);
}
} }

View File

@ -3,15 +3,18 @@ namespace nur\sery\schema\_scalar;
use nulib\ValueException; use nulib\ValueException;
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;
use nur\sery\schema\types\IType; use nur\sery\schema\types\IType;
use nur\sery\schema\Value; use nur\sery\schema\Value;
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, $destKey=null, bool $defaultVerifix=true) {
$this->schema = $schema; $this->schema = $schema;
$this->defaultVerifix = $defaultVerifix;
$this->result = new ScalarResult(); $this->result = new ScalarResult();
$this->reset($dest, $key, $verifix); $this->reset($dest, $destKey);
} }
function isScalar(?ScalarValue &$scalar=null): bool { $scalar = $this; return true; } function isScalar(?ScalarValue &$scalar=null): bool { $scalar = $this; return true; }
@ -22,24 +25,27 @@ class ScalarValue extends Value {
/** @var Input source et destination de la valeur */ /** @var Input source et destination de la valeur */
protected $input; protected $input;
/** @var string|int clé de la valeur dans le tableau destination */ /** @var string|int|null clé de la valeur dans le tableau destination */
protected $destKey; protected $destKey;
/** @var IType */ /** @var bool */
protected $defaultVerifix;
/** @var IType type de la valeur après analyse */
protected $type; protected $type;
/** @var ?ScalarResult résultat de l'analyse de la valeur */ /** @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=null): Value {
if ($dest instanceof Input) $input = $dest; if ($dest instanceof Input) $input = $dest;
else $input = new Input($dest); else $input = new Input($dest);
$this->input = $input; $this->input = $input;
$this->destKey = $destKey; $this->destKey = $destKey;
$this->result = null; $this->type = null;
#XXX résoudre les types ici? $this->_analyze();
if ($verifix === null) $verifix = $this->defaultVerifix;
if ($verifix) $this->verifix(); if ($verifix) $this->verifix();
else $this->_analyze();
return $this; return $this;
} }
@ -59,42 +65,28 @@ class ScalarValue extends Value {
$destKey = $this->destKey; $destKey = $this->destKey;
$result = $this->result; $result = $this->result;
$result->reset(); $result->reset();
#XXX résoudre le type if (!$input->isPresent($destKey)) return $result->setMissing($schema);
if (!$input->isAvailable()) return $result->setUnavailable($schema); $haveType = false;
$firstType = null;
foreach ($schema->type as $name) {
$type = types::get($name);
if ($firstType === null) $firstType = $type;
if ($type->canAnalyze($input, $destKey)) {
$haveType = true;
$this->type = $type;
break;
}
}
if (!$haveType) $type = $this->type = $firstType;
if (!$type->isAvailable($input, $destKey)) return $result->setUnavailable($schema);
$value = $input->get($destKey); $value = $input->get($destKey);
if ($value === null) return $result->setNull($schema); if ($type->isNull($value)) return $result->setNull($schema);
if ($type->isValid($value, $normalized)) {
if ($normalized) return $result->setNormalized();
else return $result->setValid();
} }
if (is_string($value)) return ref_analyze::STRING;
function isPresent(): bool { else return $result->setInvalid($schema);
return $this->input->isPresent($this->destKey);
}
function isAvailable(): bool {
return $this->input->isAvailable($this->destKey);
}
function get($default=null) {
$destKey = $this->destKey;
$input = $this->input;
if ($input->isAvailable($destKey)) return $input->get($destKey);
else return $default;
}
function set($value, bool $verifix=true): Value {
$this->input->set($value, $this->destKey);
if ($verifix) $this->verifix();
return $this;
}
function getType(): IType {
if ($this->type === null) $this->type = $this->schema->getType($this->destKey);
return $this->type;
}
function isValid(): bool {
}
function isNormalized(): bool {
} }
/** /**
@ -102,26 +94,61 @@ class ScalarValue extends Value {
* *
* si la valeur était déjà normalisée, retourner false. * si la valeur était déjà normalisée, retourner false.
*/ */
function verifix(bool $throw=true, ?Result &$result=null): bool { function verifix(bool $throw=true): bool {
$type = $this->getType(); $type = $this->getType();
$key = $this->destKey; $destKey = $this->destKey;
if ($key === null) $modified = $type->verifix($this->input, $throw, $result); $value = $this->input->get($destKey);
else $modified = $type->verifix($this->input[$key], $throw, $result); $modified = $type->verifix($value, $this->result);
$this->result = $result; if ($this->result->valid) $this->input->set($value, $destKey);
else $this->result->throw($throw);
return $modified; return $modified;
} }
function parse($value, bool $throw=true, ?Result &$result=null) { function getResult(): Result {
$this->getType()->verifix($value, $throw, $result); return $this->result;
$this->set($value);
$this->result = $result;
return $value;
} }
function format(?string $format=null): string { function isPresent(): bool {
$type = $this->getType(); return $this->result->present;
$key = $this->destKey; }
if ($key === null) return $type->format($this->input, $format);
else return $type->format($this->input[$key], $format); function getType(): IType {
return $this->type;
}
function isAvailable(): bool {
return $this->result->available;
}
function isValid(): bool {
return $this->result->valid;
}
function isNormalized(): bool {
return $this->result->normalized;
}
function get($default=null) {
if ($this->result->available) return $this->input->get($this->destKey);
else return $default;
}
function set($value, ?bool $verifix=null): Value {
$this->input->set($value, $this->destKey);
$this->_analyze();
if ($verifix === null) $verifix = $this->defaultVerifix;
if ($verifix) $this->verifix();
return $this;
}
function unset(?bool $verifix=null): void {
$this->input->unset($this->destKey);
$this->_analyze();
if ($verifix === null) $verifix = $this->defaultVerifix;
if ($verifix) $this->verifix();
}
function format($format=null): string {
return $this->getType()->format($this->input->get($this->destKey), $format);
} }
} }

View File

@ -56,4 +56,9 @@ class Input {
if ($key === null) $this->dest = $value; if ($key === null) $this->dest = $value;
else $this->dest[$key] = $value; else $this->dest[$key] = $value;
} }
function unset($key=null): void {
if ($key === null) $this->dest = null;
else unset($this->dest[$key]);
}
} }

View File

@ -3,8 +3,8 @@ namespace nur\sery\schema\ref;
class ref_types { class ref_types {
const ALIASES = [ const ALIASES = [
"key" => "string|int", "boolean" => "bool",
"pkey" => "string|int|array", "integer" => "int",
"content" => "string|array", "flt" => "float", "double" => "float", "dbl" => "float",
]; ];
} }

View File

@ -1,13 +1,22 @@
<?php <?php
namespace nur\sery\schema\types; namespace nur\sery\schema\types;
use nur\sery\schema\input\Input;
use nur\sery\schema\Result; use nur\sery\schema\Result;
/** /**
* Interface IType: un type de données * Interface IType: un type de données
*/ */
interface IType { interface IType {
function verifix(&$value, bool $throw, ?Result &$result); function canAnalyze(Input $input, $desyKey): bool;
function isAvailable(Input $input, $desyKey): bool;
function isNull($value): bool;
function isValid($value, ?bool &$normalized=null): bool;
function verifix(&$value, ?Result &$result): bool;
function format($value, $format=null); function format($value, $format=null);
} }

View File

@ -10,6 +10,10 @@ class Registry {
"double" => tfloat::class, "dbl" => tfloat::class, "double" => tfloat::class, "dbl" => tfloat::class,
"array" => tarray::class, "array" => tarray::class,
"callable" => tcallable::class, "callable" => tcallable::class,
# types spéciaux
"key" => tkey::class,
"pkey" => tpkey::class,
"content" => tcontent::class,
]; ];
function __construct() { function __construct() {

View File

@ -3,7 +3,7 @@ namespace nur\sery\schema\types;
use nulib\cl; use nulib\cl;
class tarray implements IType { abstract class tarray implements IType {
static function ensure_array(&$array): void { static function ensure_array(&$array): void {
if (!is_array($array)) $array = cl::with($array); if (!is_array($array)) $array = cl::with($array);
} }

View File

@ -1,7 +1,7 @@
<?php <?php
namespace nur\sery\schema\types; namespace nur\sery\schema\types;
class tbool implements IType { abstract class tbool implements IType {
static function ensure_bool(&$bool): void { static function ensure_bool(&$bool): void {
if (!is_bool($bool)) $bool = boolval($bool); if (!is_bool($bool)) $bool = boolval($bool);
} }

View File

@ -3,9 +3,9 @@ namespace nur\sery\schema\types;
use nulib\ValueException; use nulib\ValueException;
class tcallable implements IType { abstract class tcallable implements IType {
static function ensure_callable(&$callable): void { static function ensure_callable(&$callable): void {
if (!is_callable($callable)) throw ValueException::unexpected_type($callable, "callable"); if (!is_callable($callable)) throw ValueException::invalid_type($callable, "callable");
} }
static function ensure_ncallable(&$callable): void { static function ensure_ncallable(&$callable): void {

View File

@ -1,7 +1,7 @@
<?php <?php
namespace nur\sery\schema\types; namespace nur\sery\schema\types;
class tcontent implements IType { abstract class tcontent implements IType {
static function ensure_content(&$content): void { static function ensure_content(&$content): void {
if (!is_string($content) && !is_array($content)) $content = strval($content); if (!is_string($content) && !is_array($content)) $content = strval($content);
} }

View File

@ -1,5 +1,5 @@
<?php <?php
namespace nur\sery\schema\types; namespace nur\sery\schema\types;
class tfloat implements IType { abstract class tfloat implements IType {
} }

View File

@ -1,5 +1,5 @@
<?php <?php
namespace nur\sery\schema\types; namespace nur\sery\schema\types;
class tint implements IType { abstract class tint implements IType {
} }

View File

@ -1,7 +1,7 @@
<?php <?php
namespace nur\sery\schema\types; namespace nur\sery\schema\types;
class tkey implements IType { abstract class tkey implements IType {
static function ensure_key(&$key): void { static function ensure_key(&$key): void {
if (!is_string($key) && !is_int($key)) $key = strval($key); if (!is_string($key) && !is_int($key)) $key = strval($key);
} }

View File

@ -1,7 +1,7 @@
<?php <?php
namespace nur\sery\schema\types; namespace nur\sery\schema\types;
class tpkey implements IType { abstract class tpkey implements IType {
static function ensure_pkey(&$pkey): void { static function ensure_pkey(&$pkey): void {
if (!is_string($pkey) && !is_int($pkey) && !is_array($pkey)) $pkey = strval($pkey); if (!is_string($pkey) && !is_int($pkey) && !is_array($pkey)) $pkey = strval($pkey);
if (is_array($pkey)) { if (is_array($pkey)) {

View File

@ -1,7 +1,7 @@
<?php <?php
namespace nur\sery\schema\types; namespace nur\sery\schema\types;
class tstring implements IType { abstract class tstring implements IType {
static function ensure_string(&$string): void { static function ensure_string(&$string): void {
if (!is_string($string)) $string = strval($string); if (!is_string($string)) $string = strval($string);
} }

View File

@ -1,9 +1,8 @@
<?php <?php
namespace nur\sery\schema\schemas; namespace nur\sery\schema\_scalar;
use nulib\tests\TestCase; use nulib\tests\TestCase;
use nur\sery\schema\SchemaException; use nur\sery\schema\SchemaException;
use nur\sery\schema\_scalar\ScalarSchema;
class ScalarSchemaTest extends TestCase { class ScalarSchemaTest extends TestCase {
const NULL_SCHEMA = [ const NULL_SCHEMA = [
@ -55,11 +54,10 @@ class ScalarSchemaTest extends TestCase {
self::assertSame($nstring, ScalarSchema::normalize([["string", null]])); self::assertSame($nstring, ScalarSchema::normalize([["string", null]]));
$key = self::schema(["type" => ["string", "int"], "nullable" => false]); $key = self::schema(["type" => ["string", "int"], "nullable" => false]);
self::assertSame($key, ScalarSchema::normalize("key")); self::assertSame($key, ScalarSchema::normalize("string|int"));
self::assertSame($key, ScalarSchema::normalize("key|string"));
$nkey = self::schema(["type" => ["string", "int"], "nullable" => true]); $nkey = self::schema(["type" => ["string", "int"], "nullable" => true]);
self::assertSame($nkey, ScalarSchema::normalize("?key")); self::assertSame($nkey, ScalarSchema::normalize("?string|int"));
self::assertSame($nkey, ScalarSchema::normalize("?key|string")); self::assertSame($nkey, ScalarSchema::normalize("string|?int"));
} }
} }

View File

@ -9,15 +9,15 @@ class schemaTest extends TestCase {
function testInt() { function testInt() {
/** @var ScalarValue $intv */ /** @var ScalarValue $intv */
Schema::nv($intv, $int, null, $schema, "int"); Schema::nv($intv, $int, null, $schema, "int");
$f = function($value) use($intv) { $intvSetter = function($value) use($intv) {
return function() use($intv, $value) { return function() use($intv, $value) {
$intv->set($value); $intv->set($value);
}; };
}; };
self::assertException(Exception::class, $f(null)); self::assertException(Exception::class, $intvSetter(null));
self::assertException(Exception::class, $f("")); self::assertException(Exception::class, $intvSetter(""));
self::assertException(Exception::class, $f(" ")); self::assertException(Exception::class, $intvSetter(" "));
$intv->set(12); $intv->set(12);
self::assertSame(12, $intv->get()); self::assertSame(12, $intv->get());
@ -31,17 +31,17 @@ class schemaTest extends TestCase {
$intv->set(" 12 "); $intv->set(" 12 ");
self::assertSame(12, $intv->get()); self::assertSame(12, $intv->get());
self::assertException(Exception::class, $f(true)); self::assertException(Exception::class, $intvSetter(true));
self::assertException(Exception::class, $f(false)); self::assertException(Exception::class, $intvSetter(false));
self::assertException(Exception::class, $f("a")); self::assertException(Exception::class, $intvSetter("a"));
self::assertException(Exception::class, $f([])); self::assertException(Exception::class, $intvSetter([]));
self::assertException(Exception::class, $f(["a"])); self::assertException(Exception::class, $intvSetter(["a"]));
} }
function testNint() { function testNint() {
/** @var ScalarValue $intv */ /** @var ScalarValue $intv */
Schema::nv($intv, $int, null, $schema, "?int"); Schema::nv($intv, $int, null, $schema, "?int");
$f = function($value) use($intv) { $intvSetter = function($value) use($intv) {
return function() use($intv, $value) { return function() use($intv, $value) {
$intv->set($value); $intv->set($value);
}; };
@ -74,11 +74,11 @@ class schemaTest extends TestCase {
$intv->set(" 12 "); $intv->set(" 12 ");
self::assertSame(12, $int); self::assertSame(12, $int);
self::assertException(Exception::class, $f(true)); self::assertException(Exception::class, $intvSetter(true));
self::assertException(Exception::class, $f(false)); self::assertException(Exception::class, $intvSetter(false));
self::assertException(Exception::class, $f("a")); self::assertException(Exception::class, $intvSetter("a"));
self::assertException(Exception::class, $f([])); self::assertException(Exception::class, $intvSetter([]));
self::assertException(Exception::class, $f(["a"])); self::assertException(Exception::class, $intvSetter(["a"]));
} }
function testUnionTypes() { function testUnionTypes() {