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 LogicException;
use nulib\AccessException;
use nulib\cl;
use nur\sery\schema\_assoc\AssocSchema;
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
* 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 (AssocSchema::isa_definition($definition)) {
$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
* 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) {
# bien que techniquement, $definition peut être null (il s'agit alors du
# schéma d'un scalaire quelconque), on ne l'autorise pas ici
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];
}
function offsetSet($offset, $value): void {
throw new LogicException("read-only");
throw AccessException::read_only(null, $offset);
}
function offsetUnset($offset): void {
throw new LogicException("read-only");
throw AccessException::read_only(null, $offset);
}
const _PROPERTY_PKEYS = [];

View File

@ -5,7 +5,7 @@
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 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; }
/** 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
@ -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 */
abstract function isPresent(): bool;
/** retourner le type associé à la valeur */
abstract function getType(): IType;
/** retourner true si la valeur est disponible */
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 */
abstract function isValid(): bool;
/** obtenir la valeur */
abstract function get($default=null);
/** retourner true si la valeur est dans sa forme normalisée */
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 */
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 $available si la valeur existe, est-elle disponible?
* @property bool $null si la valeur existe, est-elle nulle?
* @property bool $valid si la valeur existe, est-elle valide?
* @property bool $null si la valeur est disponible, est-elle nulle?
* @property bool $valid si la valeur est disponible, 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
@ -105,4 +105,36 @@ class ScalarResult extends Result {
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 nur\sery\schema\input\Input;
use nur\sery\schema\ref\ref_analyze;
use nur\sery\schema\Result;
use nur\sery\schema\types;
use nur\sery\schema\types\IType;
use nur\sery\schema\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->defaultVerifix = $defaultVerifix;
$this->result = new ScalarResult();
$this->reset($dest, $key, $verifix);
$this->reset($dest, $destKey);
}
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 */
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;
/** @var IType */
/** @var bool */
protected $defaultVerifix;
/** @var IType type de la valeur après analyse */
protected $type;
/** @var ?ScalarResult résultat de l'analyse de la valeur */
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;
else $input = new Input($dest);
$this->input = $input;
$this->destKey = $destKey;
$this->result = null;
#XXX résoudre les types ici?
$this->type = null;
$this->_analyze();
if ($verifix === null) $verifix = $this->defaultVerifix;
if ($verifix) $this->verifix();
else $this->_analyze();
return $this;
}
@ -59,42 +65,28 @@ class ScalarValue extends Value {
$destKey = $this->destKey;
$result = $this->result;
$result->reset();
#XXX résoudre le type
if (!$input->isAvailable()) return $result->setUnavailable($schema);
if (!$input->isPresent($destKey)) return $result->setMissing($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);
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();
}
function isPresent(): bool {
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 {
if (is_string($value)) return ref_analyze::STRING;
else return $result->setInvalid($schema);
}
/**
@ -102,26 +94,61 @@ class ScalarValue extends Value {
*
* 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();
$key = $this->destKey;
if ($key === null) $modified = $type->verifix($this->input, $throw, $result);
else $modified = $type->verifix($this->input[$key], $throw, $result);
$this->result = $result;
$destKey = $this->destKey;
$value = $this->input->get($destKey);
$modified = $type->verifix($value, $this->result);
if ($this->result->valid) $this->input->set($value, $destKey);
else $this->result->throw($throw);
return $modified;
}
function parse($value, bool $throw=true, ?Result &$result=null) {
$this->getType()->verifix($value, $throw, $result);
$this->set($value);
$this->result = $result;
return $value;
function getResult(): Result {
return $this->result;
}
function format(?string $format=null): string {
$type = $this->getType();
$key = $this->destKey;
if ($key === null) return $type->format($this->input, $format);
else return $type->format($this->input[$key], $format);
function isPresent(): bool {
return $this->result->present;
}
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;
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 {
const ALIASES = [
"key" => "string|int",
"pkey" => "string|int|array",
"content" => "string|array",
"boolean" => "bool",
"integer" => "int",
"flt" => "float", "double" => "float", "dbl" => "float",
];
}

View File

@ -1,13 +1,22 @@
<?php
namespace nur\sery\schema\types;
use nur\sery\schema\input\Input;
use nur\sery\schema\Result;
/**
* Interface IType: un type de données
*/
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);
}

View File

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

View File

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

View File

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

View File

@ -3,9 +3,9 @@ namespace nur\sery\schema\types;
use nulib\ValueException;
class tcallable implements IType {
abstract class tcallable implements IType {
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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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