modifs.mineures sans commentaires
This commit is contained in:
parent
ff02ffdf4f
commit
c274adb6e6
8
src/php/access/ArrayAccess.php
Normal file
8
src/php/access/ArrayAccess.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\php\access;
|
||||
|
||||
class ArrayAccess extends KeyAccess {
|
||||
const ALLOW_NULL = true;
|
||||
const ALLOW_FALSE = false;
|
||||
const PROTECT_DEST = true;
|
||||
}
|
@ -5,9 +5,7 @@ use nulib\StateException;
|
||||
use nulib\str;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
use ReflectionNamedType;
|
||||
use ReflectionProperty;
|
||||
use stdClass;
|
||||
|
||||
class PropertyAccess extends AbstractAccess {
|
||||
const PROTECT_DEST = true;
|
||||
|
8
src/php/access/ValueAccess.php
Normal file
8
src/php/access/ValueAccess.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\php\access;
|
||||
|
||||
class ValueAccess extends KeyAccess {
|
||||
const ALLOW_NULL = false;
|
||||
const ALLOW_FALSE = true;
|
||||
const PROTECT_DEST = false;
|
||||
}
|
@ -12,7 +12,7 @@ use nur\sery\wip\schema\_scalar\ScalarSchema;
|
||||
use nur\sery\wip\schema\types\IType;
|
||||
use nur\sery\wip\schema\types\tarray;
|
||||
use nur\sery\wip\schema\types\tbool;
|
||||
use nur\sery\wip\schema\types\tcallable;
|
||||
use nur\sery\wip\schema\types\tfunc;
|
||||
use nur\sery\wip\schema\types\tcontent;
|
||||
use nur\sery\wip\schema\types\tpkey;
|
||||
use nur\sery\wip\schema\types\trawstring;
|
||||
@ -50,7 +50,7 @@ abstract class Schema implements ArrayAccess {
|
||||
* l'instance de Schema nouvelle créée
|
||||
* - sinon, prendre $definition comme définition
|
||||
*/
|
||||
static function ns(&$schema, $definition=null, $definitionKey=null, bool $normalize=true): self {
|
||||
static function ns($definition=null, $definitionKey=null, &$schema=null, bool $normalize=true): self {
|
||||
if (is_array($schema)) {
|
||||
$definition = $schema;
|
||||
$schema = null;
|
||||
@ -74,13 +74,13 @@ abstract class Schema implements ArrayAccess {
|
||||
* variable $value (si $valueKey===null) ou $value[$valueKey] si $valueKey
|
||||
* n'est pas null
|
||||
*/
|
||||
static function nw(&$value=null, $valueKey=null, &$schema=null, $definition=null, ?Wrapper &$wrapper=null): Wrapper {
|
||||
static function nw(&$value=null, $valueKey=null, $definition=null, &$schema=null, ?Wrapper &$wrapper=null): Wrapper {
|
||||
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)->getWrapper($value, $valueKey, null, $wrapper);
|
||||
return self::ns($definition, null, $schema)->getWrapper($value, $valueKey, null, $wrapper);
|
||||
}
|
||||
|
||||
protected static function have_nature(array $definition, ?string &$nature=null): bool {
|
||||
@ -187,12 +187,12 @@ abstract class Schema implements ArrayAccess {
|
||||
tbool::ensure_bool($definition["required"]);
|
||||
tbool::ensure_bool($definition["nullable"]);
|
||||
tcontent::ensure_ncontent($definition["desc"]);
|
||||
tcallable::ensure_ncallable($definition["analyzer_func"]);
|
||||
tcallable::ensure_ncallable($definition["extractor_func"]);
|
||||
tcallable::ensure_ncallable($definition["parser_func"]);
|
||||
tcallable::ensure_ncallable($definition["normalizer_func"]);
|
||||
tfunc::ensure_nfunc($definition["analyzer_func"]);
|
||||
tfunc::ensure_nfunc($definition["extractor_func"]);
|
||||
tfunc::ensure_nfunc($definition["parser_func"]);
|
||||
tfunc::ensure_nfunc($definition["normalizer_func"]);
|
||||
tarray::ensure_narray($definition["messages"]);
|
||||
tcallable::ensure_ncallable($definition["formatter_func"]);
|
||||
tfunc::ensure_nfunc($definition["formatter_func"]);
|
||||
tbool::ensure_nbool($definition["computed"]);
|
||||
|
||||
switch ($nature[0] ?? null) {
|
||||
@ -252,11 +252,11 @@ abstract class Schema implements ArrayAccess {
|
||||
case "assoc":
|
||||
foreach ($definition["schema"] as &$keydef) {
|
||||
self::_ensure_schema_instances($keydef);
|
||||
Schema::ns($keydef, null, null, false);
|
||||
Schema::ns(null, null, $keydef, false);
|
||||
}; unset($keydef);
|
||||
break;
|
||||
case "list":
|
||||
Schema::ns($definition["schema"], null, null, false);
|
||||
Schema::ns(null, null, $definition["schema"], false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -280,14 +280,7 @@ abstract class Schema implements ArrayAccess {
|
||||
*/
|
||||
abstract function getKeys(): array;
|
||||
|
||||
abstract function getSchema($key): Schema;
|
||||
|
||||
/** retourner true si le schéma est de nature tableau associatif */
|
||||
function isAssoc(?AssocSchema &$schema=null): bool { return false; }
|
||||
/** retourner true si le schéma est de nature liste */
|
||||
function isList(?ListSchema &$schema=null): bool { return false; }
|
||||
/** retourner true si le schéma est de nature scalaire */
|
||||
function isScalar(?ScalarSchema &$schema=null): bool { return false; }
|
||||
abstract function getSchema($key=false): Schema;
|
||||
|
||||
abstract protected function newWrapper(): Wrapper;
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
schéma qui les génère. ensureKeys($values)
|
||||
* méthode ensureAssoc() transforme les clés séquentielles en clés associatives
|
||||
* l'ordre est `ensureAssoc [--> ensureKeys] [--> orderKeys]`
|
||||
* si false, supprimer la clé du tableau sauf si ensureKeys
|
||||
|
||||
* pour AssocResult, les clés suivantes sont supportées:
|
||||
* false pour la clé courante
|
||||
|
@ -12,10 +12,6 @@ use nur\sery\wip\schema\input\Input;
|
||||
use nur\sery\wip\schema\types\IType;
|
||||
|
||||
abstract class Wrapper implements ArrayAccess, IteratorAggregate {
|
||||
function isAssoc(?AssocWrapper &$wrapper=null): bool { return false; }
|
||||
function isList(?ListWrapper &$wrapper=null): bool { return false; }
|
||||
function isScalar(?ScalarWrapper &$wrapper=null): bool { return false; }
|
||||
|
||||
protected WrapperContext $context;
|
||||
|
||||
/** changer les paramètres de gestion des valeurs */
|
||||
@ -25,7 +21,9 @@ abstract class Wrapper implements ArrayAccess, IteratorAggregate {
|
||||
|
||||
protected function resetContext($resetSelectedKey): void {
|
||||
$context = $this->context;
|
||||
$context->type = null;
|
||||
$type = $context->schema->type;
|
||||
if (is_array($type)) $type = $type[0];
|
||||
$context->type = $type;
|
||||
$context->result->reset();
|
||||
$context->analyzed = false;
|
||||
$context->normalized = false;
|
||||
|
@ -47,6 +47,10 @@ class AssocSchema extends Schema {
|
||||
$this->_definition = $definition;
|
||||
self::_ensure_type($definition);
|
||||
self::_ensure_schema_instances($definition);
|
||||
} else {
|
||||
# ici, $definition contient un schema déjà instancié, mais c'est le mieux
|
||||
# qu'on puisse faire
|
||||
$this->_definition = $definition;
|
||||
}
|
||||
$this->definition = $definition;
|
||||
$keys = [];
|
||||
@ -56,19 +60,14 @@ class AssocSchema extends Schema {
|
||||
$this->keys = $keys;
|
||||
}
|
||||
|
||||
function isAssoc(?AssocSchema &$schema=null): bool {
|
||||
$schema = $this;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected array $keys;
|
||||
|
||||
function getKeys(): array {
|
||||
return $this->keys;
|
||||
}
|
||||
|
||||
function getSchema($key): Schema {
|
||||
if ($key === null) return $this;
|
||||
function getSchema($key=false): Schema {
|
||||
if ($key === null || $key === false) return $this;
|
||||
$schema = $this->definition["schema"][$key] ?? null;
|
||||
if ($schema === null) throw ValueException::invalid_key($key);
|
||||
return $schema;
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\_assoc;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\ref\schema\ref_analyze;
|
||||
use nulib\ValueException;
|
||||
use nur\sery\wip\schema\_scalar\ScalarResult;
|
||||
@ -14,29 +15,28 @@ use nur\sery\wip\schema\WrapperContext;
|
||||
class AssocWrapper extends Wrapper {
|
||||
function __construct(AssocSchema $schema, &$value=null, $valueKey=null, ?array $params=null) {
|
||||
$keys = $schema->getKeys();
|
||||
$keyParams = cl::merge($params, [
|
||||
"throw" => false,
|
||||
]);
|
||||
$keyWrappers = [];
|
||||
foreach ($keys as $key) {
|
||||
$keyWrappers[$key] = $schema->getSchema($key)->getWrapper();
|
||||
$value = null;
|
||||
$keyWrappers[$key] = $schema->getSchema($key)->getWrapper($value, null, $keyParams);
|
||||
}
|
||||
$this->context = $context = new AssocWrapperContext($schema, null, null, $params);
|
||||
$context->arrayWrapper = new ScalarWrapper($schema, $dummy, null, null, $context);
|
||||
$arrayParams = cl::merge($params, [
|
||||
"throw" => false,
|
||||
]);
|
||||
$context->arrayWrapper = new ScalarWrapper($schema, $dummy, null, $arrayParams, $context);
|
||||
$context->keys = $keys;
|
||||
$context->keyWrappers = $keyWrappers;
|
||||
|
||||
# calculer manuellemet throw ici parce que WrapperContext le met à true par
|
||||
# défaut. on veut pouvoir mettre temporairement throw à false si jamais il
|
||||
# n'est pas spécifié par l'utilisateur
|
||||
$throw = $params["throw"] ?? null;
|
||||
# Si $value est null, ne pas lancer d'exception, parce qu'on considère que
|
||||
# c'est une initialisation sans conséquences
|
||||
if ($throw === null && $value !== null) $throw = true;
|
||||
$context->throw = $throw ?? false;
|
||||
$this->reset($value, $valueKey);
|
||||
$context->throw = $throw ?? true;
|
||||
if ($value !== null) {
|
||||
# n'initialiser que si $value n'est pas null
|
||||
$this->reset($value, $valueKey);
|
||||
}
|
||||
}
|
||||
|
||||
function isAssoc(?AssocWrapper &$wrapper=null): bool { $wrapper = $this; return true; }
|
||||
|
||||
/** @var AssocWrapperContext */
|
||||
protected WrapperContext $context;
|
||||
|
||||
@ -70,7 +70,8 @@ class AssocWrapper extends Wrapper {
|
||||
}
|
||||
|
||||
protected function _getWrapper($key): Wrapper {
|
||||
if ($key === null) return $this->context->arrayWrapper;
|
||||
$context = $this->context;
|
||||
if ($key === null) return $context->arrayWrapper;
|
||||
$wrapper = $context->keyWrappers[$key] ?? null;
|
||||
if ($wrapper === null) throw ValueException::invalid_key($key);
|
||||
return $wrapper;
|
||||
@ -93,17 +94,26 @@ class AssocWrapper extends Wrapper {
|
||||
$array = $context->input->get($valueKey);
|
||||
if ($array === null) $context->input->set([], $valueKey);
|
||||
}
|
||||
|
||||
if ($context->ensureAssoc) {
|
||||
$context->input->ensureAssoc($context->schema->getKeys());
|
||||
}
|
||||
|
||||
$what = ScalarWrapper::_analyze($context, $wrapper, $params);
|
||||
/** @var ScalarResult $result */
|
||||
$result = $context->result;
|
||||
if (!$result->valid) return $what;
|
||||
|
||||
foreach ($context->keyWrappers as $keyWrapper) {
|
||||
$keyWrapper->analyze($params);
|
||||
if (!$keyWrapper->isValid()) {
|
||||
#XXX distinguer MISSING, UNAVAILABLE, NULL et !VALID
|
||||
$what = ref_analyze::INVALID;
|
||||
$result->addInvalidMessage($keyWrapper);
|
||||
}
|
||||
}
|
||||
|
||||
#XXX supprimer les clés "missing" ou "unavailable"
|
||||
return $what;
|
||||
}
|
||||
|
||||
@ -112,6 +122,25 @@ class AssocWrapper extends Wrapper {
|
||||
* @param AssocWrapper $wrapper
|
||||
*/
|
||||
static function _normalize(WrapperContext $context, Wrapper $wrapper, ?array $params): bool {
|
||||
$ensureKeys = $context->ensureKeys;
|
||||
$ensureOrder = $context->ensureOrder;
|
||||
if ($ensureKeys || $ensureOrder) {
|
||||
$schema = $context->schema;
|
||||
$keys = $schema->getKeys();
|
||||
if ($ensureKeys) {
|
||||
$defaults = [];
|
||||
foreach ($keys as $key) {
|
||||
$default = $schema->getSchema($key)->default;
|
||||
if ($default === null) {
|
||||
$default = $wrapper->getType($key)->getNullValue();
|
||||
}
|
||||
$defaults[$key] = $default;
|
||||
}
|
||||
}
|
||||
if ($ensureKeys) $context->input->ensureKeys($defaults, $params);
|
||||
if ($ensureOrder) $context->input->ensureOrder($keys, $params);
|
||||
}
|
||||
|
||||
$modified = ScalarWrapper::_normalize($context, $wrapper, $params);
|
||||
foreach ($context->keyWrappers as $keyWrapper) {
|
||||
if ($keyWrapper->normalize($params)) $modified = true;
|
||||
|
@ -9,20 +9,23 @@ use nur\sery\wip\schema\WrapperContext;
|
||||
|
||||
class AssocWrapperContext extends WrapperContext {
|
||||
const DEFAULT_ENSURE_ARRAY = false;
|
||||
const DEFAULT_ENSURE_ASSOC = true;
|
||||
const DEFAULT_ENSURE_KEYS = true;
|
||||
const DEFAULT_ENSURE_ORDER = true;
|
||||
|
||||
function __construct(Schema $schema, ?Input $input, $valueKey, ?array $params) {
|
||||
parent::__construct($schema, $input, $valueKey, $params);
|
||||
public bool $ensureArray;
|
||||
public bool $ensureAssoc;
|
||||
public bool $ensureKeys;
|
||||
public bool $ensureOrder;
|
||||
|
||||
public function resetParams(?array $params): void {
|
||||
parent::resetParams($params);
|
||||
$this->ensureArray = $params["ensure_array"] ?? self::DEFAULT_ENSURE_ARRAY;
|
||||
$this->ensureAssoc = $params["ensure_assoc"] ?? self::DEFAULT_ENSURE_ASSOC;
|
||||
$this->ensureKeys = $params["ensure_keys"] ?? self::DEFAULT_ENSURE_KEYS;
|
||||
$this->ensureOrder = $params["ensure_order"] ?? self::DEFAULT_ENSURE_ORDER;
|
||||
}
|
||||
|
||||
public bool $ensureArray;
|
||||
public bool $ensureKeys;
|
||||
public bool $ensureOrder;
|
||||
|
||||
public ?ScalarWrapper $arrayWrapper = null;
|
||||
|
||||
/** liste des clés valides */
|
||||
|
@ -51,18 +51,13 @@ class ListSchema extends Schema {
|
||||
$this->definition = $definition;
|
||||
}
|
||||
|
||||
function isList(?ListSchema &$schema=null): bool {
|
||||
$schema = $this;
|
||||
return true;
|
||||
}
|
||||
|
||||
const KEYS = [null];
|
||||
|
||||
function getKeys(): array {
|
||||
return self::KEYS;
|
||||
}
|
||||
|
||||
public function getSchema($key): Schema {
|
||||
public function getSchema($key=false): Schema {
|
||||
if ($key !== null) throw ValueException::invalid_key($key);
|
||||
return $this;
|
||||
}
|
||||
|
@ -5,8 +5,6 @@ use nur\sery\wip\schema\Result;
|
||||
use nur\sery\wip\schema\Wrapper;
|
||||
|
||||
abstract/*XXX*/ class ListWrapper extends Wrapper {
|
||||
function isList(?ListWrapper &$wrapper=null): bool { $wrapper = $this; return true; }
|
||||
|
||||
function ensureKeys(): bool {
|
||||
}
|
||||
|
||||
|
@ -56,24 +56,23 @@ class ScalarSchema extends Schema {
|
||||
$this->_definition = $definition;
|
||||
self::_ensure_type($definition);
|
||||
self::_ensure_schema_instances($definition);
|
||||
} else {
|
||||
# ici, $definition contient un schema déjà instancié, mais c'est le mieux
|
||||
# qu'on puisse faire
|
||||
$this->_definition = $definition;
|
||||
}
|
||||
$this->definition = $definition;
|
||||
}
|
||||
|
||||
function isScalar(?ScalarSchema &$schema=null): bool {
|
||||
$schema = $this;
|
||||
return true;
|
||||
}
|
||||
|
||||
const KEYS = [null];
|
||||
|
||||
function getKeys(): array {
|
||||
return self::KEYS;
|
||||
}
|
||||
|
||||
function getSchema($key): Schema {
|
||||
if ($key !== null) throw ValueException::invalid_key($key);
|
||||
return $this;
|
||||
function getSchema($key=false): Schema {
|
||||
if ($key === null || $key === false) return $this;
|
||||
throw ValueException::invalid_key($key);
|
||||
}
|
||||
|
||||
protected function newWrapper(): ScalarWrapper {
|
||||
|
@ -4,8 +4,6 @@ namespace nur\sery\wip\schema\_scalar;
|
||||
use nulib\php\func;
|
||||
use nulib\ref\schema\ref_analyze;
|
||||
use nulib\ValueException;
|
||||
use nur\sery\wip\schema\_assoc\AssocWrapper;
|
||||
use nur\sery\wip\schema\_assoc\AssocWrapperContext;
|
||||
use nur\sery\wip\schema\Schema;
|
||||
use nur\sery\wip\schema\types;
|
||||
use nur\sery\wip\schema\types\IType;
|
||||
@ -26,20 +24,15 @@ class ScalarWrapper extends Wrapper {
|
||||
$context->result = new ScalarResult();
|
||||
$this->context = $context;
|
||||
|
||||
# calculer manuellemet throw ici parce que WrapperContext le met à true par
|
||||
# défaut. on veut pouvoir mettre temporairement throw à false si jamais il
|
||||
# n'est pas spécifié par l'utilisateur
|
||||
$throw = $params["throw"] ?? null;
|
||||
# Si $value est null, ne pas lancer d'exception, parce qu'on considère que
|
||||
# c'est une initialisation sans conséquences
|
||||
if ($throw === null && $value !== null) $throw = true;
|
||||
$context->throw = $throw ?? false;
|
||||
$this->reset($value, $valueKey);
|
||||
$context->throw = $throw ?? true;
|
||||
if ($value !== null) {
|
||||
# n'initialiser que si $value n'est pas null
|
||||
$this->reset($value, $valueKey);
|
||||
} else {
|
||||
# il faut au moins que le type soit disponible
|
||||
$this->resetContext(false);
|
||||
}
|
||||
}
|
||||
|
||||
function isScalar(?ScalarWrapper &$wrapper=null): bool { $wrapper = $this; return true; }
|
||||
|
||||
protected WrapperContext $context;
|
||||
|
||||
function getKeys(): array {
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace nur\sery\wip\schema\input;
|
||||
|
||||
use nulib\ref\schema\ref_input;
|
||||
use nulib\StateException;
|
||||
use nur\sery\wip\php\access\IAccess;
|
||||
use nur\sery\wip\php\access\KeyAccess;
|
||||
@ -14,25 +15,23 @@ use nur\sery\wip\php\access\PropertyAccess;
|
||||
class Input {
|
||||
const ALLOW_EMPTY = true;
|
||||
|
||||
const ACCESS_AUTO = 0, ACCESS_KEY = 1, ACCESS_PROPERTY = 2;
|
||||
|
||||
private static function unexpected_access_type(): StateException {
|
||||
return StateException::unexpected_state("access_type");
|
||||
}
|
||||
|
||||
function __construct(&$dest=null, ?array $params=null) {
|
||||
$accessType = $params["access_type"] ?? self::ACCESS_AUTO;
|
||||
if ($accessType === self::ACCESS_AUTO) {
|
||||
$accessType = is_object($dest)? self::ACCESS_PROPERTY: self::ACCESS_KEY;
|
||||
$accessType = $params["access_type"] ?? ref_input::ACCESS_AUTO;
|
||||
if ($accessType === ref_input::ACCESS_AUTO) {
|
||||
$accessType = is_object($dest)? ref_input::ACCESS_PROPERTY : ref_input::ACCESS_KEY;
|
||||
}
|
||||
|
||||
$allowEmpty = $params["allow_empty"] ?? static::ALLOW_EMPTY;
|
||||
if ($accessType == self::ACCESS_PROPERTY) {
|
||||
if ($accessType == ref_input::ACCESS_PROPERTY) {
|
||||
$this->access = new PropertyAccess($dest, null, [
|
||||
"allow_empty" => $allowEmpty,
|
||||
"allow_null" => true,
|
||||
]);
|
||||
} elseif ($accessType == self::ACCESS_KEY) {
|
||||
} elseif ($accessType == ref_input::ACCESS_KEY) {
|
||||
$this->access = new KeyAccess($dest, null, [
|
||||
"allow_empty" => $allowEmpty,
|
||||
"allow_null" => true,
|
||||
@ -72,4 +71,16 @@ class Input {
|
||||
$input->access = $this->access->addKey($key);
|
||||
return $input;
|
||||
}
|
||||
|
||||
function ensureAssoc(array $keys, ?array $params=null): void {
|
||||
$this->access->ensureAssoc($keys, $params);
|
||||
}
|
||||
|
||||
function ensureKeys(array $defaults, ?array $params=null): void {
|
||||
$this->access->ensureKeys($defaults, $params);
|
||||
}
|
||||
|
||||
function ensureOrder(array $keys, ?array $params=null): void {
|
||||
$this->access->ensureOrder($keys, $params);
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use nur\sery\wip\schema\types\IType;
|
||||
use nur\sery\wip\schema\types\Registry;
|
||||
use nur\sery\wip\schema\types\tarray;
|
||||
use nur\sery\wip\schema\types\tbool;
|
||||
use nur\sery\wip\schema\types\tcallable;
|
||||
use nur\sery\wip\schema\types\tfunc;
|
||||
use nur\sery\wip\schema\types\tcontent;
|
||||
use nur\sery\wip\schema\types\tfloat;
|
||||
use nur\sery\wip\schema\types\tint;
|
||||
@ -47,7 +47,7 @@ class types {
|
||||
static function int(bool $nullable=true): tint { return self::get($nullable, "int"); }
|
||||
static function float(bool $nullable=true): tfloat { return self::get($nullable, "float"); }
|
||||
static function array(bool $nullable=true): tarray { return self::get($nullable, "array"); }
|
||||
static function callable(bool $nullable=true): tcallable { return self::get($nullable, "callable"); }
|
||||
static function callable(bool $nullable=true): tfunc { return self::get($nullable, "callable"); }
|
||||
static function raw(bool $nullable=true): traw { return self::get($nullable, "raw"); }
|
||||
static function mixed(bool $nullable=true): tmixed { return self::get($nullable, "mixed"); }
|
||||
static function key(bool $nullable=true): tkey { return self::get($nullable, "key"); }
|
||||
|
@ -47,6 +47,9 @@ interface IType {
|
||||
*/
|
||||
function getPhpType(bool $allowNullable=true): ?string;
|
||||
|
||||
/** obtenir la valeur "nulle" pour les objets de ce type */
|
||||
function getNullValue();
|
||||
|
||||
/**
|
||||
* indiquer si c'est le type d'une valeur qui ne peut prendre que 2 états: une
|
||||
* "vraie" et une "fausse"
|
||||
|
@ -12,10 +12,9 @@ class Registry {
|
||||
"text" => ttext::class,
|
||||
"bool" => tbool::class, "boolean" => tbool::class,
|
||||
"int" => tint::class, "integer" => tint::class,
|
||||
"float" => tfloat::class, "flt" => tfloat::class,
|
||||
"double" => tfloat::class, "dbl" => tfloat::class,
|
||||
"float" => tfloat::class, "flt" => tfloat::class, "double" => tfloat::class, "dbl" => tfloat::class,
|
||||
"array" => tarray::class,
|
||||
"callable" => tcallable::class,
|
||||
"func" => tfunc::class, "function" => tfunc::class, "callable" => tfunc::class,
|
||||
# types spéciaux
|
||||
"raw" => traw::class,
|
||||
"mixed" => tmixed::class,
|
||||
|
@ -35,6 +35,10 @@ class tarray extends _tstring {
|
||||
return "array";
|
||||
}
|
||||
|
||||
function getNullValue() {
|
||||
return $this->nullable? null: [];
|
||||
}
|
||||
|
||||
function isValid($value, ?bool &$normalized=null): bool {
|
||||
$normalized = is_array($value);
|
||||
return $normalized || is_scalar($value);
|
||||
|
@ -76,6 +76,10 @@ class tbool extends _tformatable {
|
||||
return [false, true, null];
|
||||
}
|
||||
|
||||
function getNullValue() {
|
||||
return $this->nullable? null: false;
|
||||
}
|
||||
|
||||
function isAvailable(Input $input, $valueKey): bool {
|
||||
return $input->isAvailable($valueKey);
|
||||
}
|
||||
|
@ -23,6 +23,10 @@ abstract class tcontent extends _tunion {
|
||||
return "string|array";
|
||||
}
|
||||
|
||||
function getNullValue() {
|
||||
return $this->nullable? null: [];
|
||||
}
|
||||
|
||||
function isValid($value, ?bool &$normalized=null): bool {
|
||||
$normalized = is_string($value) || is_array($value);
|
||||
return $normalized || is_scalar($value);
|
||||
|
@ -24,6 +24,10 @@ class tfloat extends _tformatable {
|
||||
return "float";
|
||||
}
|
||||
|
||||
function getNullValue() {
|
||||
return $this->nullable? null: 0.0;
|
||||
}
|
||||
|
||||
function isValid($value, ?bool &$normalized=null): bool {
|
||||
$normalized = is_float($value);
|
||||
return is_scalar($value);
|
||||
|
@ -9,23 +9,27 @@ use nur\sery\wip\schema\_scalar\ScalarSchema;
|
||||
use nur\sery\wip\schema\Result;
|
||||
use nur\sery\wip\schema\Schema;
|
||||
|
||||
class tcallable extends _tsimple {
|
||||
const NAME = "callable";
|
||||
class tfunc extends _tsimple {
|
||||
const NAME = "func";
|
||||
|
||||
const ALIASES = ["func", "function"];
|
||||
const ALIASES = ["function", "callable"];
|
||||
|
||||
static function ensure_callable(&$callable): void {
|
||||
$callable = func::ensure($callable);
|
||||
static function ensure_func(&$func): void {
|
||||
$func = func::ensure($func);
|
||||
}
|
||||
|
||||
static function ensure_ncallable(&$callable): void {
|
||||
if ($callable !== null) self::ensure_callable($callable);
|
||||
static function ensure_nfunc(&$func): void {
|
||||
if ($func !== null) self::ensure_func($func);
|
||||
}
|
||||
|
||||
function getClass(): string {
|
||||
return func::class;
|
||||
}
|
||||
|
||||
function getNullValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
function isValid($value, ?bool &$normalized=null): bool {
|
||||
$normalized = $value instanceof func;
|
||||
return func::check($value);
|
@ -20,6 +20,10 @@ class tgeneric extends _tsimple {
|
||||
return $this->class;
|
||||
}
|
||||
|
||||
function getNullValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
function isAvailable(Input $input, $valueKey): bool {
|
||||
return $input->isAvailable($valueKey);
|
||||
}
|
||||
|
@ -26,6 +26,10 @@ class tint extends _tformatable {
|
||||
return "int";
|
||||
}
|
||||
|
||||
function getNullValue() {
|
||||
return $this->nullable? null: 0;
|
||||
}
|
||||
|
||||
function isValid($value, ?bool &$normalized=null): bool {
|
||||
$normalized = is_int($value);
|
||||
return is_scalar($value);
|
||||
|
@ -23,6 +23,10 @@ class tkey extends _tunion {
|
||||
return "string|int";
|
||||
}
|
||||
|
||||
function getNullValue() {
|
||||
return $this->nullable? null: "";
|
||||
}
|
||||
|
||||
function isValid($value, ?bool &$normalized=null): bool {
|
||||
$normalized = is_string($value) || is_int($value);
|
||||
return $normalized || is_scalar($value);
|
||||
|
@ -14,6 +14,10 @@ class tmixed extends _tsimple {
|
||||
return "mixed";
|
||||
}
|
||||
|
||||
function getNullValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
function isAvailable(Input $input, $valueKey): bool {
|
||||
return $input->isAvailable($valueKey);
|
||||
}
|
||||
|
@ -28,6 +28,10 @@ class tpkey extends _tunion {
|
||||
return "string|int|array";
|
||||
}
|
||||
|
||||
function getNullValue() {
|
||||
return $this->nullable? null: [];
|
||||
}
|
||||
|
||||
function isValid($value, ?bool &$normalized=null): bool {
|
||||
$normalized = is_string($value) || is_int($value) || is_array($value);
|
||||
return $normalized || is_scalar($value);
|
||||
|
@ -24,6 +24,10 @@ class trawstring extends _tstring {
|
||||
return "string";
|
||||
}
|
||||
|
||||
function getNullValue() {
|
||||
return $this->nullable? null: "";
|
||||
}
|
||||
|
||||
function isNull($value): bool {
|
||||
return $value === null;
|
||||
}
|
||||
|
@ -3,7 +3,9 @@ namespace nur\sery\wip\schema\_assoc;
|
||||
|
||||
use nulib\ext\yaml;
|
||||
use nulib\tests\TestCase;
|
||||
use nulib\ValueException;
|
||||
use nur\sery\wip\schema\_scalar\ScalarSchemaTest;
|
||||
use nur\sery\wip\schema\Schema;
|
||||
|
||||
class AssocSchemaTest extends TestCase {
|
||||
const NULL_SCHEMA = [
|
||||
@ -114,6 +116,7 @@ class AssocSchemaTest extends TestCase {
|
||||
"c" => false,
|
||||
], $array);
|
||||
|
||||
###########################################################################
|
||||
$schema = new AssocSchema([
|
||||
"a" => "string",
|
||||
"b" => "int",
|
||||
@ -124,15 +127,15 @@ class AssocSchemaTest extends TestCase {
|
||||
$schema->getWrapper($array);
|
||||
self::assertSame([
|
||||
"a" => "string",
|
||||
"b" => false,
|
||||
"c" => null,
|
||||
"b" => 0,
|
||||
"c" => false,
|
||||
], $array);
|
||||
|
||||
$array = ["c" => false, "a" => " string "];
|
||||
$schema->getWrapper($array);
|
||||
self::assertSame([
|
||||
"a" => "string",
|
||||
"b" => false,
|
||||
"b" => 0,
|
||||
"c" => false,
|
||||
], $array);
|
||||
|
||||
@ -140,8 +143,8 @@ class AssocSchemaTest extends TestCase {
|
||||
$schema->getWrapper($array, null, ["ensure_order" => false]);
|
||||
self::assertSame([
|
||||
"a" => "string",
|
||||
"b" => false,
|
||||
"c" => null,
|
||||
"b" => 0,
|
||||
"c" => false,
|
||||
], $array);
|
||||
|
||||
$array = ["c" => false, "a" => " string "];
|
||||
@ -149,7 +152,7 @@ class AssocSchemaTest extends TestCase {
|
||||
self::assertSame([
|
||||
"c" => false,
|
||||
"a" => "string",
|
||||
"b" => false,
|
||||
"b" => 0,
|
||||
], $array);
|
||||
|
||||
$array = ["a" => " string "];
|
||||
@ -165,4 +168,185 @@ class AssocSchemaTest extends TestCase {
|
||||
"c" => false,
|
||||
], $array);
|
||||
}
|
||||
|
||||
const STRING_SCHEMA = [
|
||||
"s" => "string",
|
||||
"f" => "string",
|
||||
"m" => "string",
|
||||
];
|
||||
|
||||
const NSTRING_SCHEMA = [
|
||||
"s" => "?string",
|
||||
"f" => "?string",
|
||||
"m" => "?string",
|
||||
];
|
||||
|
||||
const RSTRING_SCHEMA = [
|
||||
"s" => ["string", "required" => true],
|
||||
"f" => ["string", "required" => true],
|
||||
"m" => ["string", "required" => true],
|
||||
];
|
||||
|
||||
const RNSTRING_SCHEMA = [
|
||||
"s" => ["?string", "required" => true],
|
||||
"f" => ["?string", "required" => true],
|
||||
"m" => ["?string", "required" => true],
|
||||
];
|
||||
|
||||
const STRINGS = ["s" => "string", "f" => false];
|
||||
const NSTRINGS = ["s" => null, "f" => null];
|
||||
|
||||
function testString() {
|
||||
/** @var AssocSchema $schema */
|
||||
$schema = Schema::ns(self::STRING_SCHEMA);
|
||||
|
||||
$array = self::STRINGS;
|
||||
$wrapper = $schema->getWrapper($array, null, ["throw" => false]);
|
||||
self::assertSame(self::STRINGS, $array);
|
||||
$result = $wrapper->getResult("s");
|
||||
self::assertTrue($result->normalized);
|
||||
$result = $wrapper->getResult("f");
|
||||
self::assertTrue($result->present);
|
||||
self::assertFalse($result->available);
|
||||
$result = $wrapper->getResult("m");
|
||||
self::assertFalse($result->present);
|
||||
|
||||
self::assertNotException(function() use ($schema) {
|
||||
$array = self::STRINGS;
|
||||
$schema->getWrapper($array);
|
||||
});
|
||||
|
||||
$array = self::NSTRINGS;
|
||||
$wrapper = $schema->getWrapper($array, null, ["throw" => false]);
|
||||
self::assertSame(self::NSTRINGS, $array);
|
||||
$result = $wrapper->getResult("s");
|
||||
self::assertFalse($result->valid);
|
||||
self::assertSame("null", $result->messageKey);
|
||||
$result = $wrapper->getResult("f");
|
||||
self::assertFalse($result->valid);
|
||||
self::assertSame("null", $result->messageKey);
|
||||
$result = $wrapper->getResult("m");
|
||||
self::assertFalse($result->present);
|
||||
|
||||
self::assertException(ValueException::class, function() use ($schema) {
|
||||
$array = self::NSTRINGS;
|
||||
$schema->getWrapper($array);
|
||||
});
|
||||
}
|
||||
|
||||
function testNstring() {
|
||||
/** @var AssocSchema $schema */
|
||||
$schema = Schema::ns(self::NSTRING_SCHEMA);
|
||||
|
||||
$array = self::STRINGS;
|
||||
$wrapper = $schema->getWrapper($array, null, ["throw" => false]);
|
||||
self::assertSame(self::STRINGS, $array);
|
||||
$result = $wrapper->getResult("s");
|
||||
self::assertTrue($result->normalized);
|
||||
$result = $wrapper->getResult("f");
|
||||
self::assertTrue($result->present);
|
||||
self::assertFalse($result->available);
|
||||
$result = $wrapper->getResult("m");
|
||||
self::assertFalse($result->present);
|
||||
|
||||
self::assertNotException(function() use ($schema) {
|
||||
$array = self::STRINGS;
|
||||
$schema->getWrapper($array);
|
||||
});
|
||||
|
||||
$array = self::NSTRINGS;
|
||||
$wrapper = $schema->getWrapper($array, null, ["throw" => false]);
|
||||
self::assertSame(self::NSTRINGS, $array);
|
||||
$result = $wrapper->getResult("s");
|
||||
self::assertTrue($result->normalized);
|
||||
$result = $wrapper->getResult("f");
|
||||
self::assertTrue($result->normalized);
|
||||
$result = $wrapper->getResult("m");
|
||||
self::assertFalse($result->present);
|
||||
|
||||
self::assertNotException(function() use ($schema) {
|
||||
$array = self::NSTRINGS;
|
||||
$schema->getWrapper($array);
|
||||
});
|
||||
}
|
||||
|
||||
function testRstring() {
|
||||
/** @var AssocSchema $schema */
|
||||
$schema = Schema::ns(self::RSTRING_SCHEMA);
|
||||
|
||||
$array = self::STRINGS;
|
||||
$wrapper = $schema->getWrapper($array, null, ["throw" => false]);
|
||||
self::assertSame(self::STRINGS, $array);
|
||||
$result = $wrapper->getResult("s");
|
||||
self::assertTrue($result->normalized);
|
||||
$result = $wrapper->getResult("f");
|
||||
self::assertTrue($result->present);
|
||||
self::assertFalse($result->available);
|
||||
self::assertSame("unavailable", $result->messageKey);
|
||||
$result = $wrapper->getResult("m");
|
||||
self::assertFalse($result->present);
|
||||
self::assertSame("missing", $result->messageKey);
|
||||
|
||||
self::assertException(ValueException::class, function() use ($schema) {
|
||||
$array = self::STRINGS;
|
||||
$schema->getWrapper($array);
|
||||
});
|
||||
|
||||
$array = self::NSTRINGS;
|
||||
$wrapper = $schema->getWrapper($array, null, ["throw" => false]);
|
||||
self::assertSame(self::NSTRINGS, $array);
|
||||
$result = $wrapper->getResult("s");
|
||||
self::assertFalse($result->valid);
|
||||
self::assertSame("null", $result->messageKey);
|
||||
$result = $wrapper->getResult("f");
|
||||
self::assertFalse($result->valid);
|
||||
self::assertSame("null", $result->messageKey);
|
||||
$result = $wrapper->getResult("m");
|
||||
self::assertFalse($result->present);
|
||||
self::assertSame("missing", $result->messageKey);
|
||||
|
||||
self::assertException(ValueException::class, function() use ($schema) {
|
||||
$array = self::NSTRINGS;
|
||||
$schema->getWrapper($array);
|
||||
});
|
||||
}
|
||||
|
||||
function testRnstring() {
|
||||
/** @var AssocSchema $schema */
|
||||
$schema = Schema::ns(self::RNSTRING_SCHEMA);
|
||||
|
||||
$array = self::STRINGS;
|
||||
$wrapper = $schema->getWrapper($array, null, ["throw" => false]);
|
||||
self::assertSame(self::STRINGS, $array);
|
||||
$result = $wrapper->getResult("s");
|
||||
self::assertTrue($result->normalized);
|
||||
$result = $wrapper->getResult("f");
|
||||
self::assertTrue($result->present);
|
||||
self::assertFalse($result->available);
|
||||
self::assertSame("unavailable", $result->messageKey);
|
||||
$result = $wrapper->getResult("m");
|
||||
self::assertFalse($result->present);
|
||||
self::assertSame("missing", $result->messageKey);
|
||||
|
||||
self::assertException(ValueException::class, function() use ($schema) {
|
||||
$array = self::STRINGS;
|
||||
$schema->getWrapper($array);
|
||||
});
|
||||
|
||||
$array = self::NSTRINGS;
|
||||
$wrapper = $schema->getWrapper($array, null, ["throw" => false]);
|
||||
self::assertSame(self::NSTRINGS, $array);
|
||||
$result = $wrapper->getResult("s");
|
||||
self::assertTrue($result->normalized);
|
||||
$result = $wrapper->getResult("f");
|
||||
self::assertTrue($result->normalized);
|
||||
$result = $wrapper->getResult("m");
|
||||
self::assertFalse($result->present);
|
||||
self::assertSame("missing", $result->messageKey);
|
||||
|
||||
self::assertException(ValueException::class, function() use ($schema) {
|
||||
$array = self::NSTRINGS;
|
||||
$schema->getWrapper($array);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ class boolTest extends TestCase {
|
||||
|
||||
function testBool() {
|
||||
/** @var ScalarWrapper $wrapper */
|
||||
Schema::nw($value, null, $schema, "bool", $wrapper);
|
||||
Schema::nw($value, null, "bool", $schema, $wrapper);
|
||||
$wrapperSetter = function($value) use($wrapper) {
|
||||
return function() use($wrapper, $value) {
|
||||
$wrapper->set($value);
|
||||
@ -75,7 +75,7 @@ class boolTest extends TestCase {
|
||||
|
||||
function testNbool() {
|
||||
/** @var ScalarWrapper $wrapper */
|
||||
Schema::nw($value, null, $schema, "?bool", $wrapper);
|
||||
Schema::nw($value, null, "?bool", $schema, $wrapper);
|
||||
$wrapperSetter = function($value) use($wrapper) {
|
||||
return function() use($wrapper, $value) {
|
||||
$wrapper->set($value);
|
||||
|
@ -33,7 +33,7 @@ class floatTest extends TestCase {
|
||||
|
||||
function testFloat() {
|
||||
/** @var ScalarWrapper $wrapper */
|
||||
Schema::nw($value, null, $schema, "float", $wrapper);
|
||||
Schema::nw($value, null, "float", $schema, $wrapper);
|
||||
$wrapperSetter = function($value) use($wrapper) {
|
||||
return function() use($wrapper, $value) {
|
||||
$wrapper->set($value);
|
||||
@ -53,10 +53,10 @@ class floatTest extends TestCase {
|
||||
|
||||
function testRequiredFloat() {
|
||||
/** @var ScalarWrapper $wrapper */
|
||||
Schema::nw($value, null, $schema, [
|
||||
Schema::nw($value, null, [
|
||||
"float", null,
|
||||
"required" => true,
|
||||
], $wrapper);
|
||||
], $schema, $wrapper);
|
||||
$wrapperSetter = function($value) use($wrapper) {
|
||||
return function() use($wrapper, $value) {
|
||||
$wrapper->set($value);
|
||||
@ -75,7 +75,7 @@ class floatTest extends TestCase {
|
||||
|
||||
function testNfloat() {
|
||||
/** @var ScalarWrapper $wrapper */
|
||||
Schema::nw($value, null, $schema, "?float", $wrapper);
|
||||
Schema::nw($value, null, "?float", $schema, $wrapper);
|
||||
$wrapperSetter = function($value) use($wrapper) {
|
||||
return function() use($wrapper, $value) {
|
||||
$wrapper->set($value);
|
||||
@ -106,10 +106,10 @@ class floatTest extends TestCase {
|
||||
|
||||
function testRequiredNfloat() {
|
||||
/** @var ScalarWrapper $wrapper */
|
||||
Schema::nw($value, null, $schema, [
|
||||
Schema::nw($value, null, [
|
||||
"?float", null,
|
||||
"required" => true,
|
||||
], $wrapper);
|
||||
], $schema, $wrapper);
|
||||
$wrapperSetter = function($value) use($wrapper) {
|
||||
return function() use($wrapper, $value) {
|
||||
$wrapper->set($value);
|
||||
|
@ -33,7 +33,7 @@ class intTest extends TestCase {
|
||||
|
||||
function testInt() {
|
||||
/** @var ScalarWrapper $wrapper */
|
||||
Schema::nw($value, null, $schema, "int", $wrapper);
|
||||
Schema::nw($value, null, "int", $schema, $wrapper);
|
||||
$wrapperSetter = function($value) use($wrapper) {
|
||||
return function() use($wrapper, $value) {
|
||||
$wrapper->set($value);
|
||||
@ -53,10 +53,10 @@ class intTest extends TestCase {
|
||||
|
||||
function testRequiredInt() {
|
||||
/** @var ScalarWrapper $wrapper */
|
||||
Schema::nw($value, null, $schema, [
|
||||
Schema::nw($value, null, [
|
||||
"int", null,
|
||||
"required" => true,
|
||||
], $wrapper);
|
||||
], $schema, $wrapper);
|
||||
$wrapperSetter = function($value) use($wrapper) {
|
||||
return function() use($wrapper, $value) {
|
||||
$wrapper->set($value);
|
||||
@ -75,7 +75,7 @@ class intTest extends TestCase {
|
||||
|
||||
function testNint() {
|
||||
/** @var ScalarWrapper $wrapper */
|
||||
Schema::nw($value, null, $schema, "?int", $wrapper);
|
||||
Schema::nw($value, null, "?int", $schema, $wrapper);
|
||||
$wrapperSetter = function($value) use($wrapper) {
|
||||
return function() use($wrapper, $value) {
|
||||
$wrapper->set($value);
|
||||
@ -106,10 +106,10 @@ class intTest extends TestCase {
|
||||
|
||||
function testRequiredNint() {
|
||||
/** @var ScalarWrapper $wrapper */
|
||||
Schema::nw($value, null, $schema, [
|
||||
Schema::nw($value, null, [
|
||||
"?int", null,
|
||||
"required" => true,
|
||||
], $wrapper);
|
||||
], $schema, $wrapper);
|
||||
$wrapperSetter = function($value) use($wrapper) {
|
||||
return function() use($wrapper, $value) {
|
||||
$wrapper->set($value);
|
||||
|
@ -41,7 +41,7 @@ class strTest extends TestCase {
|
||||
|
||||
function testStr() {
|
||||
/** @var ScalarWrapper $wrapper */
|
||||
Schema::nw($value, null, $schema, "string", $wrapper);
|
||||
Schema::nw($value, null, "string", $schema, $wrapper);
|
||||
$wrapperSetter = function($value) use($wrapper) {
|
||||
return function() use($wrapper, $value) {
|
||||
$wrapper->set($value);
|
||||
@ -59,10 +59,10 @@ class strTest extends TestCase {
|
||||
|
||||
function testRequiredStr() {
|
||||
/** @var ScalarWrapper $wrapper */
|
||||
Schema::nw($value, null, $schema, [
|
||||
Schema::nw($value, null, [
|
||||
"string", null,
|
||||
"required" => true,
|
||||
], $wrapper);
|
||||
], $schema, $wrapper);
|
||||
$wrapperSetter = function($value) use($wrapper) {
|
||||
return function() use($wrapper, $value) {
|
||||
$wrapper->set($value);
|
||||
@ -79,7 +79,7 @@ class strTest extends TestCase {
|
||||
|
||||
function testNstr() {
|
||||
/** @var ScalarWrapper $wrapper */
|
||||
Schema::nw($value, null, $schema, "?string", $wrapper);
|
||||
Schema::nw($value, null, "?string", $schema, $wrapper);
|
||||
$wrapperSetter = function($value) use($wrapper) {
|
||||
return function() use($wrapper, $value) {
|
||||
$wrapper->set($value);
|
||||
@ -100,10 +100,10 @@ class strTest extends TestCase {
|
||||
|
||||
function testRequiredNstr() {
|
||||
/** @var ScalarWrapper $wrapper */
|
||||
Schema::nw($value, null, $schema, [
|
||||
Schema::nw($value, null, [
|
||||
"?string", null,
|
||||
"required" => true,
|
||||
], $wrapper);
|
||||
], $schema, $wrapper);
|
||||
$wrapperSetter = function($value) use($wrapper) {
|
||||
return function() use($wrapper, $value) {
|
||||
$wrapper->set($value);
|
||||
|
@ -11,7 +11,7 @@ class unionTest extends TestCase {
|
||||
|
||||
# string puis int
|
||||
/** @var ScalarWrapper $siw */
|
||||
Schema::nw($si, null, $sis, "string|int", $siw);
|
||||
Schema::nw($si, null, "string|int", $sis, $siw);
|
||||
|
||||
$siw->set("12");
|
||||
self::assertSame("12", $si);
|
||||
@ -20,7 +20,7 @@ class unionTest extends TestCase {
|
||||
|
||||
# int puis string
|
||||
/** @var ScalarWrapper $isw */
|
||||
Schema::nw($is, null, $iss, "int|string", $isw);
|
||||
Schema::nw($is, null, "int|string", $iss, $isw);
|
||||
|
||||
$isw->set("12");
|
||||
self::assertSame("12", $is);
|
||||
|
Loading…
Reference in New Issue
Block a user