<pman>Intégration de la branche dev74

This commit is contained in:
Jephté Clain 2025-03-23 08:20:26 +04:00
commit 9d7dbd76d1
46 changed files with 1596 additions and 847 deletions

View File

@ -8,6 +8,18 @@ use nulib\cl;
* de {@link IAccess}
*/
abstract class AbstractAccess implements IAccess {
const ALLOW_EMPTY = true;
function __construct(?array $params=null) {
$this->allowEmpty = $params["allow_empty"] ?? static::ALLOW_EMPTY;
}
protected bool $allowEmpty;
function isAllowEmpty(): bool {
return $this->allowEmpty;
}
function inc(): int {
$value = (int)$this->get();
$this->set(++$value);
@ -33,4 +45,13 @@ abstract class AbstractAccess implements IAccess {
cl::set($array, $key, $value);
$this->set($array);
}
function ensureAssoc(array $keys, ?array $params=null): void {
}
function ensureKeys(array $defaults, ?array $params=null): void {
}
function ensureOrder(array $keys, ?array $params=null): void {
}
}

View File

@ -0,0 +1,182 @@
<?php
namespace nur\sery\wip\php\access;
use nulib\cl;
use nulib\StateException;
use ReflectionClass;
use ReflectionException;
class ChainAccess extends AbstractAccess {
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(IAccess $access, $key, ?array $params=null) {
parent::__construct();
$this->access = $access;
$this->key = $key;
$this->accessType = $params["access_type"] ?? self::ACCESS_AUTO;
}
protected IAccess $access;
/** @var null|int|string|array */
protected $key;
protected int $accessType;
protected function _accessType($access, $key): int {
$accessType = $this->accessType;
if ($accessType === self::ACCESS_AUTO) {
if (is_object($access) && is_string($key)) {
$accessType = self::ACCESS_PROPERTY;
} else {
$accessType = self::ACCESS_KEY;
}
}
return $accessType;
}
protected function _has(): bool {
$src = $this->access->get();
$key = $this->key;
$accessType = $this->_accessType($src, $key);
if ($accessType === self::ACCESS_KEY) {
return cl::phas($src, $key);
} elseif ($accessType === self::ACCESS_PROPERTY) {
$class = new ReflectionClass($src);
return $class->hasProperty($key) || property_exists($src, $key);
} else {
throw self::unexpected_access_type();
}
}
protected function _get($default=null) {
$src = $this->access->get();
$key = $this->key;
$accessType = $this->_accessType($src, $key);
if ($accessType === self::ACCESS_KEY) {
return cl::pget($src, $key);
} elseif ($accessType === self::ACCESS_PROPERTY) {
$class = new ReflectionClass($src);
try {
$property = $class->getProperty($key);
$property->setAccessible(true);
} catch (ReflectionException $e) {
$property = null;
}
if ($property !== null) {
return $property->getValue($src);
} elseif (property_exists($src, $key)) {
return $src->$key;
} else {
return $default;
}
} else {
throw self::unexpected_access_type();
}
}
protected function _pset(object $dest, $name, $value): void {
$class = new ReflectionClass($dest);
try {
$property = $class->getProperty($name);
$property->setAccessible(true);
} catch (ReflectionException $e) {
$property = null;
}
if ($property !== null) {
$property->setValue($dest, $value);
} else {
$dest->$name = $value;
}
}
function isAllowEmpty(): bool {
return $this->access->isAllowEmpty();
}
function exists(): bool {
if (!$this->access->exists()) return false;
if ($this->key === null) return true;
return $this->_has();
}
function available(): bool {
if (!$this->access->available()) return false;
if ($this->key === null) return true;
if (!$this->_has()) return false;
return $this->isAllowEmpty() || $this->_get() !== "";
}
function get($default=null) {
if ($this->key === null) {
return $this->access->get($default);
}
return $this->_get($default);
}
function set($value): void {
if ($this->key === null) {
$this->access->set($value);
return;
}
$dest = $this->access->get();
$key = $this->key;
$accessType = $this->_accessType($dest, $key);
if ($accessType === self::ACCESS_KEY) {
cl::pset($dest, $key, $value);
$this->access->set($dest);
} elseif ($accessType === self::ACCESS_PROPERTY) {
$this->_pset($dest, $key, $value);
} else {
throw self::unexpected_access_type();
}
}
function del(): void {
if ($this->key === null) {
$this->access->del();
return;
}
$dest = $this->access->get();
$key = $this->key;
$accessType = $this->_accessType($dest, $key);
if ($accessType === self::ACCESS_KEY) {
cl::pdel($dest, $key);
$this->access->set($dest);
} elseif ($accessType === self::ACCESS_PROPERTY) {
$this->_pset($dest, $key, null);
} else {
throw self::unexpected_access_type();
}
}
function addKey($key, ?array $params=null): IAccess {
if ($key === null) return $this;
$accessType = $params["access_type"] ?? $this->accessType;
if ($accessType === self::ACCESS_KEY && $accessType === $this->accessType) {
if ($this->key !== null) $key = cl::merge($this->key, $key);
return new ChainAccess($this->access, $key);
} else {
return new ChainAccess($this, $key);
}
}
function ensureAssoc(array $keys, ?array $params=null): void {
#XXX fonction de $accessType?
#$this->access->ensureAssoc($keys, $params);
}
function ensureKeys(array $defaults, ?array $params=null): void {
#XXX fonction de $accessType?
#$this->access->ensureKeys($defaults, $params);
}
function ensureOrder(array $keys, ?array $params=null): void {
#XXX fonction de $accessType?
#$this->access->ensureOrder($keys, $params);
}
}

View File

@ -7,67 +7,99 @@ use nulib\cl;
* Class FormAccess: accès à une valeur de $_POST puis $_GET, dans cet ordre
*/
class FormAccess extends AbstractAccess {
const ALLOW_EMPTY = false;
function __construct($key, ?array $params=null) {
parent::__construct($params);
$this->key = $key;
$this->allowEmpty = $params["allow_empty"] ?? false;
}
/** @var int|string */
/** @var null|int|string|array */
protected $key;
protected bool $allowEmpty;
function exists(): bool {
protected function _exists(array $first, ?array $second=null): bool {
$key = $this->key;
if ($key === null) return false;
return array_key_exists($key, $_POST) || array_key_exists($key, $_GET);
if ($key === null) return true;
if (cl::phas($first, $key)) return true;
return $second !== null && cl::phas($second, $key);
}
public function available(): bool {
function exists(): bool {
return $this->_exists($_POST, $_GET);
}
protected function _available(array $first, ?array $second=null): bool {
$key = $this->key;
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] !== "";
if ($key === null) return true;
if (cl::phas($first, $key)) {
return $this->allowEmpty || cl::pget($first, $key) !== "";
} elseif ($second !== null && cl::phas($second, $key)) {
return $this->allowEmpty || cl::pget($second, $key) !== "";
} else {
return false;
}
}
function get($default=null) {
public function available(): bool {
return $this->_available($_POST, $_GET);
}
protected function _get($default, array $first, ?array $second=null) {
$key = $this->key;
if ($key === null) return $default;
if (array_key_exists($key, $_POST)) {
$value = $_POST[$key];
if ($value === "" && !$this->allowEmpty) return $default;
return $value;
} elseif (array_key_exists($key, $_GET)) {
$value = $_GET[$key];
if ($value === "" && !$this->allowEmpty) return $default;
return $value;
if ($key === null) return cl::merge($first, $second);
if (cl::phas($first, $key)) {
$value = cl::pget($first, $key);
if ($value !== "" || $this->allowEmpty) return $value;
} elseif ($second !== null && cl::phas($second, $key)) {
$value = cl::pget($second, $key);
if ($value !== "" || $this->allowEmpty) return $value;
}
return $default;
}
function get($default=null) {
return $this->_get($default, $_POST, $_GET);
}
function _set($value, array &$first, ?array &$second=null): void {
$key = $this->key;
if ($key === null) {
# interdire la modification de la destination
return;
}
if ($second !== null && !cl::phas($first, $key) && cl::phas($second, $key)) {
cl::pset($second, $key, $value);
} else {
return $default;
cl::pset($first, $key, $value);
}
}
function set($value): void {
$this->_set($value, $_POST, $_GET);
}
function _del(array &$first, ?array &$second=null): void {
$key = $this->key;
if ($key === null) return;
if (!array_key_exists($key, $_POST) && array_key_exists($key, $_GET)) {
cl::set($_GET, $key, $value);
if ($key === null) {
# interdire la modification de la destination
return;
}
if ($second !== null && !cl::phas($first, $key) && cl::phas($second, $key)) {
cl::pdel($second, $key);
} else {
cl::set($_POST, $key, $value);
cl::pdel($first, $key);
}
}
function del(): void {
$key = $this->key;
if ($key === null) return;
if (!array_key_exists($key, $_POST) && array_key_exists($key, $_GET)) {
cl::del($_GET, $key);
} else {
cl::del($_POST, $key);
}
$this->_del($_POST, $_GET);
}
function addKey($key): self {
if ($key === null) return $this;
if ($this->key !== null) $key = cl::merge($this->key, $key);
return new static($key, [
"allow_empty" => $this->allowEmpty
]);
}
}

View File

@ -8,42 +8,22 @@ use nulib\cl;
*/
class GetAccess extends FormAccess {
function exists(): bool {
$key = $this->key;
if ($key === null) return false;
return array_key_exists($key, $_GET);
return $this->_exists($_GET);
}
public function available(): bool {
$key = $this->key;
if ($key === null) return false;
if (array_key_exists($key, $_GET)) {
return $this->allowEmpty || $_GET[$key] !== "";
} else {
return false;
}
return $this->_available($_GET);
}
function get($default=null) {
$key = $this->key;
if ($key === null) return $default;
if (array_key_exists($key, $_GET)) {
$value = $_GET[$key];
if ($value === "" && !$this->allowEmpty) return $default;
return $value;
} else {
return $default;
}
return $this->_get($default, $_GET);
}
function set($value): void {
$key = $this->key;
if ($key === null) return;
cl::set($_GET, $key, $value);
$this->_set($value, $_GET);
}
function del(): void {
$key = $this->key;
if ($key === null) return;
cl::del($_GET, $key);
$this->_del($_GET);
}
}

View File

@ -1,6 +1,8 @@
<?php
namespace nur\sery\wip\php\access;
use ReflectionClass;
/**
* Interface IAccess: abstraction d'un accès complet à une valeur
*/
@ -25,4 +27,26 @@ interface IAccess extends IGetter, ISetter, IDeleter {
* tableau si $key===null
*/
function append($value, $key=null): void;
/** retourner une instance permettant d'accéder à $value[$key] */
function addKey($key): IAccess;
/**
* s'assurer que la destination est un tableau associatif en remplaçant les
* clés numériques par les clés correspondantes du tableau $keys
*/
function ensureAssoc(array $keys, ?array $params=null): void;
/**
* s'assurer que toutes les clés mentionnées dans le tableau $defaults
* existent. si elles n'existent pas, leur donner la valeur du tableau
* $defaults
*/
function ensureKeys(array $defaults, ?array $params=null): void;
/**
* s'assure que les clés de la destination sont dans l'ordre mentionné dans le
* tableau $keys. toutes les clés supplémentaires sont placées à la fin
*/
function ensureOrder(array $keys, ?array $params=null): void;
}

View File

@ -11,6 +11,12 @@ interface IGetter {
*/
function exists(): bool;
/**
* @return bool true si cet objet autorise les chaines vides. si c'est le cas,
* {@link exists()} et {@link available()} sont fonctionnellement identiques
*/
function isAllowEmpty(): bool;
/** @return bool true si la valeur existe et est utilisable, false sinon */
function available(): bool;

View File

@ -3,67 +3,186 @@ namespace nur\sery\wip\php\access;
use ArrayAccess;
use nulib\cl;
use nulib\ref\schema\ref_schema;
/**
* Class KeyAccess: accès à une valeur d'une clé dans un tableau
* Class KeyAccess: accès
* - soit à une valeur d'un chemin de clé dans un tableau (si $key !== null)
* - soit à une valeur scalaire (si $key === null)
*/
class KeyAccess extends AbstractAccess {
function __construct(&$array, $key, ?array $params=null) {
$this->array =& $array;
const ALLOW_NULL = null;
const ALLOW_FALSE = null;
const PROTECT_DEST = false;
function __construct(&$dest, $key=null, ?array $params=null) {
parent::__construct($params);
$this->protectDest = $params["protect_dest"] ?? static::PROTECT_DEST;
$this->dest =& $dest;
$this->key = $key;
$this->allowNull = $params["allow_null"] ?? true;
$this->allowFalse = $params["allow_false"] ?? false;
$this->allowEmpty = $params["allow_empty"] ?? true;
$this->allowNull = $params["allow_null"] ?? static::ALLOW_NULL;
$this->allowFalse = $params["allow_false"] ?? static::ALLOW_FALSE;
}
/** @var array|ArrayAccess */
protected $array;
protected bool $protectDest;
function reset(&$array): self {
$this->array =& $array;
/** @var mixed|array|ArrayAccess */
protected $dest;
/** @var null|int|string|array */
protected $key;
function reset(&$dest, $key=null): self {
$this->dest =& $dest;
$this->key = $key;
return $this;
}
/** @var int|string */
protected $key;
function resetKey($key=null): self {
$this->key = $key;
return $this;
}
protected bool $allowNull;
protected ?bool $allowNull;
protected bool $allowFalse;
protected function isAllowNull(): bool {
$allowNull = $this->allowNull;
if ($allowNull !== null) return $allowNull;
return $this->key !== null;
}
protected bool $allowEmpty;
protected ?bool $allowFalse;
protected function isAllowFalse(): bool {
$allowFalse = $this->allowFalse;
if ($allowFalse !== null) return $allowFalse;
return $this->key === null;
}
function exists(): bool {
$key = $this->key;
if ($key === null) return false;
return cl::has($this->array, $key);
if ($key === null) {
return $this->isAllowNull() || $this->dest !== null;
} else {
return cl::phas($this->dest, $key);
}
}
function available(): bool {
if (!$this->exists()) return false;
$value = cl::get($this->array, $this->key);
if ($value === null) return $this->allowNull;
if ($value === false) return $this->allowFalse;
$key = $this->key;
if ($key === null) $value = $this->dest;
else $value = cl::pget($this->dest, $key);
if ($value === "") return $this->allowEmpty;
if ($value === null) return $this->isAllowNull();
if ($value === false) return $this->isAllowFalse();
return true;
}
function get($default=null) {
if ($this->key === null) return $default;
$value = cl::get($this->array, $this->key, $default);
if ($value === null && !$this->allowNull) return $default;
if ($value === false && !$this->allowFalse) return $default;
$key = $this->key;
if ($key === null) $value = $this->dest;
else $value = cl::pget($this->dest, $key, $default);
if ($value === "" && !$this->allowEmpty) return $default;
if ($value === null && !$this->isAllowNull()) return $default;
if ($value === false && !$this->isAllowFalse()) return $default;
return $value;
}
function set($value): void {
if ($this->key === null) return;
cl::set($this->array, $this->key, $value);
$key = $this->key;
if ($key === null) {
if (!$this->protectDest) $this->dest = $value;
} else {
cl::pset($this->dest, $key, $value);
}
}
function del(): void {
if ($this->key === null) return;
cl::del($this->array, $this->key);
$key = $this->key;
if ($key === null) {
if (!$this->protectDest) $this->dest = null;
} else {
cl::pdel($this->dest, $key);
}
}
function addKey($key): self {
if ($key === null) return $this;
if ($this->key !== null) $key = cl::merge($this->key, $key);
return new KeyAccess($this->dest, $key, [
"allow_empty" => $this->allowEmpty,
"allow_null" => $this->allowNull,
"allow_false" => $this->allowFalse,
"protect_dest" => $this->protectDest,
]);
}
function ensureAssoc(array $keys, ?array $params=null): void {
$dest =& $this->dest;
$prefix = $params["key_prefix"] ?? null;
$suffix = $params["key_suffix"] ?? null;
$index = 0;
foreach ($keys as $key) {
if ($prefix !== null || $suffix !== null) {
$destKey = "$prefix$key$suffix";
} else {
# préserver les clés numériques
$destKey = $key;
}
if ($dest !== null && array_key_exists($destKey, $dest)) continue;
while (in_array($index, $keys, true)) {
$index++;
}
if ($dest !== null && array_key_exists($index, $dest)) {
$dest[$destKey] = $dest[$index];
unset($dest[$index]);
$index++;
}
}
}
function ensureKeys(array $defaults, ?array $params=null): void {
$dest =& $this->dest;
$keys = array_keys($defaults);
$prefix = $params["key_prefix"] ?? null;
$suffix = $params["key_suffix"] ?? null;
foreach ($keys as $key) {
$destKey = "$prefix$key$suffix";
if ($dest === null || !array_key_exists($destKey, $dest)) {
$dest[$destKey] = $defaults[$key];
}
}
}
function ensureOrder(array $keys, ?array $params=null): void {
$dest =& $this->dest;
if ($dest === null) return;
$prefix = $params["key_prefix"] ?? null;
$suffix = $params["key_suffix"] ?? null;
if ($prefix !== null || $suffix !== null) {
foreach ($keys as &$key) {
$key = "$prefix$key$suffix";
}; unset($key);
}
$destKeys = array_keys($dest);
$keyCount = count($keys);
if (array_slice($destKeys, 0, $keyCount) === $keys) {
# si le tableau a déjà les bonnes clés dans le bon ordre, rien à faire
return;
}
$ordered = [];
foreach ($keys as $key) {
if (array_key_exists($key, $dest)) {
$ordered[$key] = $dest[$key];
unset($dest[$key]);
}
}
$preserveKeys = $params["preserve_keys"] ?? false;
if ($preserveKeys) $dest = cl::merge2($ordered, $dest);
else $dest = array_merge($ordered, $dest);
}
}

View File

@ -8,42 +8,22 @@ use nulib\cl;
*/
class PostAccess extends FormAccess {
function exists(): bool {
$key = $this->key;
if ($key === null) return false;
return array_key_exists($key, $_POST);
return $this->_exists($_POST);
}
public function available(): bool {
$key = $this->key;
if ($key === null) return false;
if (array_key_exists($key, $_POST)) {
return $this->allowEmpty || $_POST[$key] !== "";
} else {
return false;
}
return $this->_available($_POST);
}
function get($default=null) {
$key = $this->key;
if ($key === null) return $default;
if (array_key_exists($key, $_POST)) {
$value = $_POST[$key];
if ($value === "" && !$this->allowEmpty) return $default;
return $value;
} else {
return $default;
}
return $this->_get($default, $_POST);
}
function set($value): void {
$key = $this->key;
if ($key === null) return;
cl::set($_POST, $key, $value);
$this->_set($value, $_POST);
}
function del(): void {
$key = $this->key;
if ($key === null) return;
cl::del($_POST, $key);
$this->_del($_POST);
}
}

View File

@ -0,0 +1,175 @@
<?php
namespace nur\sery\wip\php\access;
use nulib\StateException;
use nulib\str;
use ReflectionClass;
use ReflectionException;
use ReflectionNamedType;
use ReflectionProperty;
use stdClass;
class PropertyAccess extends AbstractAccess {
const PROTECT_DEST = true;
const MAP_NAMES = true;
const ALLOW_NULL = true;
const ALLOW_FALSE = false;
function __construct(?object $dest, ?string $name=null, ?array $params=null) {
parent::__construct($params);
$this->protectDest = $params["protect_dest"] ?? static::PROTECT_DEST;
$this->mapNames = $params["map_names"] ?? static::MAP_NAMES;
$this->_setName($name);
$this->_setDest($dest);
$this->allowNull = $params["allow_null"] ?? static::ALLOW_NULL;
$this->allowFalse = $params["allow_false"] ?? static::ALLOW_FALSE;
}
protected bool $protectDest;
protected ?object $dest;
protected bool $mapNames;
protected ?string $name;
protected ?ReflectionProperty $property;
private function _getName(string $key): string {
return $this->mapNames? str::us2camel($key): $key;
}
private function _setName(?string $name): void {
if ($name !== null) $name = $this->_getName($name);
$this->name = $name;
}
private function _getProperty(?string $name, ?ReflectionClass $class, ?object $object=null): ?ReflectionProperty {
$property = null;
if ($class === null && $object !== null) {
$class = new ReflectionClass($object);
}
if ($class !== null && $name !== null) {
try {
$property = $class->getProperty($name);
$property->setAccessible(true);
} catch (ReflectionException $e) {
}
}
return $property;
}
private function _setDest(?object $dest): void {
$this->dest = $dest;
$this->property = $this->_getProperty($this->name, null, $dest);
}
function reset(?object $dest, ?string $name=null): self {
$this->_setName($name);
$this->_setDest($dest);
return $this;
}
function resetKey($name=null): self {
$this->_setName($name);
return $this;
}
protected bool $allowNull;
protected bool $allowFalse;
function exists(): bool {
$name = $this->name;
if ($this->dest === null) return false;
return $name === null
|| $this->property !== null
|| property_exists($this->dest, $name);
}
protected function _get($default=null) {
$name = $this->name;
$property = $this->property;
if ($this->dest === null) {
return $default;
} elseif ($name === null) {
return $this->dest;
} elseif ($property !== null) {
return $property->getValue($this->dest);
} elseif (property_exists($this->dest, $name)) {
return $this->dest->$name;
} else {
return $default;
}
}
function available(): bool {
if (!$this->exists()) return false;
$value = $this->_get();
if ($value === "") return $this->allowEmpty;
if ($value === null) return $this->allowNull;
if ($value === false) return $this->allowFalse;
return true;
}
function get($default=null) {
if (!$this->exists()) return $default;
$value = $this->_get();
if ($value === "" && !$this->allowEmpty) return $default;
if ($value === null && !$this->allowNull) return $default;
if ($value === false && !$this->allowFalse) return $default;
return $value;
}
protected function _set($value): void {
$name = $this->name;
$property = $this->property;
if ($this->dest === null) {
throw StateException::unexpected_state("dest is null");
} elseif ($name === null) {
if (!$this->protectDest) $this->_setDest($value);
} elseif ($property !== null) {
$property->setValue($this->dest, $value);
} else {
$this->dest->$name = $value;
}
}
function set($value): void {
$this->_set($value);
}
function del(): void {
$this->_set(null);
}
function addKey($key): IAccess {
if ($key === null) return $this;
return new ChainAccess($this, $key);
}
function ensureKeys(array $defaults, ?array $params=null): void {
$dest = $this->dest;
if ($dest === null) {
# comme ne connait pas la classe de l'objet destination, on n'essaie pas
# de le créer
return;
}
$class = new ReflectionClass($dest);
$keys = array_keys($defaults);
$prefix = $params["key_prefix"] ?? null;
$suffix = $params["key_suffix"] ?? null;
foreach ($keys as $key) {
$name = $this->_getName("$prefix$key$suffix");
$property = $this->_getProperty($name, $class);
if ($property !== null) {
$type = $property->getType();
if ($type !== null && !$property->isInitialized($dest) && $type->allowsNull()) {
# initialiser avec null au lieu de $defaults[$key] pour respecter le
# type de la propriété
$property->setValue($dest, null);
}
} elseif (!property_exists($dest, $name)) {
$dest->$name = $defaults[$key];
}
}
}
}

View File

@ -16,6 +16,7 @@ namespace nur\sery\wip\php\access;
*/
class ShadowAccess extends AbstractAccess {
function __construct(IAccess $reader, IAccess $writer) {
parent::__construct();
$this->reader = $reader;
$this->writer = $writer;
$this->getter = $reader;
@ -27,6 +28,10 @@ class ShadowAccess extends AbstractAccess {
protected IGetter $getter;
public function isAllowEmpty(): bool {
return $this->getter->isAllowEmpty();
}
function exists(): bool {
return $this->getter->exists();
}
@ -48,4 +53,20 @@ class ShadowAccess extends AbstractAccess {
$this->writer->del();
$this->getter = $this->reader;
}
function addKey($key): IAccess {
return new ChainAccess($this, $key);
}
function ensureAssoc(array $keys, ?array $params=null): void {
$this->writer->ensureAssoc($keys, $params);
}
function ensureKeys(array $defaults, ?array $params=null): void {
$this->writer->ensureKeys($defaults, $params);
}
function ensureOrder(array $keys, ?array $params=null): void {
$this->writer->ensureOrder($keys, $params);
}
}

View File

@ -1,56 +0,0 @@
<?php
namespace nur\sery\wip\php\access;
/**
* Class ValueAccess: accès à une valeur unitaire
*/
class ValueAccess extends AbstractAccess {
function __construct(&$value, ?array $params=null) {
$this->value =& $value;
$this->allowNull = $params["allow_null"] ?? false;
$this->allowFalse = $params["allow_false"] ?? true;
$this->allowEmpty = $params["allow_empty"] ?? true;
}
/** @var mixed */
protected $value;
function reset(&$value): self {
$this->value =& $value;
return $this;
}
protected bool $allowNull;
protected bool $allowFalse;
protected bool $allowEmpty;
function exists(): bool {
return $this->allowNull || $this->value !== null;
}
function available(): bool {
if (!$this->exists()) return false;
$value = $this->value;
if ($value === false) return $this->allowFalse;
if ($value === "") return $this->allowEmpty;
return true;
}
function get($default=null) {
$value = $this->value;
if ($value === null && !$this->allowNull) return $default;
if ($value === false && !$this->allowFalse) return $default;
if ($value === "" && !$this->allowEmpty) return $default;
return $value;
}
function set($value): void {
$this->value = $value;
}
function del(): void {
$this->value = null;
}
}

View File

@ -2,9 +2,7 @@
namespace nur\sery\wip\schema;
use IteratorAggregate;
use nur\sery\wip\schema\_assoc\AssocResult;
use nur\sery\wip\schema\_list\ListResult;
use nur\sery\wip\schema\_scalar\ScalarResult;
use Throwable;
/**
* Class Result: résultat de l'analyse ou de la normalisation d'une valeur
@ -17,6 +15,8 @@ use nur\sery\wip\schema\_scalar\ScalarResult;
* @property bool $normalized si la valeur est valide, est-elle normalisée?
* @property string|null $messageKey clé de message si la valeur n'est pas valide
* @property string|null $message message si la valeur n'est pas valide
* @property Throwable|null $exception l'exception qui a fait échouer la
* validation le cas échéant
* @property string|null $origValue valeur originale avant extraction et analyse
* @property mixed|null $normalizedValue la valeur normalisée si elle est
* disponible, null sinon. ce champ est utilisé comme optimisation si la valeur
@ -26,7 +26,7 @@ abstract class Result implements IteratorAggregate {
const KEYS = [
"resultAvailable",
"present", "available", "null", "valid", "normalized",
"messageKey", "message",
"messageKey", "message", "exception",
"origValue", "normalizedValue",
];
@ -34,10 +34,6 @@ abstract class Result implements IteratorAggregate {
$this->reset();
}
function isAssoc(?AssocResult &$result=null): bool { return false; }
function isList(?ListResult &$result=null): bool { return false; }
function isScalar(?ScalarResult &$result=null): bool { return false; }
/**
* Obtenir la liste des clés valides pour les valeurs accessibles via cet
* objet

View File

@ -17,6 +17,29 @@ use nur\sery\wip\schema\types\tcontent;
use nur\sery\wip\schema\types\tpkey;
use nur\sery\wip\schema\types\trawstring;
/**
* Class Schema
*
* @property-read array|IType $type
* @property-read mixed $default
* @property-read string|null $title
* @property-read bool $required
* @property-read bool $nullable
* @property-read string|array|null $desc
* @property-read callable|null $analyzerFunc
* @property-read callable|null $extractorFunc
* @property-read callable|null $parserFunc
* @property-read callable|null $normalizerFunc
* @property-read array|null $messages
* @property-read callable|null $formatterFunc
* @property-read mixed $format
* @property-read array $nature
* @property-read array|null $schema
* @property-read string|int|null $name
* @property-read string|array|null $pkey
* @property-read string|null $header
* @property-read bool|null $computed
*/
abstract class Schema implements ArrayAccess {
/**
* créer le cas échéant une nouvelle instance de {@link Schema} à partir d'une
@ -57,7 +80,7 @@ abstract class Schema implements ArrayAccess {
# 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, $wrapper);
return self::ns($schema, $definition)->getWrapper($value, $valueKey, null, $wrapper);
}
protected static function have_nature(array $definition, ?string &$nature=null): bool {
@ -75,18 +98,18 @@ abstract class Schema implements ArrayAccess {
return false;
}
protected static function _normalize(&$definition, $definitionKey=null): void {
protected static function _normalize_definition(&$definition, $definitionKey=null, ?array $natureMetaschema=null): void {
if (!is_array($definition)) $definition = [$definition];
# s'assurer que toutes les clés existent avec leur valeur par défaut
$index = 0;
foreach (array_keys(ref_schema::SCALAR_METASCHEMA) as $key) {
foreach (array_keys(ref_schema::VALUE_METASCHEMA) as $key) {
if (!array_key_exists($key, $definition)) {
if (array_key_exists($index, $definition)) {
$definition[$key] = $definition[$index];
unset($definition[$index]);
$index++;
} else {
$definition[$key] = ref_schema::SCALAR_METASCHEMA[$key][1];
$definition[$key] = ref_schema::VALUE_METASCHEMA[$key][1];
}
}
}
@ -138,6 +161,12 @@ abstract class Schema implements ArrayAccess {
# nature
$nature = $definition[""];
tarray::ensure_array($nature);
$natureMetaschema ??= ref_schema::NATURE_METASCHEMA;
foreach (array_keys($natureMetaschema) as $key) {
if (!array_key_exists($key, $nature)) {
$nature[$key] = $natureMetaschema[$key][1];
}
}
$definition[""] = $nature;
# name, pkey, header
$name = $definition["name"];
@ -169,11 +198,11 @@ abstract class Schema implements ArrayAccess {
switch ($nature[0] ?? null) {
case "assoc":
foreach ($definition["schema"] as $key => &$keydef) {
self::_normalize($keydef, $key);
self::_normalize_definition($keydef, $key);
}; unset($keydef);
break;
case "list":
self::_normalize($definition["schema"]);
self::_normalize_definition($definition["schema"]);
break;
}
}
@ -246,6 +275,13 @@ abstract class Schema implements ArrayAccess {
return $this->_definition;
}
/**
* retourner la liste des clés valides pour l'accès aux valeurs et résultats
*/
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 */
@ -253,7 +289,9 @@ abstract class Schema implements ArrayAccess {
/** retourner true si le schéma est de nature scalaire */
function isScalar(?ScalarSchema &$schema=null): bool { return false; }
abstract function getWrapper(&$value=null, $valueKey=null, ?Wrapper &$wrapper=null): Wrapper;
abstract protected function newWrapper(): Wrapper;
abstract function getWrapper(&$value=null, $valueKey=null, ?array $params=null, ?Wrapper &$wrapper=null): Wrapper;
#############################################################################
# key & properties
@ -272,7 +310,15 @@ abstract class Schema implements ArrayAccess {
throw AccessException::read_only(null, $offset);
}
const _PROPERTY_PKEYS = [];
const _PROPERTY_PKEYS = [
"analyzerFunc" => "analyzer_func",
"extractorFunc" => "extractor_func",
"parserFunc" => "parser_func",
"normalizerFunc" => "normalizer_func",
"formatterFunc" => "formatter_func",
"nature" => ["", 0],
];
function __get($name) {
$pkey = cl::get(static::_PROPERTY_PKEYS, $name, $name);
return cl::pget($this->definition, $pkey);

View File

@ -1,20 +1,32 @@
# nulib\schema
* instance de WrapperContext directement dans le schéma
* plus de {key} ni {orig} dans messages
* les messages standard ne sont utilisés que s'il n'y a pas de message dans
l'exception
* si instance de UserException, prendre le message "non technique" pour
résultat
* ensureKeys() et orderKeys() se fait au niveau de access (ou input?)
* access/input ne pouvant pas connaître les valeurs appropriées, c'est le
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]`
* rajouter l'attribut "size" pour spécifier la taille maximale des valeurs
* cela pourrait servir pour générer automatiquement des tables SQL
* ou pour modéliser un schéma FSV
* valeurs composite/computed
* analyse / vérification de la valeur complète après calcul du résultat, si
tous les résultats sont bons
* calcul des valeurs composites/computed par une fonction avant/après l'analyse
globale si résultat ok
* fonction getter_func, setter_func, deleter_func pour les propriétés de type
computed
* tdate et tdatetime. qu'en est-il des autres classes (delay, etc.)
* possibilité de spécifier le format de la date à analyser
* ScalarSchema::from_property()
* l'argument $format de AssocWrapper::format() est un tableau associatif
`[$key => $format]`
cela permet de spécifier des format spécifiques pour certains champs.
* cela signifie que la valeur de retour n'est pas string :-(
retourner string|array
* dans AssocSchema, support `[key_prefix]` qui permet de spécifier un préfixe
commun aux champs dans le tableau destination, e.g
~~~php
@ -58,6 +70,8 @@
la définition de ces "circonstances" est encore à faire: soit un paramètre
lors de la définition du schéma, soit un truc magique du genre "toutes les
valeurs séquentielles sont des clés du schéma"
valeurs séquentielles sont des clés du schéma", soit un mode automatique
activé par un paramètre où une valeur "val" devient "val"=>true si la clé
"val" existe dans le schéma
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary

View File

@ -3,9 +3,12 @@ namespace nur\sery\wip\schema;
use ArrayAccess;
use IteratorAggregate;
use nulib\php\func;
use nur\sery\wip\schema\_assoc\AssocWrapper;
use nur\sery\wip\schema\_list\ListWrapper;
use nur\sery\wip\schema\_scalar\ScalarResult;
use nur\sery\wip\schema\_scalar\ScalarWrapper;
use nur\sery\wip\schema\input\Input;
use nur\sery\wip\schema\types\IType;
abstract class Wrapper implements ArrayAccess, IteratorAggregate {
@ -13,8 +16,92 @@ abstract class Wrapper implements ArrayAccess, IteratorAggregate {
function isList(?ListWrapper &$wrapper=null): bool { return false; }
function isScalar(?ScalarWrapper &$wrapper=null): bool { return false; }
/** spécifier la valeur destination gérée par cet objet */
abstract function reset(&$value, $valueKey=null, ?bool $verifix=null): self;
protected WrapperContext $context;
/** changer les paramètres de gestion des valeurs */
function resetParams(?array $params): void {
$this->context->resetParams($params);
}
protected function resetContext($resetSelectedKey): void {
$context = $this->context;
$context->type = null;
$context->result->reset();
$context->analyzed = false;
$context->normalized = false;
}
protected function afterModify(?array $params, $resetSelectedKey=false): void {
$context = $this->context;
$this->resetContext($resetSelectedKey);
if ($params["analyze"] ?? $context->analyze) {
$this->analyze($params);
}
if ($context->analyzed) {
if ($params["normalize"] ?? $context->normalize) {
$this->normalize($params);
}
}
}
protected function newInput(&$value): Input {
return new Input($value);
}
/**
* spécifier la valeur destination gérée par cet objet.
*
* @param ?array $params paramètres spécifique à cet appel, qui peuvent être
* différent des paramètres par défaut
*/
function reset(&$value, $valueKey=null, ?array $params=null): Wrapper {
$context = $this->context;
if ($value instanceof Input) $input = $value;
else $input = $this->newInput($value);
$context->input = $input;
$context->valueKey = $valueKey;
$this->afterModify($params, true);
return $this;
}
/** analyser la valeur */
abstract static function _analyze(WrapperContext $context, Wrapper $wrapper, ?array $params): int;
function analyze(?array $params=null): bool {
$context = $this->context;
$reanalyze = $params["reanalyze"] ?? false;
if ($context->analyzed && !$reanalyze) return false;
static::_analyze($context, $this, $params);
$context->analyzed = true;
return true;
}
/** normaliser la valeur */
abstract static function _normalize(WrapperContext $context, Wrapper $wrapper, ?array $params): bool;
function normalize(?array $params=null): bool {
$context = $this->context;
// il faut que la valeur soit analysée avant de la normaliser
static::analyze($params);
if (!$context->analyzed) return false;
$renormalize = $params["renormalize"] ?? false;
if ($renormalize || !$context->normalized) {
$modified = static::_normalize($context, $this, $params);
$context->normalized = true;
} else {
$modified = false;
}
/** @var ScalarResult $result */
$result = $context->result;
if (!$result->valid) {
$result->throw($params["throw"] ?? $context->throw);
}
return $modified;
}
/**
* Obtenir la liste des clés valides pour les valeurs accessibles via cet
@ -38,37 +125,80 @@ abstract class Wrapper implements ArrayAccess, IteratorAggregate {
}
/**
* obtenir le résultat de l'appel d'une des fonctions {@link set()} ou
* {@link unset()}
* obtenir le résultat de l'analyse de la valeur du wrapper sélectionné
*
* cette fonction doit être appelée après {@link set()} ou {@link unset()} et
* après que le wrapper aie été sélectionné avec {@link select()}
*/
abstract function getResult(): Result;
function getResult($key=false): Result {
return $this->context->result;
}
/** retourner true si la valeur existe */
abstract function isPresent(): bool;
function isPresent($key=false): bool {
return $this->getResult($key)->present;
}
/** retourner le type associé à la valeur */
abstract function getType(): IType;
function getType($key=false): IType {
return $this->context->type;
}
/** retourner true si la valeur est disponible */
abstract function isAvailable(): bool;
function isAvailable($key=false): bool {
return $this->getResult($key)->available;
}
/** retourner true si la valeur est valide */
abstract function isValid(): bool;
function isValid($key=false): bool {
return $this->getResult($key)->valid;
}
/** retourner true si la valeur est dans sa forme normalisée */
abstract function isNormalized(): bool;
function isNormalized($key=false): bool {
return $this->getResult($key)->normalized;
}
/** obtenir la valeur */
abstract function get($default=null);
function get($default=null, $key=false) {
$context = $this->context;
if (!$context->result->available) return $default;
return $context->input->get($context->valueKey);
}
/** remplacer la valeur */
abstract function set($value): self;
function set($value, ?array $params=null, $key=false): self {
$context = $this->context;
$context->input->set($value, $context->valueKey);
$this->afterModify($params);
return $this;
}
/** supprimer la valeur */
abstract function unset(): self;
function unset(?array $params=null, $key=false): self {
$context = $this->context;
$context->input->unset($context->valueKey);
$this->afterModify($params);
return $this;
}
protected function _format(WrapperContext $context, $format=null): string {
$value = $context->input->get($context->valueKey);
/** @var func $formatterFunc */
$formatterFunc = $context->schema->formatterFunc;
if ($formatterFunc !== null) {
# la fonction formatter n'a pas forcément accès au format de la définition
# le lui fournir ici
$format ??= $context->schema->format;
return $formatterFunc->invoke([$value, $format, $context, $this]);
} else {
# on assume que le type a été initialisé avec le format de la définition
# le cas échéant
return $context->type->format($value, $format);
}
}
/** formatter la valeur pour affichage */
abstract function format($format=null): string;
function format($format=null, $key=false): string {
return $this->_format($this->context, $format);
}
#############################################################################
# key & properties
@ -78,14 +208,14 @@ abstract class Wrapper implements ArrayAccess, IteratorAggregate {
}
function offsetGet($offset) {
return $this->select($offset);
return $this->get(null, $offset);
}
function offsetSet($offset, $value): void {
$this->select($offset)->set($value);
$this->set($value, null, $offset);
}
function offsetUnset($offset): void {
$this->select($offset)->unset();
$this->unset(null, $offset);
}
}

View File

@ -5,26 +5,44 @@ use nur\sery\wip\schema\input\Input;
use nur\sery\wip\schema\types\IType;
class WrapperContext {
function __construct(Schema $schema, Wrapper $wrapper, Input $input, $valueKey, Result $result) {
const DEFAULT_ANALYZE = true;
const DEFAULT_NORMALIZE = true;
const DEFAULT_THROW = true;
function __construct(Schema $schema, ?Input $input, $valueKey, ?array $params) {
$this->resetParams($params);
$this->schema = $schema;
$this->wrapper = $wrapper;
$this->input = $input;
$this->result = $result;
$this->type = null;
$this->origValue = null;
$this->value = null;
if ($input !== null) $this->input = $input;
$this->valueKey = $valueKey;
}
public ?array $params;
public bool $analyze, $analyzed = false;
public bool $normalize, $normalized = false;
public ?bool $throw;
function resetParams(?array $params): void {
$this->params = $params;
$this->analyze = $params["analyze"] ?? self::DEFAULT_ANALYZE;
$this->normalize = $params["normalize"] ?? self::DEFAULT_NORMALIZE;
$this->throw = $params["throw"] ?? self::DEFAULT_THROW;
}
/** schéma de la valeur */
public Schema $schema;
public Wrapper $wrapper;
/** source et destination de la valeur */
public Input $input;
public Result $result;
public ?IType $type;
/** @var mixed */
public $origValue;
/** @var mixed */
public $value;
/** @var int|string|null */
/** @var string|int|null clé de la valeur dans le tableau destination */
public $valueKey;
/** @var mixed */
public $origValue = null;
/** @var mixed */
public $value = null;
/** @var string|int|null clé sélectionnée */
public $selectedKey = null;
/** type de la valeur de la clé sélectionnée après analyse */
public ?IType $type = null;
/** résultat de l'analyse de la valeur de la clé sélectionnée */
public ?Result $result = null;
}

View File

@ -1,53 +0,0 @@
<?php
namespace nur\sery\wip\schema\_assoc;
use nulib\ValueException;
use nur\sery\wip\schema\Result;
class AssocResult extends Result {
function __construct(Result $arrayResult, array &$keyResults) {
$this->arrayResult = $arrayResult;
$this->keyResults =& $keyResults;
$this->result =& $this->arrayResult;
parent::__construct();
}
function isAssoc(?AssocResult &$result=null): bool { $result = $this; return true;}
protected Result $arrayResult;
/** @var Result[] */
protected array $keyResults;
function getKeys(): array {
return array_keys($this->keyResults);
}
protected Result $result;
function select($key): Result {
if ($key === null) {
$this->result =& $this->arrayResult;
} elseif (array_key_exists($key, $this->keyResults)) {
$this->result =& $this->keyResults[$key];
} else {
throw ValueException::invalid_key($key);
}
return $this;
}
function reset(): void {
$this->arrayResult->reset();
foreach ($this->keyResults as $result) {
$result->reset();
}
}
function __get(string $name) {
return $this->result[$name];
}
function __set(string $name, $value): void {
$this->result[$name] = $value;
}
}

View File

@ -3,6 +3,7 @@ namespace nur\sery\wip\schema\_assoc;
use nulib\cl;
use nulib\ref\schema\ref_schema;
use nulib\ValueException;
use nur\sery\wip\schema\Schema;
use nur\sery\wip\schema\Wrapper;
@ -10,12 +11,9 @@ use nur\sery\wip\schema\Wrapper;
* Class AssocSchema
*/
class AssocSchema extends Schema {
/** @var array meta-schema d'un schéma de nature tableau associatif */
const METASCHEMA = ref_schema::ASSOC_METASCHEMA;
/**
* indiquer si $definition est une définition de schéma de nature tableau
* associatif que {@link normalize()} pourrait normaliser
* associatif que {@link normalize_definition()} pourrait normaliser
*/
static function isa_definition($definition): bool {
if (!is_array($definition)) return false;
@ -27,7 +25,7 @@ class AssocSchema extends Schema {
return !cl::have_num_keys($definition);
}
static function normalize($definition, $definitionKey=null): array {
static function normalize_definition($definition, $definitionKey=null): array {
if (!is_array($definition)) $definition = [$definition];
if (!self::have_nature($definition)) {
$definition = [
@ -36,7 +34,8 @@ class AssocSchema extends Schema {
"schema" => $definition,
];
}
self::_normalize($definition, $definitionKey);
$natureMetaschema = array_merge(ref_schema::NATURE_METASCHEMA, ref_schema::ASSOC_NATURE_METASCHEMA);
self::_normalize_definition($definition, $definitionKey, $natureMetaschema);
self::_ensure_nature($definition, "assoc", "array");
return $definition;
}
@ -44,12 +43,17 @@ class AssocSchema extends Schema {
function __construct($definition=null, $definitionKey=null, bool $normalize=true) {
if ($definition === null) $definition = static::SCHEMA;
if ($normalize) {
$definition = self::normalize($definition, $definitionKey);
$definition = self::normalize_definition($definition, $definitionKey);
$this->_definition = $definition;
self::_ensure_type($definition);
self::_ensure_schema_instances($definition);
}
$this->definition = $definition;
$keys = [];
foreach ($definition["schema"] as $key => $schema) {
if (!$schema["computed"]) $keys[] = $key;
}
$this->keys = $keys;
}
function isAssoc(?AssocSchema &$schema=null): bool {
@ -57,12 +61,29 @@ class AssocSchema extends Schema {
return true;
}
protected array $keys;
function getKeys(): array {
return $this->keys;
}
function getSchema($key): Schema {
if ($key === null) return $this;
$schema = $this->definition["schema"][$key] ?? null;
if ($schema === null) throw ValueException::invalid_key($key);
return $schema;
}
protected function newWrapper(): AssocWrapper {
return new AssocWrapper($this);
}
function getWrapper(&$array=null, $arrayKey=null, ?Wrapper &$wrapper=null): AssocWrapper {
function getWrapper(&$value=null, $valueKey=null, ?array $params=null, ?Wrapper &$wrapper=null): AssocWrapper {
# si pas de valeur ni de wrapper, pas d'analyse et donc pas d'exception
# cf le code similaire dans AssocWrapper::__construct()
$dontAnalyze = $value === null && $wrapper === null;
if (!($wrapper instanceof AssocWrapper)) $wrapper = $this->newWrapper();
return $wrapper->reset($array, $arrayKey);
if ($params !== null) $wrapper->resetParams($params);
return $wrapper->reset($value, $valueKey, $dontAnalyze? ["analyze" => false]: null);
}
}

View File

@ -1,6 +1,7 @@
<?php
namespace nur\sery\wip\schema\_assoc;
use nulib\ref\schema\ref_analyze;
use nulib\ValueException;
use nur\sery\wip\schema\_scalar\ScalarResult;
use nur\sery\wip\schema\_scalar\ScalarWrapper;
@ -8,133 +9,150 @@ use nur\sery\wip\schema\input\Input;
use nur\sery\wip\schema\Result;
use nur\sery\wip\schema\types\IType;
use nur\sery\wip\schema\Wrapper;
use nur\sery\wip\schema\WrapperContext;
class AssocWrapper extends Wrapper {
function __construct(AssocSchema $schema, &$array=null, $arrayKey=null, ?array $params=null) {
$verifix = $params["verifix"] ?? true;
$throw = $params["throw"] ?? null;
if ($array !== null && $throw === null) {
# Si $value est null, ne pas lancer d'exception, parce qu'on considère que
# c'est une initialisation sans conséquences
$throw = true;
function __construct(AssocSchema $schema, &$value=null, $valueKey=null, ?array $params=null) {
$keys = $schema->getKeys();
$keyWrappers = [];
foreach ($keys as $key) {
$keyWrappers[$key] = $schema->getSchema($key)->getWrapper();
}
$this->schema = $schema;
$this->verifix = $verifix;
$this->throw = $throw ?? false;
$this->result = new AssocResult();
$this->reset($array, $arrayKey);
$this->throw = $throw ?? true;
$this->context = $context = new AssocWrapperContext($schema, null, null, $params);
$context->arrayWrapper = new ScalarWrapper($schema, $dummy, null, null, $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;
}
function isAssoc(?AssocWrapper &$wrapper=null): bool { $wrapper = $this; return true; }
protected bool $verifix;
/** @var AssocWrapperContext */
protected WrapperContext $context;
protected bool $throw;
/** schéma de ce tableau */
protected AssocSchema $schema;
/** source et destination de la valeur */
protected Input $input;
/** @var string|int|null clé du tableau dans le tableau destination */
protected $arrayKey;
protected IType $arrayType;
protected ScalarResult $arrayResult;
/** @var IType[] */
protected array $keyTypes;
/** @var Result[] */
protected array $keyResults;
protected AssocResult $result;
protected ?array $keys;
protected ?array $wrappers;
protected function newInput(&$value): Input {
return new Input($value);
protected function resetContext($resetSelectedKey): void {
$context = $this->context;
$context->arrayWrapper->getResult()->reset();
foreach ($context->keyWrappers as $wrapper) {
$wrapper->getResult()->reset();
}
$context->analyzed = false;
$context->normalized = false;
if ($resetSelectedKey) $context->selectedKey = null;
}
function reset(&$array, $arrayKey=null, ?bool $verifix=null): Wrapper {
if ($array instanceof Input) $input = $array;
else $input = $this->newInput($array);
$this->input = $input;
$this->arrayKey = $arrayKey;
$this->analyze();
if ($verifix ?? $this->verifix) $this->verifix();
function reset(&$value, $valueKey=null, ?array $params=null): Wrapper {
$context = $this->context;
if ($value instanceof Input) $input = $value;
else $input = $this->newInput($value);
$context->input = $input;
$context->valueKey = $valueKey;
foreach ($context->keyWrappers as $key => $keyWrapper) {
$keyInput = $input->addKey($valueKey);
$keyWrapper->reset($keyInput, $key, ["analyze" => false]);
}
$this->afterModify($params, true);
return $this;
}
function getKeys(): array {
return $this->keys;
return $this->context->keys;
}
function select($key=null): ScalarWrapper {
$wrapper = $this->wrappers[$key] ?? null;
if ($key !== null) return $wrapper;
throw ValueException::invalid_key($key);
protected function _getWrapper($key): Wrapper {
if ($key === null) return $this->context->arrayWrapper;
$wrapper = $context->keyWrappers[$key] ?? null;
if ($wrapper === null) throw ValueException::invalid_key($key);
return $wrapper;
}
/** @param Result[] $results */
function verifix(?bool $throw=null, ?array &$results=null): bool {
/** @param string|int|null $key */
function select($key=null): Wrapper {
$wrapper = $this->_getWrapper($key);
$this->context->selectedKey = $key;
return $wrapper;
}
function getResult(): AssocResult {
return $this->result;
/**
* @param AssocWrapperContext $context
* @param AssocWrapper $wrapper
*/
static function _analyze(WrapperContext $context, Wrapper $wrapper, ?array $params): int {
if ($context->ensureArray) {
$valueKey = $context->valueKey;
$array = $context->input->get($valueKey);
if ($array === null) $context->input->set([], $valueKey);
}
$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()) {
$what = ref_analyze::INVALID;
$result->addInvalidMessage($keyWrapper);
}
}
return $what;
}
function isPresent(): bool {
return $this->result->present;
/**
* @param AssocWrapperContext $context
* @param AssocWrapper $wrapper
*/
static function _normalize(WrapperContext $context, Wrapper $wrapper, ?array $params): bool {
$modified = ScalarWrapper::_normalize($context, $wrapper, $params);
foreach ($context->keyWrappers as $keyWrapper) {
if ($keyWrapper->normalize($params)) $modified = true;
}
return $modified;
}
function getType(): IType {
return $this->arrayType;
function getResult($key=false): Result {
if ($key === false) $key = $this->context->selectedKey;
return $this->_getWrapper($key)->getResult();
}
function isAvailable(): bool {
return $this->result->available;
function getType($key=false): IType {
if ($key === false) $key = $this->context->selectedKey;
return $this->_getWrapper($key)->getType();
}
function isValid(): bool {
return $this->result->valid;
function get($default=null, $key=false) {
$context = $this->context;
if (!$context->arrayWrapper->isAvailable()) return $default;
if ($key === false) $key = $context->selectedKey;
return $this->_getWrapper($key)->get($default);
}
function isNormalized(): bool {
return $this->result->normalized;
}
function get($default=null) {
if ($this->result->available) return $this->input->get($this->arrayKey);
else return $default;
}
function set($value, ?bool $verifix=null): AssocWrapper {
$this->input->set($value, $this->arrayKey);
$this->analyze();
if ($verifix ?? $this->verifix) $this->verifix();
function set($value, ?array $params=null, $key=false): Wrapper {
$context = $this->context;
if ($key === false) $key = $context->selectedKey;
$this->_getWrapper($key)->set($value);
return $this;
}
function unset(?bool $verifix=null): AssocWrapper {
$this->input->unset($this->arrayKey);
$this->analyze();
if ($verifix ?? $this->verifix) $this->verifix();
function unset(?array $params=null, $key=false): Wrapper {
$context = $this->context;
if ($key === false) $key = $context->selectedKey;
$this->_getWrapper($key)->unset();
return $this;
}
function format($format = null): string {
// TODO: Implement format() method.
}
function ensureKeys(): bool {
}
function orderKeys(): bool {
function format($format=null, $key=false): string {
$context = $this->context;
if ($key === false) $key = $context->selectedKey;
return $this->_getWrapper($key)->format($format);
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace nur\sery\wip\schema\_assoc;
use nur\sery\wip\schema\_scalar\ScalarWrapper;
use nur\sery\wip\schema\input\Input;
use nur\sery\wip\schema\Schema;
use nur\sery\wip\schema\Wrapper;
use nur\sery\wip\schema\WrapperContext;
class AssocWrapperContext extends WrapperContext {
const DEFAULT_ENSURE_ARRAY = false;
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);
$this->ensureArray = $params["ensure_array"] ?? self::DEFAULT_ENSURE_ARRAY;
$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 */
public array $keys;
/** @var Wrapper[] */
public array $keyWrappers;
}

View File

@ -2,6 +2,7 @@
namespace nur\sery\wip\schema\_list;
use nulib\ref\schema\ref_schema;
use nulib\ValueException;
use nur\sery\wip\schema\Schema;
use nur\sery\wip\schema\Wrapper;
@ -34,7 +35,7 @@ class ListSchema extends Schema {
"schema" => $definition[0],
];
}
self::_normalize($definition, $definitionKey);
self::_normalize_definition($definition, $definitionKey);
self::_ensure_nature($definition, "list", "array");
return $definition;
}
@ -55,12 +56,26 @@ class ListSchema extends Schema {
return true;
}
const KEYS = [null];
function getKeys(): array {
return self::KEYS;
}
public function getSchema($key): Schema {
if ($key !== null) throw ValueException::invalid_key($key);
return $this;
}
protected function newWrapper(): ListWrapper {
return new ListWrapper($this);
}
function getWrapper(&$value=null, $valueKey=null, ?Wrapper &$wrapper=null): ListWrapper {
function getWrapper(&$value=null, $valueKey=null, ?array $params = null, ?Wrapper &$wrapper=null): ListWrapper {
# si pas de valeur ni de wrapper, pas de vérification et donc pas d'exception
# cf le code similaire dans ScalarWrapper::__construct()
$verifix = $value !== null || $wrapper !== null;
if (!($wrapper instanceof ListWrapper)) $wrapper = $this->newWrapper();
return $wrapper->reset($value, $valueKey);
return $wrapper->reset($value, $valueKey, $verifix);
}
}

View File

@ -6,6 +6,8 @@ use nulib\ref\schema\ref_analyze;
use nulib\ref\schema\ref_schema;
use nulib\ValueException;
use nur\sery\wip\schema\Result;
use nur\sery\wip\schema\Schema;
use nur\sery\wip\schema\Wrapper;
use Throwable;
/**
@ -15,7 +17,7 @@ class ScalarResult extends Result {
function isScalar(?ScalarResult &$result=null): bool { $result = $this; return true; }
function getKeys(): array {
return [null];
return ScalarSchema::KEYS;
}
function select($key): Result {
@ -23,8 +25,7 @@ class ScalarResult extends Result {
return $this;
}
/** @var array */
protected $result;
protected array $result;
function reset(): void {
$this->result = array_merge(
@ -46,26 +47,13 @@ class ScalarResult extends Result {
$this->result[$name] = $value;
}
protected static function replace_key(string &$message, ?string $key): void {
if ($key) {
$message = str_replace("{key}", $key, $message);
} else {
$message = str_replace("{key}: ", "", $message);
$message = str_replace("cette valeur", "la valeur", $message);
}
}
protected static function replace_orig(string &$message, $origValue): void {
$message = str_replace("{orig}", strval($origValue), $message);
}
protected function getMessage(string $key, ScalarSchema $schema): string {
protected function getMessage(string $key, Schema $schema): string {
$message = cl::get($schema->messages, $key);
if ($message !== null) return $message;
return cl::get(ref_schema::MESSAGES, $key);
}
function setMissing(ScalarSchema $schema): int {
function setMissing( Schema $schema): int {
$this->resultAvailable = true;
$this->present = false;
$this->available = false;
@ -75,15 +63,13 @@ class ScalarResult extends Result {
$this->normalized = true;
return ref_analyze::NORMALIZED;
} else {
$messageKey = $this->messageKey = "missing";
$message = $this->getMessage($messageKey, $schema);
self::replace_key($message, $schema->name);
$this->message = $message;
$this->messageKey = $messageKey = "missing";
$this->message = $this->getMessage($messageKey, $schema);
return ref_analyze::MISSING;
}
}
function setUnavailable(ScalarSchema $schema): int {
function setUnavailable( Schema $schema): int {
$this->resultAvailable = true;
$this->present = true;
$this->available = false;
@ -93,15 +79,13 @@ class ScalarResult extends Result {
$this->normalized = true;
return ref_analyze::NORMALIZED;
} else {
$messageKey = $this->messageKey = "unavailable";
$message = $this->getMessage($messageKey, $schema);
self::replace_key($message, $schema->name);
$this->message = $message;
$this->messageKey = $messageKey = "unavailable";
$this->message = $this->getMessage($messageKey, $schema);
return ref_analyze::UNAVAILABLE;
}
}
function setNull(ScalarSchema $schema): int {
function setNull( Schema $schema): int {
$this->resultAvailable = true;
$this->present = true;
$this->available = true;
@ -111,33 +95,53 @@ class ScalarResult extends Result {
$this->normalized = true;
return ref_analyze::NORMALIZED;
} else {
$messageKey = $this->messageKey = "null";
$message = $this->getMessage($messageKey, $schema);
self::replace_key($message, $schema->name);
$this->message = $message;
$this->messageKey = $messageKey = "null";
$this->message = $this->getMessage($messageKey, $schema);
return ref_analyze::NULL;
}
}
function setInvalid($value, ScalarSchema $schema, ?Throwable $t=null): int {
function setInvalid($value, Schema $schema, ?Throwable $exception=null): int {
$this->resultAvailable = true;
$this->present = true;
$this->available = true;
$this->null = false;
$this->valid = false;
$this->origValue = $value;
$messageKey = $this->messageKey = "invalid";
$this->messageKey = $messageKey = "invalid";
$message = $this->getMessage($messageKey, $schema);
self::replace_key($message, $schema->name);
self::replace_orig($message, $schema->orig);
if ($t !== null) {
$tmessage = ValueException::get_message($t);
if ($tmessage) $message .= ": $tmessage";
if ($exception !== null) {
$tmessage = ValueException::get_message($exception);
if ($tmessage) $message = $tmessage;
}
$this->message = $message;
$this->exception = $exception;
return ref_analyze::INVALID;
}
function addInvalidMessage(Wrapper $wrapper): void {
$this->resultAvailable = true;
$this->present = true;
$this->available = true;
$this->null = false;
$this->valid = false;
$this->messageKey = "invalid";
$result = $wrapper->getResult();
$resultException = $result->exception;
$resultMessage = $result->message;
if ($resultException !== null) {
$tmessage = ValueException::get_message($resultException);
if ($tmessage) {
if ($resultMessage !== null) $resultMessage .= ": ";
$resultMessage .= $tmessage;
}
}
$message = $this->message;
if ($message) $message .= "\n";
$message .= $resultMessage;
$this->message = $message;
}
function setValid($normalizedValue=null): int {
$this->resultAvailable = true;
$this->present = true;
@ -159,6 +163,10 @@ class ScalarResult extends Result {
}
function throw(bool $throw): void {
if ($throw) throw new ValueException($this->message);
if ($throw) {
$exception = $this->exception;
if ($exception !== null) throw $exception;
else throw new ValueException($this->message);
}
}
}

View File

@ -1,41 +1,19 @@
<?php
namespace nur\sery\wip\schema\_scalar;
use nulib\cl;
use nulib\ref\schema\ref_schema;
use nulib\ValueException;
use nur\sery\wip\schema\Schema;
use nur\sery\wip\schema\types\IType;
use nur\sery\wip\schema\Wrapper;
/**
* Class ScalarSchema
*
* @property-read array|IType $type
* @property-read mixed $default
* @property-read string|null $title
* @property-read bool $required
* @property-read bool $nullable
* @property-read string|array|null $desc
* @property-read callable|null $analyzerFunc
* @property-read callable|null $extractorFunc
* @property-read callable|null $parserFunc
* @property-read callable|null $normalizerFunc
* @property-read array|null $messages
* @property-read callable|null $formatterFunc
* @property-read mixed $format
* @property-read array $nature
* @property-read array|null $schema
* @property-read string|int|null $name
* @property-read string|array|null $pkey
* @property-read string|null $header
* @property-read bool|null $composite
*/
class ScalarSchema extends Schema {
/** @var array meta-schema d'un schéma de nature scalaire */
const METASCHEMA = ref_schema::SCALAR_METASCHEMA;
/**
* indiquer si $definition est une définition de schéma scalaire que
* {@link normalize()} pourrait normaliser
* {@link normalize_definition()} pourrait normaliser
*/
static function isa_definition($definition): bool {
# chaine ou null
@ -64,8 +42,9 @@ class ScalarSchema extends Schema {
return $haveIndex0 && $count > 1;
}
static function normalize($definition, $definitionKey=null): array {
self::_normalize($definition, $definitionKey);
static function normalize_definition($definition, $definitionKey=null): array {
$natureMetaschema = array_merge(ref_schema::NATURE_METASCHEMA, ref_schema::SCALAR_NATURE_METASCHEMA);
self::_normalize_definition($definition, $definitionKey, $natureMetaschema);
self::_ensure_nature($definition, "scalar");
return $definition;
}
@ -73,7 +52,7 @@ class ScalarSchema extends Schema {
function __construct($definition=null, $definitionKey=null, bool $normalize=true) {
if ($definition === null) $definition = static::SCHEMA;
if ($normalize) {
$definition = self::normalize($definition, $definitionKey);
$definition = self::normalize_definition($definition, $definitionKey);
$this->_definition = $definition;
self::_ensure_type($definition);
self::_ensure_schema_instances($definition);
@ -86,27 +65,27 @@ class ScalarSchema extends Schema {
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;
}
protected function newWrapper(): ScalarWrapper {
return new ScalarWrapper($this);
}
function getWrapper(&$value=null, $valueKey=null, ?Wrapper &$wrapper=null): ScalarWrapper {
function getWrapper(&$value=null, $valueKey=null, ?array $params=null, ?Wrapper &$wrapper=null): ScalarWrapper {
# si pas de valeur ni de wrapper, pas de vérification et donc pas d'exception
# cf le code similaire dans ScalarWrapper::__construct()
$verifix = $value !== null || $wrapper !== null;
$dontAnalyze = $value === null && $wrapper === null;
if (!($wrapper instanceof ScalarWrapper)) $wrapper = $this->newWrapper();
return $wrapper->reset($value, $valueKey, $verifix);
if ($params !== null) $wrapper->resetParams($params);
return $wrapper->reset($value, $valueKey, $dontAnalyze? ["analyze" => false]: null);
}
#############################################################################
# key & properties
const _PROPERTY_PKEYS = [
"analyzerFunc" => "analyzer_func",
"extractorFunc" => "extractor_func",
"parserFunc" => "parser_func",
"normalizerFunc" => "normalizer_func",
"formatterFunc" => "formatter_func",
"nature" => ["", 0],
];
}

View File

@ -4,67 +4,46 @@ namespace nur\sery\wip\schema\_scalar;
use nulib\php\func;
use nulib\ref\schema\ref_analyze;
use nulib\ValueException;
use nur\sery\wip\schema\WrapperContext;
use nur\sery\wip\schema\input\Input;
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;
use nur\sery\wip\schema\Wrapper;
use nur\sery\wip\schema\WrapperContext;
/**
* Class ScalarWrapper
*
* @method ScalarWrapper reset(&$value, $valueKey=null, ?array $params=null)
* @method ScalarResult getResult($key=false)
* @method self set($value, ?array $params=null, $key=false)
* @method self unset(?array $params=null, $key=false)
*/
class ScalarWrapper extends Wrapper {
function __construct(ScalarSchema $schema, &$value=null, $valueKey=null, ?array $params=null) {
$verifix = $params["verifix"] ?? true;
function __construct(Schema $schema, &$value=null, $valueKey=null, ?array $params=null, ?WrapperContext $context=null) {
if ($context === null) $context = new WrapperContext($schema, null, null, $params);
$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;
if ($value !== null && $throw === null) {
# Si $value est null, ne pas lancer d'exception, parce qu'on considère que
# c'est une initialisation sans conséquences
$throw = true;
}
$this->verifix = $verifix;
$this->throw = $throw ?? false;
$this->schema = $schema;
$this->result = new ScalarResult();
# 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);
$this->throw = $throw ?? true;
$context->throw = $throw ?? true;
}
function isScalar(?ScalarWrapper &$wrapper=null): bool { $wrapper = $this; return true; }
protected bool $verifix;
protected bool $throw;
/** schéma de cette valeur */
protected ScalarSchema $schema;
/** source et destination de la valeur */
protected Input $input;
/** @var string|int|null clé de la valeur dans le tableau destination */
protected $valueKey;
/** type de la valeur après analyse */
protected ?IType $type;
/** résultat de l'analyse de la valeur */
protected ScalarResult $result;
protected function newInput(&$value): Input {
return new Input($value);
}
function reset(&$value, $valueKey=null, ?bool $verifix=null): Wrapper {
if ($value instanceof Input) $input = $value;
else $input = $this->newInput($value);
$this->input = $input;
$this->valueKey = $valueKey;
$this->type = null;
$this->analyze();
if ($verifix ?? $this->verifix) $this->verifix();
return $this;
}
protected WrapperContext $context;
function getKeys(): array {
return [null];
return ScalarSchema::KEYS;
}
/** @param string|int|null $key */
@ -74,7 +53,7 @@ class ScalarWrapper extends Wrapper {
}
/** analyser la valeur et résoudre son type */
protected function analyze0(WrapperContext $context): int {
protected static function _analyze0(WrapperContext $context): int {
/** @var ScalarSchema $schema */
$schema = $context->schema;
$input = $context->input;
@ -112,7 +91,7 @@ class ScalarWrapper extends Wrapper {
$args = $name;
$name = $key;
}
$type = types::get($schema->nullable, $name, $args, $this->schema->getDefinition());
$type = types::get($schema->nullable, $name, $args, $schema->getDefinition());
if ($firstType === null) $firstType = $type;
$types[] = $type;
if ($type->isAvailable($input, $valueKey)) {
@ -140,7 +119,7 @@ class ScalarWrapper extends Wrapper {
$type = $firstType;
}
}
$context->type = $this->type = $type;
$context->type = $type;
if (!$type->isAvailable($input, $valueKey)) {
if ($default !== null) {
@ -165,25 +144,28 @@ class ScalarWrapper extends Wrapper {
}
}
protected function analyze(): int {
$schema = $this->schema;
$input = $this->input;
$valueKey = $this->valueKey;
$result = $this->result;
$result->reset();
$context = new WrapperContext($schema, $this, $input, $valueKey, $result);
/**
* @param ScalarWrapper $wrapper
*/
static function _analyze(WrapperContext $context, Wrapper $wrapper, ?array $params): int {
/** @var ScalarSchema $schema */
$schema = $context->schema;
$input = $context->input;
$valueKey = $context->valueKey;
/** @var ScalarResult $result */
$result = $context->result;
/** @var func $analyzerFunc */
$analyzerFunc = $schema->analyzerFunc;
if ($analyzerFunc !== null) $what = $analyzerFunc->invoke([$context]);
else $what = $this->analyze0($context);
if ($analyzerFunc !== null) $what = $analyzerFunc->invoke([$context, $wrapper]);
else $what = self::_analyze0($context);
if ($what !== ref_analyze::STRING) return $what;
$value = $context->value;
try {
/** @var func $extractorFunc */
$extractorFunc = $schema->extractorFunc;
if ($extractorFunc !== null) $extracted = $extractorFunc->invoke([$value, $context]);
if ($extractorFunc !== null) $extracted = $extractorFunc->invoke([$value, $context, $wrapper]);
else $extracted = $context->type->extract($value);
$context->value = $extracted;
} catch (ValueException $e) {
@ -194,7 +176,7 @@ class ScalarWrapper extends Wrapper {
try {
/** @var func $parserFunc */
$parserFunc = $schema->parserFunc;
if ($parserFunc !== null) $parsed = $parserFunc->invoke([$extracted, $context]);
if ($parserFunc !== null) $parsed = $parserFunc->invoke([$extracted, $context, $wrapper]);
else $parsed = $context->type->parse($extracted);
$context->value = $parsed;
} catch (ValueException $e) {
@ -211,107 +193,53 @@ class ScalarWrapper extends Wrapper {
}
}
function verifix(?bool $throw=null): bool {
$result = $this->result;
$valueKey = $this->valueKey;
$verifix = false;
/**
* @param ScalarWrapper $wrapper
*/
static function _normalize(WrapperContext $context, Wrapper $wrapper, ?array $params): bool {
/** @var ScalarSchema $schema */
$schema = $context->schema;
$input = $context->input;
$valueKey = $context->valueKey;
/** @var ScalarResult $result */
$result = $context->result;
$normalize = false;
$modified = false;
if ($result->resultAvailable) {
if ($result->null) {
# forcer la valeur null, parce que la valeur actuelle est peut-être une
# valeur assimilée à null
$this->input->set(null, $valueKey);
$input->set(null, $valueKey);
} elseif ($result->valid && !$result->normalized) {
$normalizedValue = $result->normalizedValue;
if ($normalizedValue !== null) {
# la valeur normalisée est disponible
$this->input->set($normalizedValue);
$input->set($normalizedValue, $valueKey);
$result->normalizedValue = null;
$modified = true;
} else {
# normaliser la valeur
$verifix = true;
$normalize = true;
}
}
} else {
$verifix = true;
$normalize = true;
}
if ($verifix) {
$value = $this->input->get($valueKey);
$schema = $this->schema;
if ($normalize) {
$value = $input->get($valueKey);
/** @var func $normalizerFunc */
$normalizerFunc = $schema->normalizerFunc;
if ($normalizerFunc !== null) {
$context = new WrapperContext($schema, $this, $this->input, $valueKey, $result);
$orig = $value;
$value = $normalizerFunc->invoke([$orig, $context]);
$value = $normalizerFunc->invoke([$orig, $context, $wrapper]);
$modified = $value !== $orig;
} else {
$modified = $this->type->verifix($value, $result, $this->schema);
$modified = $context->type->normalize($value, $result, $schema);
}
if ($result->valid) $this->input->set($value, $valueKey);
if ($result->valid) $input->set($value, $valueKey);
}
if (!$result->valid) $result->throw($throw ?? $this->throw);
return $modified;
}
function getResult(): ScalarResult {
return $this->result;
}
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->valueKey);
else return $default;
}
function set($value, ?bool $verifix=null): ScalarWrapper {
$this->input->set($value, $this->valueKey);
$this->analyze();
if ($verifix ?? $this->verifix) $this->verifix();
return $this;
}
function unset(?bool $verifix=null): ScalarWrapper {
$this->input->unset($this->valueKey);
$this->analyze();
if ($verifix ?? $this->verifix) $this->verifix();
return $this;
}
function format($format=null): string {
$value = $this->input->get($this->valueKey);
/** @var func $formatterFunc */
$formatterFunc = $this->schema->formatterFunc;
if ($formatterFunc !== null) {
# la fonction formatter n'a pas forcément accès au format de la définition
# le lui fournir ici
$format ??= $this->schema->format;
return $formatterFunc->invoke([$value, $format]);
} else {
# on assume que le type a été initialisé avec le format de la définition
# le cas échéant
return $this->type->format($value, $format);
}
}
}

View File

@ -5,7 +5,6 @@ namespace nur\sery\wip\schema\input;
# construire des querystring et paramètres de formulaires
use nur\sery\wip\php\access\FormAccess;
use nur\sery\wip\php\access\IAccess;
use nur\sery\wip\php\access\KeyAccess;
use nur\sery\wip\php\access\ShadowAccess;
/**
@ -18,15 +17,14 @@ use nur\sery\wip\php\access\ShadowAccess;
class FormInput extends Input {
const ALLOW_EMPTY = false;
protected function formAccess($key): IAccess {
return new FormAccess($key, [
"allow_empty" => $this->allowEmpty,
]);
function __construct(&$dest=null, ?array $params=null) {
parent::__construct($dest, $params);
$this->access = new ShadowAccess($this->formAccess($this->access), $this->access);
}
protected function access($key): IAccess {
return $this->keyAccess[$key] ??= new ShadowAccess($this->formAccess($key), new KeyAccess($this->value, $key, [
"allow_empty" => $this->allowEmpty,
]));
protected function formAccess(IAccess $access): IAccess {
return new FormAccess(null, [
"allow_empty" => $access->isAllowEmpty(),
]);
}
}

View File

@ -11,9 +11,9 @@ use nur\sery\wip\php\access\IAccess;
* une référence
*/
class GetInput extends FormInput {
protected function formAccess($key): IAccess {
return new GetAccess($key, [
"allow_empty" => $this->allowEmpty,
protected function formAccess(IAccess $access): IAccess {
return new GetAccess(null, [
"allow_empty" => $access->isAllowEmpty(),
]);
}
}

View File

@ -1,9 +1,10 @@
<?php
namespace nur\sery\wip\schema\input;
use nulib\StateException;
use nur\sery\wip\php\access\IAccess;
use nur\sery\wip\php\access\KeyAccess;
use nur\sery\wip\php\access\ValueAccess;
use nur\sery\wip\php\access\PropertyAccess;
/**
* Class Input: accès à une valeur
@ -13,54 +14,62 @@ use nur\sery\wip\php\access\ValueAccess;
class Input {
const ALLOW_EMPTY = true;
function __construct(&$value=null, ?array $params=null) {
$this->value =& $value;
$this->allowEmpty = $params["allow_empty"] ?? static::ALLOW_EMPTY;
const ACCESS_AUTO = 0, ACCESS_KEY = 1, ACCESS_PROPERTY = 2;
private static function unexpected_access_type(): StateException {
return StateException::unexpected_state("access_type");
}
/** @var mixed */
protected $value;
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;
}
/**
* @var bool comment considérer une chaine vide: "" si allowEmpty, null sinon
*/
protected $allowEmpty;
protected ?ValueAccess $valueAccess = null;
protected ?array $keyAccess = null;
protected function access($key): IAccess {
if ($key === null) {
return $this->valueAccess ??= new ValueAccess($this->value, [
$allowEmpty = $params["allow_empty"] ?? static::ALLOW_EMPTY;
if ($accessType == self::ACCESS_PROPERTY) {
$this->access = new PropertyAccess($dest, null, [
"allow_empty" => $allowEmpty,
"allow_null" => true,
]);
} elseif ($accessType == self::ACCESS_KEY) {
$this->access = new KeyAccess($dest, null, [
"allow_empty" => $allowEmpty,
"allow_null" => true,
"allow_empty" => $this->allowEmpty,
]);
} else {
return $this->keyAccess[$key] ??= new KeyAccess($this->value, $key, [
"allow_empty" => $this->allowEmpty,
]);
throw self::unexpected_access_type();
}
}
protected IAccess $access;
/** tester si la valeur existe sans tenir compte de $allowEmpty */
function isPresent($key=null): bool {
return $this->access($key)->exists();
return $this->access->resetKey($key)->exists();
}
/** tester si la valeur est disponible en tenant compte de $allowEmpty */
function isAvailable($key=null): bool {
return $this->access($key)->available();
return $this->access->resetKey($key)->available();
}
function get($key=null) {
return $this->access($key)->get();
return $this->access->resetKey($key)->get();
}
function set($value, $key=null): void {
$this->access($key)->set($value);
$this->access->resetKey($key)->set($value);
}
function unset($key=null): void {
$this->access($key)->del();
$this->access->resetKey($key)->del();
}
function addKey($key): Input {
if ($key === null) return $this;
$input = clone $this;
$input->access = $this->access->addKey($key);
return $input;
}
}

View File

@ -11,9 +11,9 @@ use nur\sery\wip\php\access\PostAccess;
* une référence
*/
class PostInput extends FormInput {
protected function formAccess($key): IAccess {
return new PostAccess($key, [
"allow_empty" => $this->allowEmpty,
protected function formAccess(IAccess $access): IAccess {
return new PostAccess(null, [
"allow_empty" => $access->isAllowEmpty(),
]);
}
}

View File

@ -23,7 +23,7 @@ interface IType {
/**
* @return string la classe des objets gérés par ce format: le type attendu
* par {@link format()} et le type retourné par {@link verifix()}
* par {@link format()} et le type retourné par {@link normalize()}
*
* Les valeurs "mixed", "bool", "float", "int", "string" et "array" peuvent
* aussi être retournées, bien qu'elles ne soient pas à proprement parler des
@ -98,15 +98,18 @@ interface IType {
function parse(string $value);
/**
* analyser, corriger éventuellement et normaliser la valeur
*
* NB: si $value est un string. elle doit avoir déjà été traitée au préalable
* normaliser la valeur. elle *doit* déjà être valide.
* Si $value est un string. elle *doit* avoir déjà été traitée au préalable
* par extract() et parse()
*
* si la valeur était déjà normalisée, ou si une erreur s'est produite,
* retourner false.
* - si $result indique que la valeur est déjà normalisée, cette méthode ne
* fait rien
* - si la valeur était déjà normalisée, mettre à jour $result pour indiquer
* que la valeur est normalisée et retourner false
* - sinon, retourner true pour indiquer qu'il a fallut normaliser la valeur.
* $result n'est pas modifié
*/
function verifix(&$value, Result $result, Schema $schema): bool;
function normalize(&$value, Result $result, Schema $schema): bool;
/**
* formatter la valeur pour affichage. si $value n'est pas null, elle est

View File

@ -37,7 +37,7 @@ class tarray extends _tstring {
function isValid($value, ?bool &$normalized=null): bool {
$normalized = is_array($value);
return is_scalar($value) || is_array($value);
return $normalized || is_scalar($value);
}
function parse(string $value) {
@ -49,23 +49,14 @@ class tarray extends _tstring {
* @var ScalarResult $result
* @var ScalarSchema $schema
*/
function verifix(&$value, Result $result, Schema $schema): bool {
if (is_array($value)) {
function normalize(&$value, Result $result, Schema $schema): bool {
if ($result->normalized) {
} elseif (is_array($value)) {
$result->setNormalized();
return false;
} elseif (is_string($value)) {
try {
$value = $this->parse($value);
$result->setValid();
return true;
} catch (ValueException $e) {
}
} elseif (is_scalar($value)) {
$value = cl::with($value);
$result->setValid();
return true;
}
$result->setInvalid($value, $schema);
return false;
}

View File

@ -99,23 +99,14 @@ class tbool extends _tformatable {
* @var ScalarResult $result
* @var ScalarSchema $schema
*/
function verifix(&$value, Result $result, Schema $schema): bool {
if (is_bool($value)) {
function normalize(&$value, Result $result, Schema $schema): bool {
if ($result->normalized) {
} elseif (is_bool($value)) {
$result->setNormalized();
return false;
} elseif (is_string($value)) {
try {
$value = $this->parse($value);
$result->setValid();
return true;
} catch (ValueException $e) {
}
} elseif (is_scalar($value)) {
$value = boolval($value);
$result->setValid();
return true;
}
$result->setInvalid($value, $schema);
return false;
}

View File

@ -27,7 +27,7 @@ class tcallable extends _tsimple {
}
function isValid($value, ?bool &$normalized=null): bool {
$normalized = is_callable($value);
$normalized = $value instanceof func;
return func::check($value);
}
@ -43,23 +43,14 @@ class tcallable extends _tsimple {
* @var ScalarResult $result
* @var ScalarSchema $schema
*/
function verifix(&$value, Result $result, Schema $schema): bool {
if ($value instanceof func) {
function normalize(&$value, Result $result, Schema $schema): bool {
if ($result->normalized) {
} elseif ($value instanceof func) {
$result->setNormalized();
return false;
} elseif (is_callable($value)) {
$value = func::with($value);
$result->setNormalized();
return true;
} elseif (is_string($value)) {
try {
$value = $this->parse($value);
$result->setValid();
return true;
} catch (ValueException $e) {
}
}
$result->setInvalid($value, $schema);
return false;
}

View File

@ -25,7 +25,7 @@ abstract class tcontent extends _tunion {
function isValid($value, ?bool &$normalized=null): bool {
$normalized = is_string($value) || is_array($value);
return is_scalar($value) || is_array($value);
return $normalized || is_scalar($value);
}
function parse(string $value) {
@ -36,18 +36,15 @@ abstract class tcontent extends _tunion {
* @var ScalarResult $result
* @var ScalarSchema $schema
*/
function verifix(&$value, Result $result, Schema $schema): bool {
if (is_string($value) || is_array($value)) {
function normalize(&$value, Result $result, Schema $schema): bool {
if ($result->normalized) {
} elseif (is_string($value) || is_array($value)) {
$result->setNormalized();
return false;
} elseif (is_scalar($value)) {
$value = strval($value);
$result->setValid();
return true;
} else {
$result->setInvalid($value, $schema);
return false;
}
return false;
}
function format($value, $format=null): string {

View File

@ -43,23 +43,14 @@ class tfloat extends _tformatable {
* @var ScalarResult $result
* @var ScalarSchema $schema
*/
function verifix(&$value, Result $result, Schema $schema): bool {
if (is_float($value)) {
function normalize(&$value, Result $result, Schema $schema): bool {
if ($result->normalized) {
} elseif (is_float($value)) {
$result->setNormalized();
return false;
} elseif (is_string($value)) {
try {
$value = $this->parse($value);
$result->setValid();
return true;
} catch (ValueException $e) {
}
} elseif (is_scalar($value)) {
$value = floatval($value);
$result->setValid();
return true;
}
$result->setInvalid($value, $schema);
return false;
}
}

View File

@ -29,8 +29,8 @@ class tgeneric extends _tsimple {
}
function isValid($value, ?bool &$normalized=null): bool {
$normalized = true;
return $value instanceof $this->class;
$normalized = $value instanceof $this->class;
return $normalized;
}
function parse(string $value) {
@ -41,8 +41,8 @@ class tgeneric extends _tsimple {
* @var ScalarResult $result
* @var ScalarSchema $schema
*/
function verifix(&$value, Result $result, Schema $schema): bool {
$result->setNormalized();
function normalize(&$value, Result $result, Schema $schema): bool {
if (!$result->normalized) $result->setNormalized();
return false;
}

View File

@ -45,23 +45,14 @@ class tint extends _tformatable {
* @var ScalarResult $result
* @var ScalarSchema $schema
*/
function verifix(&$value, Result $result, Schema $schema): bool {
if (is_int($value)) {
function normalize(&$value, Result $result, Schema $schema): bool {
if ($result->normalized) {
} elseif (is_int($value)) {
$result->setNormalized();
return false;
} elseif (is_string($value)) {
try {
$value = $this->parse($value);
$result->setValid();
return true;
} catch (ValueException $e) {
}
} elseif (is_scalar($value)) {
$value = intval($value);
$result->setValid();
return true;
}
$result->setInvalid($value, $schema);
return false;
}
}

View File

@ -25,7 +25,7 @@ class tkey extends _tunion {
function isValid($value, ?bool &$normalized=null): bool {
$normalized = is_string($value) || is_int($value);
return is_scalar($value);
return $normalized || is_scalar($value);
}
function parse(string $value) {
@ -36,18 +36,15 @@ class tkey extends _tunion {
* @var ScalarResult $result
* @var ScalarSchema $schema
*/
function verifix(&$value, Result $result, Schema $schema): bool {
if (is_string($value) || is_int($value)) {
function normalize(&$value, Result $result, Schema $schema): bool {
if ($result->normalized) {
} elseif (is_string($value) || is_int($value)) {
$result->setNormalized();
return false;
} elseif (is_scalar($value)) {
$value = strval($value);
$result->setValid();
return true;
} else {
$result->setInvalid($value, $schema);
return false;
}
return false;
}
function format($value, $format=null): string {

View File

@ -35,8 +35,8 @@ class tmixed extends _tsimple {
* @var ScalarResult $result
* @var ScalarSchema $schema
*/
function verifix(&$value, Result $result, Schema $schema): bool {
$result->setNormalized();
function normalize(&$value, Result $result, Schema $schema): bool {
if (!$result->normalized) $result->setNormalized();
return false;
}

View File

@ -30,7 +30,7 @@ class tpkey extends _tunion {
function isValid($value, ?bool &$normalized=null): bool {
$normalized = is_string($value) || is_int($value) || is_array($value);
return is_scalar($value) || is_array($value);
return $normalized || is_scalar($value);
}
function parse(string $value) {
@ -41,18 +41,15 @@ class tpkey extends _tunion {
* @var ScalarResult $result
* @var ScalarSchema $schema
*/
function verifix(&$value, Result $result, Schema $schema): bool {
if (is_string($value) || is_int($value) || is_array($value)) {
function normalize(&$value, Result $result, Schema $schema): bool {
if ($result->normalized) {
} elseif (is_string($value) || is_int($value) || is_array($value)) {
$result->setNormalized();
return false;
} elseif (is_scalar($value)) {
$value = strval($value);
$result->setValid();
return true;
} else {
$result->setInvalid($value, $schema);
return false;
}
return false;
}
function format($value, $format=null): string {

View File

@ -44,18 +44,15 @@ class trawstring extends _tstring {
* @var ScalarResult $result
* @var ScalarSchema $schema
*/
function verifix(&$value, Result $result, Schema $schema): bool {
if (is_string($value)) {
function normalize(&$value, Result $result, Schema $schema): bool {
if ($result->normalized) {
} elseif (is_string($value)) {
$result->setNormalized();
return false;
} elseif (is_scalar($value)) {
$value = strval($value);
$result->setValid();
return true;
} else {
$result->setInvalid($value, $schema);
return false;
}
return false;
}
function format($value, $format=null): string {

View File

@ -5,7 +5,69 @@ use nulib\tests\TestCase;
use stdClass;
class KeyAccessTest extends TestCase {
function testAccess() {
function testValueAccess() {
$default = new stdClass();
#
$i = null;
$a = new KeyAccess($i);
self::assertFalse($a->exists());
self::assertFalse($a->available());
self::assertSame($default, $a->get($default));
$i = false;
$a = new KeyAccess($i);
self::assertTrue($a->exists());
self::assertTrue($a->available());
self::assertSame(false, $a->get($default));
$i = "";
$a = new KeyAccess($i);
self::assertTrue($a->exists());
self::assertTrue($a->available());
self::assertSame("", $a->get($default));
#
$i = null;
$a = new KeyAccess($i, null, ["allow_null" => false]);
self::assertFalse($a->exists());
self::assertFalse($a->available());
self::assertSame($default, $a->get($default));
$i = null;
$a = new KeyAccess($i, null, ["allow_null" => true]);
self::assertTrue($a->exists());
self::assertTrue($a->available());
self::assertSame(null, $a->get($default));
#
$i = false;
$a = new KeyAccess($i, null, ["allow_false" => false]);
self::assertTrue($a->exists());
self::assertFalse($a->available());
self::assertSame($default, $a->get($default));
$i = false;
$a = new KeyAccess($i, null, ["allow_false" => true]);
self::assertTrue($a->exists());
self::assertTrue($a->available());
self::assertSame(false, $a->get($default));
#
$i = "";
$a = new KeyAccess($i, null, ["allow_empty" => false]);
self::assertTrue($a->exists());
self::assertFalse($a->available());
self::assertSame($default, $a->get($default));
$i = "";
$a = new KeyAccess($i, null, ["allow_empty" => true]);
self::assertTrue($a->exists());
self::assertTrue($a->available());
self::assertSame("", $a->get($default));
}
function testArrayAccess() {
$default = new stdClass();
$array = ["null" => null, "false" => false, "empty" => ""];
@ -63,4 +125,91 @@ class KeyAccessTest extends TestCase {
self::assertTrue($a->available());
self::assertSame("", $a->get($default));
}
private function _ensureAssoc(?array $orig, ?array $expected, array $keys, ?array $params=null) {
$v = $orig; $a = new KeyAccess($v);
$a->ensureAssoc($keys, $params);
self::assertSame($expected, $v);
}
function testEnsureAssoc() {
$keys = ["a", "b", "c"];
$this->_ensureAssoc(null, null, $keys);
$this->_ensureAssoc([], [], $keys);
$this->_ensureAssoc([1], ["a" => 1], $keys);
$this->_ensureAssoc([1, 2, 3], ["a" => 1, "b" => 2, "c" => 3], $keys);
$this->_ensureAssoc([1, 2, 3, 4], [3 => 4, "a" => 1, "b" => 2, "c" => 3], $keys);
$this->_ensureAssoc(["c" => 3, 1], ["c" => 3, "a" => 1], $keys);
$this->_ensureAssoc(["c" => 3, "b" => 2, 1], ["c" => 3, "b" => 2, "a" => 1], $keys);
$this->_ensureAssoc(["c" => 3, "b" => 2, "a" => 1], ["c" => 3, "b" => 2, "a" => 1], $keys);
$this->_ensureAssoc(["a" => 1, 2], ["a" => 1, "b" => 2], $keys);
$this->_ensureAssoc([2, "a" => 1], ["a" => 1, "b" => 2], $keys);
$keys = [0, "a", "b"];
$this->_ensureAssoc([1], [1], $keys);
$this->_ensureAssoc([1, 2], [1, "a" => 2], $keys);
}
private function _ensureKeys(?array $orig, ?array $expected, array $defaults, ?array $params=null) {
$v = $orig; $a = new KeyAccess($v);
$a->ensureKeys($defaults, $params);
self::assertSame($expected, $v);
}
function testEnsureKeys() {
$defaults = ["a" => false, "b" => false, "c" => false];
$this->_ensureKeys(null, ["a" => false, "b" => false, "c" => false], $defaults);
$this->_ensureKeys([], ["a" => false, "b" => false, "c" => false], $defaults);
$this->_ensureKeys(["a" => 1], ["a" => 1, "b" => false, "c" => false], $defaults);
$this->_ensureKeys(["a" => 1, "b" => 2, "c" => 3], ["a" => 1, "b" => 2, "c" => 3], $defaults);
$this->_ensureKeys(["x"], ["x", "a" => false, "b" => false, "c" => false], $defaults);
$this->_ensureKeys(["x", "a" => 1], ["x", "a" => 1, "b" => false, "c" => false], $defaults);
$this->_ensureKeys(["a" => 1, "x"], ["a" => 1, "x", "b" => false, "c" => false], $defaults);
$this->_ensureKeys(["a" => 1, "b" => 2, "c" => 3, "x"], ["a" => 1, "b" => 2, "c" => 3, "x"], $defaults);
}
private function _ensureOrder(?array $orig, ?array $expected, array $keys, ?array $params=null) {
$v = $orig; $a = new KeyAccess($v);
$a->ensureOrder($keys, $params);
self::assertSame($expected, $v);
}
function testEnsureOrder() {
$keys = ["a", "b", "c"];
$this->_ensureOrder(null, null, $keys);
$this->_ensureOrder([], [], $keys);
$this->_ensureOrder([1], [1], $keys);
$this->_ensureOrder(["b" => 2, "a" => 1], ["a" => 1, "b" => 2], $keys);
$this->_ensureOrder(["c" => 3, "a" => 1], ["a" => 1, "c" => 3], $keys);
}
private function _ensureAssocKeysOrder(?array $orig, ?array $expected, array $defaults, ?array $params=null) {
$v = $orig; $a = new KeyAccess($v);
$keys = array_keys($defaults);
$a->ensureAssoc($keys, $params);
$a->ensureKeys($defaults, $params);
$a->ensureOrder($keys, $params);
self::assertSame($expected, $v);
}
function testEnsureAssocKeysOrder() {
$defaults = ["a" => false, "b" => false, "c" => false];
$this->_ensureAssocKeysOrder(null, ["a" => false, "b" => false, "c" => false], $defaults);
$this->_ensureAssocKeysOrder([], ["a" => false, "b" => false, "c" => false], $defaults);
$this->_ensureAssocKeysOrder([1], ["a" => 1, "b" => false, "c" => false], $defaults);
$this->_ensureAssocKeysOrder([1, 2, 3], ["a" => 1, "b" => 2, "c" => 3], $defaults);
$this->_ensureAssocKeysOrder([1, 2, 3, 4], ["a" => 1, "b" => 2, "c" => 3, 4], $defaults);
$this->_ensureAssocKeysOrder([1, 2, 3, 4], ["a" => 1, "b" => 2, "c" => 3, 3 => 4], $defaults, [
"preserve_keys" => true,
]);
$this->_ensureAssocKeysOrder(["c" => 3, 1], ["a" => 1, "b" => false, "c" => 3], $defaults);
$this->_ensureAssocKeysOrder(["c" => 3, "b" => 2, 1], ["a" => 1, "b" => 2, "c" => 3], $defaults);
$this->_ensureAssocKeysOrder(["c" => 3, "b" => 2, "a" => 1], ["a" => 1, "b" => 2, "c" => 3], $defaults);
$this->_ensureAssocKeysOrder(["a" => 1, 2], ["a" => 1, "b" => 2, "c" => false], $defaults);
$this->_ensureAssocKeysOrder([2, "a" => 1], ["a" => 1, "b" => 2, "c" => false], $defaults);
$this->_ensureAssocKeysOrder([1], ["x_a" => 1, "x_b" => false, "x_c" => false], $defaults, [
"key_prefix" => "x_",
]);
}
}

View File

@ -1,69 +0,0 @@
<?php
namespace nur\sery\wip\php\access;
use nulib\tests\TestCase;
use stdClass;
class ValueAccessTest extends TestCase {
function testAccess() {
$default = new stdClass();
#
$i = null;
$a = new ValueAccess($i);
self::assertFalse($a->exists());
self::assertFalse($a->available());
self::assertSame($default, $a->get($default));
$i = false;
$a = new ValueAccess($i);
self::assertTrue($a->exists());
self::assertTrue($a->available());
self::assertSame(false, $a->get($default));
$i = "";
$a = new ValueAccess($i);
self::assertTrue($a->exists());
self::assertTrue($a->available());
self::assertSame("", $a->get($default));
#
$i = null;
$a = new ValueAccess($i, ["allow_null" => false]);
self::assertFalse($a->exists());
self::assertFalse($a->available());
self::assertSame($default, $a->get($default));
$i = null;
$a = new ValueAccess($i, ["allow_null" => true]);
self::assertTrue($a->exists());
self::assertTrue($a->available());
self::assertSame(null, $a->get($default));
#
$i = false;
$a = new ValueAccess($i, ["allow_false" => false]);
self::assertTrue($a->exists());
self::assertFalse($a->available());
self::assertSame($default, $a->get($default));
$i = false;
$a = new ValueAccess($i, ["allow_false" => true]);
self::assertTrue($a->exists());
self::assertTrue($a->available());
self::assertSame(false, $a->get($default));
#
$i = "";
$a = new ValueAccess($i, ["allow_empty" => false]);
self::assertTrue($a->exists());
self::assertFalse($a->available());
self::assertSame($default, $a->get($default));
$i = "";
$a = new ValueAccess($i, ["allow_empty" => true]);
self::assertTrue($a->exists());
self::assertTrue($a->available());
self::assertSame("", $a->get($default));
}
}

View File

@ -7,7 +7,14 @@ use nur\sery\wip\schema\_scalar\ScalarSchemaTest;
class AssocSchemaTest extends TestCase {
const NULL_SCHEMA = [
"" => ["assoc"],
"" => [
"assoc",
"compute_func" => null,
"validate_func" => null,
"ensure_array" => false,
"ensure_keys" => true,
"ensure_order" => true,
],
"schema" => null,
"type" => [null],
"default" => null,
@ -44,7 +51,7 @@ class AssocSchemaTest extends TestCase {
"type" => ["string"], "nullable" => false,
"name" => "a", "pkey" => "a", "header" => "a",
],
]), AssocSchema::normalize(["a" => "string"]));
]), AssocSchema::normalize_definition(["a" => "string"]));
self::assertSame(self::schema([
"type" => ["array"], "nullable" => true,
@ -61,7 +68,7 @@ class AssocSchemaTest extends TestCase {
"type" => ["bool"], "nullable" => false,
"name" => "c", "pkey" => "c", "header" => "c",
],
]), AssocSchema::normalize([
]), AssocSchema::normalize_definition([
"a" => "string",
"b" => "int",
"c" => "bool",
@ -90,6 +97,72 @@ class AssocSchemaTest extends TestCase {
"name" => "c", "pkey" => "c", "header" => "c",
],
]), $schema->getDefinition());
yaml::dump($schema->getDefinition());
//yaml::dump($schema->getDefinition());
}
function testWrapper() {
$schema = new AssocSchema([
"a" => "?string",
"b" => "?int",
"c" => "?bool",
]);
$array = ["a" => " string ", "b" => " 42 ", "c" => false];
$schema->getWrapper($array);
self::assertSame([
"a" => "string",
"b" => 42,
"c" => false,
], $array);
$schema = new AssocSchema([
"a" => "string",
"b" => "int",
"c" => "bool",
]);
$array = ["a" => " string "];
$schema->getWrapper($array);
self::assertSame([
"a" => "string",
"b" => false,
"c" => null,
], $array);
$array = ["c" => false, "a" => " string "];
$schema->getWrapper($array);
self::assertSame([
"a" => "string",
"b" => false,
"c" => false,
], $array);
$array = ["a" => " string "];
$schema->getWrapper($array, null, ["ensure_order" => false]);
self::assertSame([
"a" => "string",
"b" => false,
"c" => null,
], $array);
$array = ["c" => false, "a" => " string "];
$schema->getWrapper($array, null, ["ensure_order" => false]);
self::assertSame([
"c" => false,
"a" => "string",
"b" => false,
], $array);
$array = ["a" => " string "];
$schema->getWrapper($array, null, ["ensure_keys" => false]);
self::assertSame([
"a" => "string",
], $array);
$array = ["c" => false, "a" => " string "];
$schema->getWrapper($array, null, ["ensure_keys" => false]);
self::assertSame([
"a" => "string",
"c" => false,
], $array);
}
}

View File

@ -19,7 +19,11 @@ class ScalarSchemaTest extends TestCase {
"messages" => null,
"formatter_func" => null,
"format" => null,
"" => ["scalar"],
"" => [
"scalar",
"compute_func" => null,
"validate_func" => null,
],
"schema" => null,
"name" => null,
"pkey" => null,
@ -32,33 +36,33 @@ class ScalarSchemaTest extends TestCase {
}
function testNormalize() {
self::assertSame(self::NULL_SCHEMA, ScalarSchema::normalize(null));
self::assertSame(self::NULL_SCHEMA, ScalarSchema::normalize([]));
self::assertSame(self::NULL_SCHEMA, ScalarSchema::normalize([null]));
self::assertSame(self::NULL_SCHEMA, ScalarSchema::normalize_definition(null));
self::assertSame(self::NULL_SCHEMA, ScalarSchema::normalize_definition([]));
self::assertSame(self::NULL_SCHEMA, ScalarSchema::normalize_definition([null]));
self::assertException(SchemaException::class, function () {
ScalarSchema::normalize([[]]);
ScalarSchema::normalize_definition([[]]);
});
self::assertException(SchemaException::class, function () {
ScalarSchema::normalize([[null]]);
ScalarSchema::normalize_definition([[null]]);
});
$string = self::schema(["type" => ["string"], "nullable" => false]);
self::assertSame($string, ScalarSchema::normalize("string"));
self::assertSame($string, ScalarSchema::normalize(["string"]));
self::assertSame($string, ScalarSchema::normalize_definition("string"));
self::assertSame($string, ScalarSchema::normalize_definition(["string"]));
$nstring = self::schema(["type" => ["string"]]);
self::assertSame($nstring, ScalarSchema::normalize(["?string"]));
self::assertSame($nstring, ScalarSchema::normalize(["?string|null"]));
self::assertSame($nstring, ScalarSchema::normalize(["string|null"]));
self::assertSame($nstring, ScalarSchema::normalize([["?string", "null"]]));
self::assertSame($nstring, ScalarSchema::normalize([["string", "null"]]));
self::assertSame($nstring, ScalarSchema::normalize([["string", null]]));
self::assertSame($nstring, ScalarSchema::normalize_definition(["?string"]));
self::assertSame($nstring, ScalarSchema::normalize_definition(["?string|null"]));
self::assertSame($nstring, ScalarSchema::normalize_definition(["string|null"]));
self::assertSame($nstring, ScalarSchema::normalize_definition([["?string", "null"]]));
self::assertSame($nstring, ScalarSchema::normalize_definition([["string", "null"]]));
self::assertSame($nstring, ScalarSchema::normalize_definition([["string", null]]));
$key = self::schema(["type" => ["string", "int"], "nullable" => false]);
self::assertSame($key, ScalarSchema::normalize("string|int"));
self::assertSame($key, ScalarSchema::normalize_definition("string|int"));
$nkey = self::schema(["type" => ["string", "int"], "nullable" => true]);
self::assertSame($nkey, ScalarSchema::normalize("?string|int"));
self::assertSame($nkey, ScalarSchema::normalize("string|?int"));
self::assertSame($nkey, ScalarSchema::normalize_definition("?string|int"));
self::assertSame($nkey, ScalarSchema::normalize_definition("string|?int"));
}
}

View File

@ -15,19 +15,21 @@ class ScalarWrapperTest extends TestCase {
self::assertSame($normalized, $wrapper->isNormalized(), "normalized");
}
function checkVerifix(ScalarSchema $schema, $orig, bool $verifix, $value, bool $present, bool $available, bool $valid, bool $normalized, ?array $inputParams=null): void {
function checkVerifix(ScalarSchema $schema, $orig, bool $normalize, $value, bool $present, bool $available, bool $valid, bool $normalized, ?array $inputParams=null): void {
$wrapper = $schema->getWrapper();
$wrapper->resetParams(["normalize" => $normalize]);
if ($inputParams !== null) $input = new Input($orig, $inputParams);
else $input = $orig;
$wrapper->reset($input, null, $verifix);
$wrapper->reset($input);
$this->checkValue($wrapper, $value, $present, $available, $valid, $normalized);
}
function checkException(ScalarSchema $schema, $orig, bool $verifix, string $exceptionClass, ?array $inputParams=null) {
function checkException(ScalarSchema $schema, $orig, bool $normalize, string $exceptionClass, ?array $inputParams=null) {
$wrapper = $schema->getWrapper();
if ($inputParams !== null) $orig = new Input($orig, $inputParams);
self::assertException($exceptionClass, function() use ($wrapper, &$orig, $verifix) {
$wrapper->reset($orig, null, $verifix);
self::assertException($exceptionClass, function() use ($wrapper, &$orig, $normalize) {
$wrapper->resetParams(["normalize" => $normalize]);
$wrapper->reset($orig);
});
}