diff --git a/.pman.yml b/.composer.pman.yml similarity index 76% rename from .pman.yml rename to .composer.pman.yml index 8e5132d..35e8f60 100644 --- a/.pman.yml +++ b/.composer.pman.yml @@ -11,6 +11,6 @@ composer: dist: link: false require-dev: - nulib/php: ^0.4.0p74 - nulib/spout: ^0.4.0p74 - nulib/phpss: ^0.4.0p74 + nulib/php: ^0.5.0p74 + nulib/spout: ^0.5.0p74 + nulib/phpss: ^0.5.0p74 diff --git a/.idea/nur-ture.iml b/.idea/nur-ture.iml index ae13f26..e8b45d5 100644 --- a/.idea/nur-ture.iml +++ b/.idea/nur-ture.iml @@ -4,10 +4,8 @@ - - - - + + diff --git a/CHANGES.md b/CHANGES.md index 6055589..9a2877c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,14 @@ +## Release 0.5.0p74 du 30/04/2025-05:30 + +* `2d73f4d` documenter showmorePlugin +* `3b13ef1` possiblité de forcer la suppression +* `3933fd1` corrections sur les controles +* `4d238cc` maj doc +* `f005692` déplacer nur/sery/wip et nur/sery dans nulib +* `c4e02d5` afficher la version de l'application + +## Release 0.4.1p82 du 17/03/2025-17:23 + ## Release 0.4.1p74 du 17/03/2025-17:19 * `56fda96` changer le type de variables gérées par EnvConfig diff --git a/VERSION.txt b/VERSION.txt index 267577d..8f0916f 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -0.4.1 +0.5.0 diff --git a/bin/json2yml.php b/bin/json2yml.php new file mode 100755 index 0000000..d417e90 --- /dev/null +++ b/bin/json2yml.php @@ -0,0 +1,7 @@ +#!/usr/bin/php +shouldUpdate()) { - unlink($this->ppFile); + if ($force || $this->shouldUpdate()) { + @unlink($this->ppFile); return true; } } finally { diff --git a/nur_src/cli/Application.php b/nur_src/cli/Application.php index 07de6ed..19697ec 100644 --- a/nur_src/cli/Application.php +++ b/nur_src/cli/Application.php @@ -2,6 +2,7 @@ namespace nur\cli; use Exception; +use nulib\app; use nulib\app\RunFile; use nulib\ExitError; use nulib\ext\yaml; @@ -16,7 +17,6 @@ use nur\config\ArrayConfig; use nur\msg; use nur\os; use nur\path; -use nur\sery\app; /** * Class Application: application de base diff --git a/nur_src/ref/ref_type.php b/nur_src/ref/ref_type.php index 9dd8def..2e6474e 100644 --- a/nur_src/ref/ref_type.php +++ b/nur_src/ref/ref_type.php @@ -105,7 +105,7 @@ class ref_type { /** comme {@link c} mais nullable */ const NCONTENT = "?".self::CONTENT; - /** comme {@link \nur\sery\FILE} mais nullable */ + /** comme {@link FILE} mais nullable */ const NFILE = "?".self::FILE; /** comme {@link DATETIME} mais nullable */ diff --git a/nur_src/v/bs3/fo/Form.php b/nur_src/v/bs3/fo/Form.php index c70b2f6..7dbbc14 100644 --- a/nur_src/v/bs3/fo/Form.php +++ b/nur_src/v/bs3/fo/Form.php @@ -599,16 +599,16 @@ class Form extends ComponentPrintable implements IParametrable, ArrayAccess, Cou /** @var ?array */ protected $hiddenControls; - function addHiddenControl($control, ?string $name=null): self { - A::set($this->hiddenControls, $name, $control); + function addHiddenControl($control, ?string $id=null): self { + A::set($this->hiddenControls, $id, $control); return $this; } /** @var ?array */ protected $controls; - function addControl($control, ?string $name=null): self { - A::set($this->controls, $name, $control); + function addControl($control, ?string $id=null): self { + A::set($this->controls, $id, $control); return $this; } @@ -660,7 +660,8 @@ class Form extends ComponentPrintable implements IParametrable, ArrayAccess, Cou $param["value"] = $value; #XXX en attendant le formattage ci-dessus, forcer la format texte pour que # la comparaison puisse se faire - $param["checked"] = strval($currentValue) === strval($value); + #XXX si $name est un tableau e.g values[] le test ci-dessous ne fonctionne pas + $param["checked"] ??= strval($currentValue) === strval($value); break; case self::NV: if ($value === null) $value = $this->get($key, $default); @@ -672,7 +673,7 @@ class Form extends ComponentPrintable implements IParametrable, ArrayAccess, Cou if ($params === null) $params = $param; else A::update_n($params, $param); - return [new $controlClass($this, $params), $name]; + return [new $controlClass($this, $params), $key]; } private function _prepareControls(): ?array { diff --git a/nur_src/v/nb.php b/nur_src/v/nb.php index d0d714a..76c2520 100644 --- a/nur_src/v/nb.php +++ b/nur_src/v/nb.php @@ -32,6 +32,7 @@ class nb { } static final function menu($text, ?array $links=null, ?array $options=null): array { + $links = array_filter($links, function($link) { return $link !== null; }); $item = ["item" => "menu", "links" => $links, "value" => $text]; if ($options !== null) $item = array_merge($item, $options); return $item; diff --git a/nur_src/v/plugins/showmorePlugin.php b/nur_src/v/plugins/showmorePlugin.php index b5dbb88..483a011 100644 --- a/nur_src/v/plugins/showmorePlugin.php +++ b/nur_src/v/plugins/showmorePlugin.php @@ -5,6 +5,23 @@ use nur\v\BasePlugin; use nur\v\v; use nur\v\vo; +/** + * Class showmorePlugin: un outil pour masquer par défaut un panneau de détails + * et donner la possibilité à l'utilisateur de l'afficher + * + * s'utilise de cette façon: + *
+ * $sm = new showmorePlugin();
+ * // le tout doit être dans le container startc-endc
+ * $sm->printStartc();
+ * // l'invite contient un lien pour afficher le panneau caché
+ * $sm->printInvite();
+ * // le panneau caché est dans le container startp-endp
+ * $sm->printStartp();
+ * $sm->printEndp();
+ * $sm->printEndc();
+ * 
+ */ class showmorePlugin extends BasePlugin { const HAVE_JQUERY = true; diff --git a/nur_src/v/vp/NavigablePage.php b/nur_src/v/vp/NavigablePage.php index 64fc944..23212c3 100644 --- a/nur_src/v/vp/NavigablePage.php +++ b/nur_src/v/vp/NavigablePage.php @@ -1,6 +1,7 @@ getProjdir(); + $versionfile = "$projdir/VERSION.txt"; + if (file_exists($versionfile)) { + $name = $app->getName(); + $version = file_get_contents($versionfile); + return nb::text([ + "style" => "margin: 0 15px", + "$name v$version" + ]); + } + return null; + } + protected function getAuthzNbtext(IAuthzUser $user): array { $username = $user->getUsername(); $role = $user->getRole(); @@ -95,6 +111,7 @@ class NavigablePage extends AInitAuthzPage implements INavigablePage { $user = authz::get(); navbar::nav(["align" => "right"], [ nb::menu(icon::user($user->getShortName()), [ + $this->getAppVersionNbtext(), $this->getAuthzNbtext($user), $this->getLogoutNblink(), ]), diff --git a/nur_tbin/base/test-args4.php b/nur_tbin/base/test-args4.php index f3cfbc3..3ea17fc 100644 --- a/nur_tbin/base/test-args4.php +++ b/nur_tbin/base/test-args4.php @@ -2,7 +2,7 @@ require(__DIR__.'/../../vendor/autoload.php'); use nur\cli\Application; -use nur\sery\output\msg; +use nulib\output\msg; class TestArgs4 extends Application { protected $query; diff --git a/src_app/app.php b/src/app.php similarity index 99% rename from src_app/app.php rename to src/app.php index 9b2828b..97802b9 100644 --- a/src_app/app.php +++ b/src/app.php @@ -1,18 +1,13 @@ 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,16 @@ 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 $missings, ?array $params=null): void { + } + + function deleteMissings(array $missings, ?array $params=null): void { + } + + function ensureOrder(array $keys, ?array $params=null): void { + } } diff --git a/src/php/access/ArrayAccess.php b/src/php/access/ArrayAccess.php new file mode 100644 index 0000000..76f6786 --- /dev/null +++ b/src/php/access/ArrayAccess.php @@ -0,0 +1,8 @@ +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 $missings, ?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); + } +} diff --git a/src/php/access/FormAccess.php b/src/php/access/FormAccess.php index 7b3840d..039ec50 100644 --- a/src/php/access/FormAccess.php +++ b/src/php/access/FormAccess.php @@ -1,5 +1,5 @@ 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 + ]); } } diff --git a/src/php/access/GetAccess.php b/src/php/access/GetAccess.php index 4b67367..79bf244 100644 --- a/src/php/access/GetAccess.php +++ b/src/php/access/GetAccess.php @@ -1,49 +1,27 @@ 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); } } diff --git a/src/php/access/IAccess.php b/src/php/access/IAccess.php index 2581c8b..0d6c6f1 100644 --- a/src/php/access/IAccess.php +++ b/src/php/access/IAccess.php @@ -1,5 +1,5 @@ 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 $missings, ?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"; + $haveMissing = $missings !== null && array_key_exists($key, $missings); + if ($dest === null || !array_key_exists($destKey, $dest)) { + $dest[$destKey] = $defaults[$key]; + } elseif ($haveMissing && $dest[$destKey] === $missings[$key]) { + $dest[$destKey] = $defaults[$key]; + } + } + } + + function deleteMissings(array $missings, ?array $params=null): void { + $dest =& $this->dest; + $prefix = $params["key_prefix"] ?? null; + $suffix = $params["key_suffix"] ?? null; + foreach ($missings as $key => $missing) { + $destKey = "$prefix$key$suffix"; + if (array_key_exists($destKey, $dest) && $dest[$destKey] === $missing) { + unset($dest[$destKey]); + } + } + } + + 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); } } diff --git a/src/php/access/PostAccess.php b/src/php/access/PostAccess.php index 1756964..24c9e22 100644 --- a/src/php/access/PostAccess.php +++ b/src/php/access/PostAccess.php @@ -1,49 +1,27 @@ 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); } } diff --git a/src/php/access/PropertyAccess.php b/src/php/access/PropertyAccess.php new file mode 100644 index 0000000..b0bc011 --- /dev/null +++ b/src/php/access/PropertyAccess.php @@ -0,0 +1,173 @@ +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 $missings, ?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]; + } + } + } +} diff --git a/src/php/access/ShadowAccess.php b/src/php/access/ShadowAccess.php index 229bad1..accb960 100644 --- a/src/php/access/ShadowAccess.php +++ b/src/php/access/ShadowAccess.php @@ -1,5 +1,5 @@ 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 $missings, ?array $params=null): void { + $this->writer->ensureKeys($defaults, $missings, $params); + } + + function ensureOrder(array $keys, ?array $params=null): void { + $this->writer->ensureOrder($keys, $params); + } } diff --git a/src/php/access/ValueAccess.php b/src/php/access/ValueAccess.php index 624092f..88ea8bd 100644 --- a/src/php/access/ValueAccess.php +++ b/src/php/access/ValueAccess.php @@ -1,56 +1,8 @@ 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; - } +class ValueAccess extends KeyAccess { + const ALLOW_NULL = false; + const ALLOW_FALSE = true; + const PROTECT_DEST = false; } diff --git a/src/php/coll/Cursor.php b/src/php/coll/Cursor.php index b10d011..4834b50 100644 --- a/src/php/coll/Cursor.php +++ b/src/php/coll/Cursor.php @@ -1,11 +1,11 @@ highestResult = -1; + } + + protected function _addMessage(Wrapper $wrapper, $prefix=null): void { + $result = $wrapper->getResult(); + $message = $this->message; + if ($message) $message .= "\n"; + if ($prefix !== null) $message .= "$prefix: "; + $message .= $result->message; + $this->message = $message; + } + + function addMissingMessage(Wrapper $wrapper, $prefix=null): void { + if ($this->highestResult < ref_analyze::MISSING) { + $this->present = false; + $this->available = false; + $this->null = false; + $this->valid = false; + $this->messageKey = "missing"; + } + $this->_addMessage($wrapper, $prefix); + } + + function addUnavailableMessage(Wrapper $wrapper, $prefix=null): void { + if ($this->highestResult < ref_analyze::UNAVAILABLE) { + $this->present = true; + $this->available = false; + $this->null = false; + $this->valid = false; + $this->messageKey = "unavailable"; + } + $this->_addMessage($wrapper, $prefix); + } + + function addNullMessage(Wrapper $wrapper, $prefix=null): void { + if ($this->highestResult < ref_analyze::NULL) { + $this->present = true; + $this->available = true; + $this->null = true; + $this->valid = false; + $this->messageKey = "null"; + } + $this->_addMessage($wrapper, $prefix); + } + + function addInvalidMessage(Wrapper $wrapper, $prefix=null): void { + if ($this->highestResult < ref_analyze::INVALID) { + $this->present = true; + $this->available = true; + $this->null = false; + $this->valid = false; + $this->messageKey = "invalid"; + } + $this->_addMessage($wrapper, $prefix); + } +} diff --git a/src/schema/OldSchema.php b/src/schema/OldSchema.php index e3b4581..d823e4d 100644 --- a/src/schema/OldSchema.php +++ b/src/schema/OldSchema.php @@ -1,5 +1,5 @@ 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 - */ - abstract function getKeys(): array; - - /** - * sélectionner le résultat associé à la clé spécifiée - * - * @param string|int|null $key - * @return Result $this - */ - abstract function select($key): Result; - - function getIterator() { - foreach ($this->getKeys() as $key) { - yield $key => $this->select($key); - } - $this->select(null); - } + public bool $resultAvailable; + public bool $present; + public bool $available; + public bool $null; + public bool $valid; + public bool $normalized; + public ?string $messageKey; + public ?string $message; + public ?Throwable $exception; + public $origValue; + public $normalizedValue; /** réinitialiser tous les objets résultats accessibles via cet objet */ - abstract function reset(): void; + function reset(): void { + $this->resultAvailable = false; + $this->present = false; + $this->available = false; + $this->null = false; + $this->valid = false; + $this->normalized = false; + $this->messageKey = null; + $this->message = null; + $this->exception = null; + $this->origValue = null; + $this->normalizedValue = null; + } + + 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( Schema $schema): int { + $this->resultAvailable = true; + $this->present = false; + $this->available = false; + if (!$schema->required) { + $this->null = false; + $this->valid = true; + $this->normalized = true; + return ref_analyze::NORMALIZED; + } else { + $this->messageKey = $messageKey = "missing"; + $this->message = $this->getMessage($messageKey, $schema); + return ref_analyze::MISSING; + } + } + + function setUnavailable( Schema $schema): int { + $this->resultAvailable = true; + $this->present = true; + $this->available = false; + if (!$schema->required) { + $this->null = false; + $this->valid = true; + $this->normalized = true; + return ref_analyze::NORMALIZED; + } else { + $this->messageKey = $messageKey = "unavailable"; + $this->message = $this->getMessage($messageKey, $schema); + return ref_analyze::UNAVAILABLE; + } + } + + function setNull( Schema $schema): int { + $this->resultAvailable = true; + $this->present = true; + $this->available = true; + $this->null = true; + if ($schema->nullable) { + $this->valid = true; + $this->normalized = true; + return ref_analyze::NORMALIZED; + } else { + $this->messageKey = $messageKey = "null"; + $this->message = $this->getMessage($messageKey, $schema); + return ref_analyze::NULL; + } + } + + 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; + $this->messageKey = $messageKey = "invalid"; + $message = null; + if ($exception !== null) $message = ValueException::get_message($exception); + if (!$message) $message = $this->getMessage($messageKey, $schema); + $this->message = $message; + $this->exception = $exception; + return ref_analyze::INVALID; + } + + function setValid($normalizedValue=null): int { + $this->resultAvailable = true; + $this->present = true; + $this->available = true; + $this->null = false; + $this->valid = true; + $this->normalizedValue = $normalizedValue; + return ref_analyze::VALID; + } + + function setNormalized(): int { + $this->resultAvailable = true; + $this->present = true; + $this->available = true; + $this->null = false; + $this->valid = true; + $this->normalized = true; + return ref_analyze::NORMALIZED; + } + + function throw(bool $throw): void { + if ($throw) { + $exception = $this->exception; + if ($exception !== null) throw $exception; + else throw new ValueException($this->message); + } + } } diff --git a/src/schema/Schema.php b/src/schema/Schema.php index 02365cb..fbb441b 100644 --- a/src/schema/Schema.php +++ b/src/schema/Schema.php @@ -1,22 +1,45 @@ getWrapper($value, $valueKey, $wrapper); + return self::ns($definition, null, $schema)->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"]; @@ -158,22 +187,22 @@ abstract class Schema implements ArrayAccess { tbool::ensure_bool($definition["required"]); tbool::ensure_bool($definition["nullable"]); tcontent::ensure_ncontent($definition["desc"]); - tcallable::ensure_ncallable($definition["analyzer_func"]); - tcallable::ensure_ncallable($definition["extractor_func"]); - tcallable::ensure_ncallable($definition["parser_func"]); - tcallable::ensure_ncallable($definition["normalizer_func"]); + tfunc::ensure_nfunc($definition["analyzer_func"]); + tfunc::ensure_nfunc($definition["extractor_func"]); + tfunc::ensure_nfunc($definition["parser_func"]); + tfunc::ensure_nfunc($definition["normalizer_func"]); tarray::ensure_narray($definition["messages"]); - tcallable::ensure_ncallable($definition["formatter_func"]); + tfunc::ensure_nfunc($definition["formatter_func"]); tbool::ensure_nbool($definition["computed"]); switch ($nature[0] ?? null) { 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; } } @@ -223,11 +252,11 @@ abstract class Schema implements ArrayAccess { case "assoc": foreach ($definition["schema"] as &$keydef) { self::_ensure_schema_instances($keydef); - Schema::ns($keydef, null, null, false); + Schema::ns(null, null, $keydef, false); }; unset($keydef); break; case "list": - Schema::ns($definition["schema"], null, null, false); + Schema::ns(null, null, $definition["schema"], false); break; } } @@ -246,14 +275,16 @@ abstract class Schema implements ArrayAccess { return $this->_definition; } - /** retourner true si le schéma est de nature tableau associatif */ - function isAssoc(?AssocSchema &$schema=null): bool { return false; } - /** retourner true si le schéma est de nature liste */ - function isList(?ListSchema &$schema=null): bool { return false; } - /** retourner true si le schéma est de nature scalaire */ - function isScalar(?ScalarSchema &$schema=null): bool { return false; } + /** + * retourner la liste des clés valides pour l'accès aux valeurs et résultats + */ + abstract function getKeys(): array; - abstract function getWrapper(&$value=null, $valueKey=null, ?Wrapper &$wrapper=null): Wrapper; + abstract function getSchema($key=false): Schema; + + abstract protected function newWrapper(): Wrapper; + + abstract function getWrapper(&$value=null, $valueKey=null, ?array $params=null, ?Wrapper &$wrapper=null): Wrapper; ############################################################################# # key & properties @@ -272,7 +303,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); diff --git a/src/schema/SchemaException.php b/src/schema/SchemaException.php index 7561bf1..f6e3998 100644 --- a/src/schema/SchemaException.php +++ b/src/schema/SchemaException.php @@ -1,5 +1,5 @@ $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 +67,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 \ No newline at end of file diff --git a/src/schema/Wrapper.php b/src/schema/Wrapper.php index d8cafed..7ea7d1e 100644 --- a/src/schema/Wrapper.php +++ b/src/schema/Wrapper.php @@ -1,20 +1,105 @@ context->resetParams($params); + } + + protected function resetContext(bool $resetSelectedKey): void { + $context = $this->context; + $type = $context->schema->type; + if (is_array($type)) $type = $type[0]; + if (is_string($type)) $type = types::get($context->schema->nullable, $type); + $context->type = $type; + $context->result->reset(); + $context->analyzed = false; + $context->normalized = false; + } + + protected function afterModify(?array $params, bool $resetSelectedKey=false): void { + $context = $this->context; + $this->resetContext($resetSelectedKey); + if ($params["analyze"] ?? $context->analyze) { + $this->analyze($params); + } + if ($context->analyzed && ($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; + + protected function getConsolidatedResult(): Result { + return $this->context->result; + } + + 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; + } + + $result = $this->getConsolidatedResult(); + if (!$result->valid) { + $result->throw($params["throw"] ?? $context->throw); + } + + return $modified; + } /** * Obtenir la liste des clés valides pour les valeurs accessibles via cet @@ -25,50 +110,113 @@ abstract class Wrapper implements ArrayAccess, IteratorAggregate { /** * sélectionner le wrapper associé à la clé spécifiée * + * $key peut valoir: + * - false pour la clé courante (ne pas changer la sélection) + * - null ou "" le wrapper de la valeur principale + * - ou toute autre valeur présente dans {@link getKeys()} pour les valeurs + * accessible via cet objet + * * @param string|int|null $key * @return Wrapper $this */ - abstract function select($key): Wrapper; + abstract function select($key=null): Wrapper; function getIterator() { foreach ($this->getKeys() as $key) { yield $key => $this->select($key); } - $this->select(null); + $this->select(); } /** - * 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()} + * + * $key peut valoir: + * - false pour la clé sélectionnée avec {@link select()} + * - null pour le résultat consolidé + * - "" pour le résultat de l'analyse de la valeur principale + * - ou toute autre valeur présente dans {@link getKeys()} pour le résultat + * de l'analyse des valeurs correspondantes */ - abstract function getResult(): Result; + function getResult($key=false): Result { + if ($key === false || $key === "") return $this->context->result; + if ($key === null) return $this->getConsolidatedResult(); + throw ValueException::invalid_key($key); + } /** 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 nulle */ + function isNull($key=false): bool { + return $this->getResult($key)->null; + } /** 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 +226,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); } } diff --git a/src/schema/WrapperContext.php b/src/schema/WrapperContext.php index 5ee0d37..a47bfd7 100644 --- a/src/schema/WrapperContext.php +++ b/src/schema/WrapperContext.php @@ -1,30 +1,44 @@ 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"] ?? ref_schema::PARAMS_SCHEMA["analyze"][1]; + $this->normalize = $params["normalize"] ?? ref_schema::PARAMS_SCHEMA["normalize"][1]; + $this->throw = $params["throw"] ?? ref_schema::PARAMS_SCHEMA["throw"][1]; + } + + /** 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; + + /** 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; } diff --git a/src/schema/_assoc/AssocResult.php b/src/schema/_assoc/AssocResult.php deleted file mode 100644 index 8ac0c77..0000000 --- a/src/schema/_assoc/AssocResult.php +++ /dev/null @@ -1,53 +0,0 @@ -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; - } -} diff --git a/src/schema/_assoc/AssocSchema.php b/src/schema/_assoc/AssocSchema.php index cabcc38..8f05843 100644 --- a/src/schema/_assoc/AssocSchema.php +++ b/src/schema/_assoc/AssocSchema.php @@ -1,21 +1,19 @@ $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,25 +43,54 @@ 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); + } else { + # ici, $definition contient un schema déjà instancié, mais c'est le mieux + # qu'on puisse faire + $this->_definition = $definition; } $this->definition = $definition; + $keys = []; + foreach ($definition["schema"] as $key => $schema) { + if (!$schema["computed"]) $keys[] = $key; + } + $this->keys = $keys; } - function isAssoc(?AssocSchema &$schema=null): bool { - $schema = $this; - return true; + protected array $keys; + + function getKeys(): array { + return $this->keys; + } + + function getSchema($key=false): Schema { + if ($key === null || $key === false) return $this; + $schema = $this->definition["schema"][$key] ?? null; + if ($schema === null) throw ValueException::invalid_key($key); + return $schema; } 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); + + # la nature du schéma peut contenir des paramètres par défaut + $nature = $this->definition[""]; + foreach (array_keys(ref_schema::ASSOC_PARAMS_SCHEMA) as $paramKey) { + $paramValue = $nature[$paramKey] ?? null; + if ($paramValue !== null) $params[$paramKey] = $paramValue; + } + if ($params !== null) $wrapper->resetParams($params); + + return $wrapper->reset($value, $valueKey, $dontAnalyze? ["analyze" => false]: null); } } diff --git a/src/schema/_assoc/AssocWrapper.php b/src/schema/_assoc/AssocWrapper.php index ea2981c..dddb067 100644 --- a/src/schema/_assoc/AssocWrapper.php +++ b/src/schema/_assoc/AssocWrapper.php @@ -1,140 +1,206 @@ context = $context = new AssocWrapperContext($schema, null, null, $params); + $paramsNoThrow = cl::merge($params, [ + "throw" => false, + ]); + + $keys = $schema->getKeys(); + $keyWrappers = []; + foreach ($keys as $key) { + $keyDummy = null; + $keyWrappers[$key] = $schema->getSchema($key)->getWrapper($keyDummy, null, $paramsNoThrow); + } + $context->keys = $keys; + $context->keyWrappers = $keyWrappers; + + $arrayDummy = null; + $context->arrayWrapper = new ScalarWrapper($schema, $arrayDummy, null, $paramsNoThrow, $context); + + $context->consolidatedResult = new ConsolidatedResult(); + + if ($value !== null) { + # n'initialiser que si $value n'est pas null + $this->reset($value, $valueKey); } - $this->schema = $schema; - $this->verifix = $verifix; - $this->throw = $throw ?? false; - $this->result = new AssocResult(); - $this->reset($array, $arrayKey); - $this->throw = $throw ?? true; } - function isAssoc(?AssocWrapper &$wrapper=null): bool { $wrapper = $this; return true; } + /** @var AssocWrapperContext */ + protected WrapperContext $context; - protected bool $verifix; - - 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(bool $resetSelectedKey): void { + parent::resetContext($resetSelectedKey); + $context = $this->context; + $context->arrayWrapper->getResult()->reset(); + foreach ($context->keyWrappers as $wrapper) { + $wrapper->getResult()->reset(); + } + 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 { + $context = $this->context; + if ($key === null || $key === "") return $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; } + /** + * @param AssocWrapperContext $context + * @param AssocWrapper $wrapper + */ + static function _analyze(WrapperContext $context, Wrapper $wrapper, ?array $params): int { + if ($params["ensure_array"] ?? $context->ensureArray) { + $valueKey = $context->valueKey; + $array = $context->input->get($valueKey); + if ($array === null) $context->input->set([], $valueKey); + } - function getResult(): AssocResult { - return $this->result; + if ($params["ensure_assoc"] ?? $context->ensureAssoc) { + $context->input->ensureAssoc($context->schema->getKeys()); + } + + $what = ScalarWrapper::_analyze($context, $wrapper, $params); + if (!$context->result->valid) return $what; + + $result = $context->consolidatedResult; + $result->setValid(); + foreach ($context->keyWrappers as $key => $keyWrapper) { + $keyWrapper->analyze($params); + if ($keyWrapper->isValid()) continue; + $what = ref_analyze::INVALID; + #XXX pour $prefix, utiliser si possible la description ou une autre valeur + # "user-friendly". possibilité de sélectionner la valeur à utiliser avec + # $params? + $prefix = $key; + if (!$keyWrapper->isPresent()) { + $result->addMissingMessage($keyWrapper, $prefix); + } elseif (!$keyWrapper->isAvailable()) { + $result->addUnavailableMessage($keyWrapper, $prefix); + } elseif ($keyWrapper->isNull()) { + $result->addNullMessage($keyWrapper, $prefix); + } else { + $result->addInvalidMessage($keyWrapper, $prefix); + } + } + + 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 { + $schema = $context->schema; + $keys = $schema->getKeys(); + + $defaults = []; + $missings = null; + foreach ($keys as $key) { + $type = $wrapper->getType($key); + $default = $schema->getSchema($key)->default; + if ($default === null) $default = $type->getNullValue(); + $defaults[$key] = $default; + $missing = $type->getMissingValue($valid); + if ($valid) $missings[$key] = $missing; + } + if ($params["ensure_keys"] ?? $context->ensureKeys) { + $context->input->ensureKeys($defaults, $missings, $params); + } else { + $context->input->deleteMissings($missings, $params); + } + + if ($params["ensure_order"] ?? $context->ensureOrder) { + $context->input->ensureOrder($keys, $params); + } + + $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; + protected function getConsolidatedResult(): Result { + return $this->context->consolidatedResult; } - function isAvailable(): bool { - return $this->result->available; + function getResult($key=false): Result { + if ($key === null) return $this->getConsolidatedResult(); + if ($key === false) $key = $this->context->selectedKey; + return $this->_getWrapper($key)->getResult(); } - function isValid(): bool { - return $this->result->valid; + function getType($key=false): IType { + if ($key === false) $key = $this->context->selectedKey; + return $this->_getWrapper($key)->getType(); } - function isNormalized(): bool { - return $this->result->normalized; + 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 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); } } diff --git a/src/schema/_assoc/AssocWrapperContext.php b/src/schema/_assoc/AssocWrapperContext.php new file mode 100644 index 0000000..38c7182 --- /dev/null +++ b/src/schema/_assoc/AssocWrapperContext.php @@ -0,0 +1,38 @@ +ensureArray = $params["ensure_array"] ?? ref_schema::ASSOC_PARAMS_SCHEMA["ensure_array"][1]; + $this->ensureAssoc = $params["ensure_assoc"] ?? ref_schema::ASSOC_PARAMS_SCHEMA["ensure_assoc"][1]; + $this->ensureKeys = $params["ensure_keys"] ?? ref_schema::ASSOC_PARAMS_SCHEMA["ensure_keys"][1]; + $this->ensureOrder = $params["ensure_order"] ?? ref_schema::ASSOC_PARAMS_SCHEMA["ensure_order"][1]; + } + + public ?ScalarWrapper $arrayWrapper = null; + + /** liste des clés valides */ + public array $keys; + + /** @var Wrapper[] */ + public array $keyWrappers; + + /** @var string|int|null clé sélectionnée */ + public $selectedKey = null; + + /** résultat consolidé de l'analyse du tableau et de ses composants */ + public ConsolidatedResult $consolidatedResult; +} diff --git a/src/schema/_list/ListResult.php b/src/schema/_list/ListResult.php deleted file mode 100644 index b40aadb..0000000 --- a/src/schema/_list/ListResult.php +++ /dev/null @@ -1,53 +0,0 @@ -arrayResult = $arrayResult; - $this->keyResults =& $keyResults; - $this->result =& $this->arrayResult; - parent::__construct(); - } - - function isList(?ListResult &$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; - } -} diff --git a/src/schema/_list/ListSchema.php b/src/schema/_list/ListSchema.php index f75b759..02a137b 100644 --- a/src/schema/_list/ListSchema.php +++ b/src/schema/_list/ListSchema.php @@ -1,9 +1,10 @@ $definition[0], ]; } - self::_normalize($definition, $definitionKey); + self::_normalize_definition($definition, $definitionKey); self::_ensure_nature($definition, "list", "array"); return $definition; } @@ -50,17 +51,26 @@ class ListSchema extends Schema { $this->definition = $definition; } - function isList(?ListSchema &$schema=null): bool { - $schema = $this; - return true; + const KEYS = [null]; + + function getKeys(): array { + return self::KEYS; + } + + public function getSchema($key=false): 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); } } diff --git a/src/schema/_list/ListWrapper.php b/src/schema/_list/ListWrapper.php index cae167b..a55c416 100644 --- a/src/schema/_list/ListWrapper.php +++ b/src/schema/_list/ListWrapper.php @@ -1,12 +1,10 @@ result = array_merge( - array_fill_keys(static::KEYS, null), [ - "resultAvailable" => false, - "present" => false, - "available" => false, - "null" => false, - "valid" => false, - "normalized" => false, - ]); - } - - function __get(string $name) { - return $this->result[$name]; - } - - function __set(string $name, $value): void { - $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 { - $message = cl::get($schema->messages, $key); - if ($message !== null) return $message; - return cl::get(ref_schema::MESSAGES, $key); - } - - function setMissing(ScalarSchema $schema): int { - $this->resultAvailable = true; - $this->present = false; - $this->available = false; - if (!$schema->required) { - $this->null = false; - $this->valid = true; - $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; - return ref_analyze::MISSING; - } - } - - function setUnavailable(ScalarSchema $schema): int { - $this->resultAvailable = true; - $this->present = true; - $this->available = false; - if (!$schema->required) { - $this->null = false; - $this->valid = true; - $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; - return ref_analyze::UNAVAILABLE; - } - } - - function setNull(ScalarSchema $schema): int { - $this->resultAvailable = true; - $this->present = true; - $this->available = true; - $this->null = true; - if ($schema->nullable) { - $this->valid = true; - $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; - return ref_analyze::NULL; - } - } - - function setInvalid($value, ScalarSchema $schema, ?Throwable $t=null): int { - $this->resultAvailable = true; - $this->present = true; - $this->available = true; - $this->null = false; - $this->valid = false; - $this->origValue = $value; - $messageKey = $this->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"; - } - $this->message = $message; - return ref_analyze::INVALID; - } - - function setValid($normalizedValue=null): int { - $this->resultAvailable = true; - $this->present = true; - $this->available = true; - $this->null = false; - $this->valid = true; - $this->normalizedValue = $normalizedValue; - return ref_analyze::VALID; - } - - function setNormalized(): int { - $this->resultAvailable = true; - $this->present = true; - $this->available = true; - $this->null = false; - $this->valid = true; - $this->normalized = true; - return ref_analyze::NORMALIZED; - } - - function throw(bool $throw): void { - if ($throw) throw new ValueException($this->message); - } -} diff --git a/src/schema/_scalar/ScalarSchema.php b/src/schema/_scalar/ScalarSchema.php index a4c11bf..c1e5125 100644 --- a/src/schema/_scalar/ScalarSchema.php +++ b/src/schema/_scalar/ScalarSchema.php @@ -1,41 +1,18 @@ 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,40 +51,47 @@ 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); + } else { + # ici, $definition contient un schema déjà instancié, mais c'est le mieux + # qu'on puisse faire + $this->_definition = $definition; } $this->definition = $definition; } - function isScalar(?ScalarSchema &$schema=null): bool { - $schema = $this; - return true; + const KEYS = []; + + function getKeys(): array { + return self::KEYS; + } + + function getSchema($key=false): Schema { + if ($key === false || $key === null || $key === "") return $this; + throw ValueException::invalid_key($key); } 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); + + # la nature du schéma peut contenir des paramètres par défaut + $nature = $this->definition[""]; + foreach (array_keys(ref_schema::SCALAR_PARAMS_SCHEMA) as $paramKey) { + $paramValue = $nature[$paramKey] ?? null; + if ($paramValue !== null) $params[$paramKey] = $paramValue; + } + 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], - ]; } diff --git a/src/schema/_scalar/ScalarWrapper.php b/src/schema/_scalar/ScalarWrapper.php index 0ea9596..00f7896 100644 --- a/src/schema/_scalar/ScalarWrapper.php +++ b/src/schema/_scalar/ScalarWrapper.php @@ -1,85 +1,56 @@ result = new Result(); + $this->context = $context; + + if ($value !== null) { + # n'initialiser que si $value n'est pas null + $this->reset($value, $valueKey); + } else { + # il faut au moins que le type soit disponible + $this->resetContext(false); } - $this->verifix = $verifix; - $this->throw = $throw ?? false; - $this->schema = $schema; - $this->result = new ScalarResult(); - $this->reset($value, $valueKey); - $this->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 */ - function select($key): ScalarWrapper { - if ($key !== null) throw ValueException::invalid_key($key); - return $this; + function select($key=null): ScalarWrapper { + if ($key === null || $key === "") return $this; + throw ValueException::invalid_key($key); } /** 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; $valueKey = $context->valueKey; - /** @var ScalarResult $result */ $result = $context->result; $default = $schema->default; @@ -112,7 +83,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 +111,7 @@ class ScalarWrapper extends Wrapper { $type = $firstType; } } - $context->type = $this->type = $type; + $context->type = $type; if (!$type->isAvailable($input, $valueKey)) { if ($default !== null) { @@ -152,6 +123,16 @@ class ScalarWrapper extends Wrapper { } $value = $input->get($valueKey); + $missing = $type->getMissingValue($haveMissing); + if ($haveMissing && $value === $missing) { + if ($default !== null) { + $input->set($default, $valueKey); + return $result->setNormalized(); + } else { + return $result->setMissing($schema); + } + } + $context->origValue = $context->value = $value; if ($type->isNull($value)) { return $result->setNull($schema); @@ -165,25 +146,27 @@ 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; + $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 +177,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 +194,52 @@ 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; + $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); - } - } } diff --git a/src/schema/input/FormInput.php b/src/schema/input/FormInput.php index e708cd5..5bc39f8 100644 --- a/src/schema/input/FormInput.php +++ b/src/schema/input/FormInput.php @@ -1,12 +1,11 @@ $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(), + ]); } } diff --git a/src/schema/input/GetInput.php b/src/schema/input/GetInput.php index 766c8e5..3bee322 100644 --- a/src/schema/input/GetInput.php +++ b/src/schema/input/GetInput.php @@ -1,8 +1,8 @@ $this->allowEmpty, + protected function formAccess(IAccess $access): IAccess { + return new GetAccess(null, [ + "allow_empty" => $access->isAllowEmpty(), ]); } } diff --git a/src/schema/input/Input.php b/src/schema/input/Input.php index 1008d87..3ff0b3c 100644 --- a/src/schema/input/Input.php +++ b/src/schema/input/Input.php @@ -1,9 +1,11 @@ value =& $value; - $this->allowEmpty = $params["allow_empty"] ?? static::ALLOW_EMPTY; + 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"] ?? ref_input::ACCESS_AUTO; + if ($accessType === ref_input::ACCESS_AUTO) { + $accessType = is_object($dest)? ref_input::ACCESS_PROPERTY : ref_input::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 == ref_input::ACCESS_PROPERTY) { + $this->access = new PropertyAccess($dest, null, [ + "allow_empty" => $allowEmpty, + "allow_null" => true, + ]); + } elseif ($accessType == ref_input::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; + } + + function ensureAssoc(array $keys, ?array $params=null): void { + $this->access->ensureAssoc($keys, $params); + } + + function ensureKeys(array $defaults, ?array $missings, ?array $params=null): void { + $this->access->ensureKeys($defaults, $missings, $params); + } + + function deleteMissings(array $missings, ?array $params=null): void { + $this->access->deleteMissings($missings, $params); + } + + function ensureOrder(array $keys, ?array $params=null): void { + $this->access->ensureOrder($keys, $params); } } diff --git a/src/schema/input/PostInput.php b/src/schema/input/PostInput.php index bf720ab..47afe22 100644 --- a/src/schema/input/PostInput.php +++ b/src/schema/input/PostInput.php @@ -1,8 +1,8 @@ $this->allowEmpty, + protected function formAccess(IAccess $access): IAccess { + return new PostAccess(null, [ + "allow_empty" => $access->isAllowEmpty(), ]); } } diff --git a/src/schema/types.php b/src/schema/types.php index c1b618b..390cd01 100644 --- a/src/schema/types.php +++ b/src/schema/types.php @@ -1,22 +1,22 @@ ttext::class, "bool" => tbool::class, "boolean" => tbool::class, "int" => tint::class, "integer" => tint::class, - "float" => tfloat::class, "flt" => tfloat::class, - "double" => tfloat::class, "dbl" => tfloat::class, + "float" => tfloat::class, "flt" => tfloat::class, "double" => tfloat::class, "dbl" => tfloat::class, "array" => tarray::class, - "callable" => tcallable::class, + "func" => tfunc::class, "function" => tfunc::class, "callable" => tfunc::class, # types spéciaux "raw" => traw::class, "mixed" => tmixed::class, diff --git a/src/schema/types/_tformatable.php b/src/schema/types/_tformatable.php index b2bf084..3ba087c 100644 --- a/src/schema/types/_tformatable.php +++ b/src/schema/types/_tformatable.php @@ -1,5 +1,5 @@ nullable? null: []; + } + 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) { @@ -46,26 +48,16 @@ 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; } diff --git a/src/schema/types/tbool.php b/src/schema/types/tbool.php index 50fa206..0f9b0db 100644 --- a/src/schema/types/tbool.php +++ b/src/schema/types/tbool.php @@ -1,14 +1,13 @@ nullable; + function getMissingValue(?bool &$valid=null) { + $valid = false; + return null; } - function get2States(): array { - return [false, true]; + function getNullValue() { + return $this->nullable? null: false; } - function is3States(): bool { - return $this->nullable; - } - - function get3States(): array { - return [false, true, null]; + public function getNbStates(?array &$states=null): int { + if ($this->nullable) { + $states = [false, true, null]; + return 3; + } else { + $states = [false, true]; + return 2; + } } function isAvailable(Input $input, $valueKey): bool { @@ -96,26 +98,16 @@ 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; } diff --git a/src/schema/types/tcallable.php b/src/schema/types/tcallable.php deleted file mode 100644 index f9e95d6..0000000 --- a/src/schema/types/tcallable.php +++ /dev/null @@ -1,68 +0,0 @@ -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; - } - - function format($value, $format=null): string { - } -} diff --git a/src/schema/types/tcontent.php b/src/schema/types/tcontent.php index 1dfe455..b775cdf 100644 --- a/src/schema/types/tcontent.php +++ b/src/schema/types/tcontent.php @@ -1,11 +1,10 @@ nullable? null: []; + } + 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) { @@ -33,21 +36,17 @@ 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 { diff --git a/src/schema/types/tfloat.php b/src/schema/types/tfloat.php index db67257..51fae89 100644 --- a/src/schema/types/tfloat.php +++ b/src/schema/types/tfloat.php @@ -1,11 +1,10 @@ nullable? null: 0.0; + } + function isValid($value, ?bool &$normalized=null): bool { $normalized = is_float($value); return is_scalar($value); @@ -40,26 +43,16 @@ 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; } } diff --git a/src/schema/types/tfunc.php b/src/schema/types/tfunc.php new file mode 100644 index 0000000..e846049 --- /dev/null +++ b/src/schema/types/tfunc.php @@ -0,0 +1,61 @@ +normalized) { + } elseif ($value instanceof func) { + $result->setNormalized(); + } elseif (is_callable($value)) { + $value = func::with($value); + return true; + } + return false; + } + + function format($value, $format=null): string { + } +} diff --git a/src/schema/types/tgeneric.php b/src/schema/types/tgeneric.php index 16f86a8..7b06db4 100644 --- a/src/schema/types/tgeneric.php +++ b/src/schema/types/tgeneric.php @@ -1,12 +1,10 @@ class; } - function isAvailable(Input $input, $valueKey): bool { - return $input->isAvailable($valueKey); + function getNullValue() { + return null; } public function isNull($value): bool { @@ -29,8 +27,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) { @@ -38,11 +36,10 @@ 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; } diff --git a/src/schema/types/tint.php b/src/schema/types/tint.php index 5673ab5..1f3a5ae 100644 --- a/src/schema/types/tint.php +++ b/src/schema/types/tint.php @@ -1,11 +1,10 @@ nullable? null: 0; + } + function isValid($value, ?bool &$normalized=null): bool { $normalized = is_int($value); return is_scalar($value); @@ -42,26 +45,16 @@ 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; } } diff --git a/src/schema/types/tkey.php b/src/schema/types/tkey.php index 0e00cb3..0650f54 100644 --- a/src/schema/types/tkey.php +++ b/src/schema/types/tkey.php @@ -1,10 +1,9 @@ nullable? null: ""; + } + 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) { @@ -33,21 +36,17 @@ 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 { diff --git a/src/schema/types/tmixed.php b/src/schema/types/tmixed.php index fa525eb..4bb4e6a 100644 --- a/src/schema/types/tmixed.php +++ b/src/schema/types/tmixed.php @@ -1,11 +1,10 @@ isAvailable($valueKey); } @@ -32,11 +40,10 @@ 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; } diff --git a/src/schema/types/tpkey.php b/src/schema/types/tpkey.php index 14f2e01..fa09fd5 100644 --- a/src/schema/types/tpkey.php +++ b/src/schema/types/tpkey.php @@ -1,10 +1,9 @@ nullable? null: []; + } + 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) { @@ -38,21 +41,17 @@ 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 { diff --git a/src/schema/types/traw.php b/src/schema/types/traw.php index b187b3f..3a5135b 100644 --- a/src/schema/types/traw.php +++ b/src/schema/types/traw.php @@ -1,7 +1,7 @@ nullable? null: ""; + } + function isNull($value): bool { return $value === null; } @@ -41,21 +44,17 @@ 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 { diff --git a/src/schema/types/tstring.php b/src/schema/types/tstring.php index 0e45d99..536f87d 100644 --- a/src/schema/types/tstring.php +++ b/src/schema/types/tstring.php @@ -1,5 +1,5 @@ args[0] ?? null; + if ($input === null || $input === "-") { + $output = null; + } else { + $output = path::ensure_ext($input, ".yml", ".json"); + } + + $data = json::load($input); + yaml::dump($data, $output); + } +} \ No newline at end of file diff --git a/src/tools/Yaml2jsonApp.php b/src/tools/Yaml2jsonApp.php new file mode 100644 index 0000000..9eba6ea --- /dev/null +++ b/src/tools/Yaml2jsonApp.php @@ -0,0 +1,23 @@ +args[0] ?? null; + if ($input === null || $input === "-") { + $output = null; + } else { + $output = path::ensure_ext($input, ".json", [".yml", ".yaml"]); + } + + $data = yaml::load($input); + json::dump($data, $output); + } +} \ No newline at end of file diff --git a/src/web/content/Tag.php b/src/web/content/Tag.php index c80ce52..e50da56 100644 --- a/src/web/content/Tag.php +++ b/src/web/content/Tag.php @@ -1,5 +1,5 @@ $projdir, + "vendor" => [ + "bindir" => "$projdir/vendor/bin", + "autoload" => "$projdir/vendor/autoload.php", + ], + "appcode" => "nur-sery", + "cwd" => $cwd, + "datadir" => "$projdir/devel", + "etcdir" => "$projdir/devel/etc", + "vardir" => "$projdir/devel/var", + "logdir" => "$projdir/devel/log", + "profile" => "devel", + "appgroup" => null, + "name" => "my-application1", + "title" => null, + ], $app1->getParams()); + + $app2 = myapp::with(MyApplication2::class, $app1); + self::assertSame([ + "projdir" => $projdir, + "vendor" => [ + "bindir" => "$projdir/vendor/bin", + "autoload" => "$projdir/vendor/autoload.php", + ], + "appcode" => "nur-sery", + "cwd" => $cwd, + "datadir" => "$projdir/devel", + "etcdir" => "$projdir/devel/etc", + "vardir" => "$projdir/devel/var", + "logdir" => "$projdir/devel/log", + "profile" => "devel", + "appgroup" => null, + "name" => "my-application2", + "title" => null, + ], $app2->getParams()); + } + + function testInit() { + $projdir = config::get_projdir(); + $cwd = getcwd(); + + myapp::reset(); + myapp::init(MyApplication1::class); + self::assertSame([ + "projdir" => $projdir, + "vendor" => [ + "bindir" => "$projdir/vendor/bin", + "autoload" => "$projdir/vendor/autoload.php", + ], + "appcode" => "nur-sery", + "cwd" => $cwd, + "datadir" => "$projdir/devel", + "etcdir" => "$projdir/devel/etc", + "vardir" => "$projdir/devel/var", + "logdir" => "$projdir/devel/log", + "profile" => "devel", + "appgroup" => null, + "name" => "my-application1", + "title" => null, + ], myapp::get()->getParams()); + + myapp::init(MyApplication2::class); + self::assertSame([ + "projdir" => $projdir, + "vendor" => [ + "bindir" => "$projdir/vendor/bin", + "autoload" => "$projdir/vendor/autoload.php", + ], + "appcode" => "nur-sery", + "cwd" => $cwd, + "datadir" => "$projdir/devel", + "etcdir" => "$projdir/devel/etc", + "vardir" => "$projdir/devel/var", + "logdir" => "$projdir/devel/log", + "profile" => "devel", + "appgroup" => null, + "name" => "my-application2", + "title" => null, + ], myapp::get()->getParams()); + } + } +} + +namespace nulib\impl { + + use nulib\app\cli\Application; + use nulib\os\path; + use nulib\app; + + class config { + const PROJDIR = __DIR__.'/..'; + + static function get_projdir(): string { + return path::abspath(self::PROJDIR); + } + } + + class myapp extends app { + static function reset(): void { + self::$app = null; + } + } + + class MyApplication1 extends Application { + const PROJDIR = config::PROJDIR; + + function main() { + } + } + class MyApplication2 extends Application { + const PROJDIR = null; + + function main() { + } + } +} diff --git a/tests/php/access/KeyAccessTest.php b/tests/php/access/KeyAccessTest.php new file mode 100644 index 0000000..6c17fef --- /dev/null +++ b/tests/php/access/KeyAccessTest.php @@ -0,0 +1,215 @@ +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" => ""]; + + # + $a = new KeyAccess($array, "inexistant"); + self::assertFalse($a->exists()); + self::assertFalse($a->available()); + self::assertSame($default, $a->get($default)); + + $a = new KeyAccess($array, "null"); + self::assertTrue($a->exists()); + self::assertTrue($a->available()); + self::assertSame(null, $a->get($default)); + + $a = new KeyAccess($array, "false"); + self::assertTrue($a->exists()); + self::assertFalse($a->available()); + self::assertSame($default, $a->get($default)); + + $a = new KeyAccess($array, "empty"); + self::assertTrue($a->exists()); + self::assertTrue($a->available()); + self::assertSame("", $a->get($default)); + + # + $a = new KeyAccess($array, "null", ["allow_null" => false]); + self::assertTrue($a->exists()); + self::assertFalse($a->available()); + self::assertSame($default, $a->get($default)); + + $a = new KeyAccess($array, "null", ["allow_null" => true]); + self::assertTrue($a->exists()); + self::assertTrue($a->available()); + self::assertSame(null, $a->get($default)); + + # + $a = new KeyAccess($array, "false", ["allow_false" => false]); + self::assertTrue($a->exists()); + self::assertFalse($a->available()); + self::assertSame($default, $a->get($default)); + + $a = new KeyAccess($array, "false", ["allow_false" => true]); + self::assertTrue($a->exists()); + self::assertTrue($a->available()); + self::assertSame(false, $a->get($default)); + + # + $a = new KeyAccess($array, "empty", ["allow_empty" => false]); + self::assertTrue($a->exists()); + self::assertFalse($a->available()); + self::assertSame($default, $a->get($default)); + + $a = new KeyAccess($array, "empty", ["allow_empty" => true]); + self::assertTrue($a->exists()); + 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, $missings, $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, $missings, $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_", + ]); + } +} diff --git a/tests/wip/php/coll/CursorTest.php b/tests/php/coll/CursorTest.php similarity index 99% rename from tests/wip/php/coll/CursorTest.php rename to tests/php/coll/CursorTest.php index f28bc95..af76c0a 100644 --- a/tests/wip/php/coll/CursorTest.php +++ b/tests/php/coll/CursorTest.php @@ -1,5 +1,5 @@ [ + "assoc", + "compute_func" => null, + "validate_func" => null, + "ensure_array" => null, + "ensure_assoc" => null, + "ensure_keys" => null, + "ensure_order" => null, + ], + "schema" => null, + "type" => [null], + "default" => null, + "title" => null, + "required" => false, + "nullable" => true, + "desc" => null, + "analyzer_func" => null, + "extractor_func" => null, + "parser_func" => null, + "normalizer_func" => null, + "messages" => null, + "formatter_func" => null, + "format" => null, + "size" => null, + "precision" => null, + "name" => null, + "pkey" => null, + "header" => null, + "computed" => null, + ]; + + static function schema(array $definition, array $keyDefinitions): array { + $definition = array_merge(self::NULL_SCHEMA, $definition, ["schema" => []]); + foreach ($keyDefinitions as $key => $keydef) { + $definition["schema"][$key] = array_merge(ScalarSchemaTest::NULL_SCHEMA, $keydef); + }; unset($subdef); + return $definition; + } + + function testNormalize() { + self::assertSame(self::schema([ + "type" => ["array"], "nullable" => true, + ], [ + "s" => [ + "type" => ["string"], "nullable" => false, + "name" => "s", "pkey" => "s", "header" => "s", + ], + ]), AssocSchema::normalize_definition(["s" => "string"])); + + self::assertSame(self::schema([ + "type" => ["array"], "nullable" => true, + ], [ + "s" => [ + "type" => ["string"], "nullable" => false, + "name" => "s", "pkey" => "s", "header" => "s", + ], + "i" => [ + "type" => ["int"], "nullable" => false, + "name" => "i", "pkey" => "i", "header" => "i", + ], + "b" => [ + "type" => ["bool"], "nullable" => false, + "name" => "b", "pkey" => "b", "header" => "b", + ], + ]), AssocSchema::normalize_definition([ + "s" => "string", + "i" => "int", + "b" => "bool", + ])); + } + + function testConstructor() { + $schema = new AssocSchema([ + "s" => "string", + "i" => "int", + "b" => "bool", + ]); + self::assertSame(self::schema([ + "type" => ["array"], "nullable" => true, + ], [ + "s" => [ + "type" => ["string"], "nullable" => false, + "name" => "s", "pkey" => "s", "header" => "s", + ], + "i" => [ + "type" => ["int"], "nullable" => false, + "name" => "i", "pkey" => "i", "header" => "i", + ], + "b" => [ + "type" => ["bool"], "nullable" => false, + "name" => "b", "pkey" => "b", "header" => "b", + ], + ]), $schema->getDefinition()); + //yaml::dump($schema->getDefinition()); + } + + function testWrapper() { + $schema = new AssocSchema([ + "s" => "?string", + "i" => "?int", + "b" => "?bool", + ]); + $array = ["s" => " string ", "i" => " 42 ", "b" => false]; + $schema->getWrapper($array); + self::assertSame([ + "s" => "string", + "i" => 42, + "b" => false, + ], $array); + + ########################################################################### + $schema = new AssocSchema([ + "s" => "string", + "i" => "int", + "b" => "bool", + ]); + + $array = ["s" => " string "]; + $schema->getWrapper($array); + self::assertSame([ + "s" => "string", + "i" => 0, + "b" => false, + ], $array); + + $array = ["b" => false, "s" => " string "]; + $schema->getWrapper($array); + self::assertSame([ + "s" => "string", + "i" => 0, + "b" => false, + ], $array); + + $array = ["s" => " string "]; + $schema->getWrapper($array, null, ["ensure_order" => false]); + self::assertSame([ + "s" => "string", + "i" => 0, + "b" => false, + ], $array); + + $array = ["b" => false, "s" => " string "]; + $schema->getWrapper($array, null, ["ensure_order" => false]); + self::assertSame([ + "b" => false, + "s" => "string", + "i" => 0, + ], $array); + + $array = ["s" => " string "]; + $schema->getWrapper($array, null, ["ensure_keys" => false]); + self::assertSame([ + "s" => "string", + ], $array); + + $array = ["b" => false, "s" => " string "]; + $schema->getWrapper($array, null, ["ensure_keys" => false]); + self::assertSame([ + "s" => "string", + "b" => false, + ], $array); + + // false équivaut à absent, sauf pour "b" qui est de type bool + $array = ["s" => false, "i" => false, "b" => false]; + $schema->getWrapper($array, null, ["ensure_keys" => true]); + self::assertSame([ + "s" => "", + "i" => 0, + "b" => false, + ], $array); + + $array = ["s" => false, "i" => false, "b" => false]; + $schema->getWrapper($array, null, ["ensure_keys" => false]); + self::assertSame([ + "b" => false, + ], $array); + } + + const STRING_SCHEMA = [ + "s" => "string", + "f" => "string", + "m" => "string", + ]; + + const NSTRING_SCHEMA = [ + "s" => "?string", + "f" => "?string", + "m" => "?string", + ]; + + const RSTRING_SCHEMA = [ + "s" => ["string", "required" => true], + "f" => ["string", "required" => true], + "m" => ["string", "required" => true], + ]; + + const RNSTRING_SCHEMA = [ + "s" => ["?string", "required" => true], + "f" => ["?string", "required" => true], + "m" => ["?string", "required" => true], + ]; + + const STRINGS = ["s" => "string", "f" => false]; + const NSTRINGS = ["s" => null, "f" => null]; + + function testString() { + /** @var AssocSchema $schema */ + $schema = Schema::ns(self::STRING_SCHEMA); + + $array = self::STRINGS; + $wrapper = $schema->getWrapper($array, null, ["throw" => false]); + self::assertSame(["s" => "string", "f" => "", "m" => ""], $array); + $result = $wrapper->getResult("s"); + self::assertTrue($result->normalized); + $result = $wrapper->getResult("f"); + self::assertTrue($result->present); + self::assertFalse($result->available); + $result = $wrapper->getResult("m"); + self::assertFalse($result->present); + + self::assertNotException(function() use ($schema) { + $array = self::STRINGS; + $schema->getWrapper($array); + }); + + $array = self::NSTRINGS; + $wrapper = $schema->getWrapper($array, null, ["throw" => false]); + self::assertSame(["s" => null, "f" => null, "m" => ""], $array); + $result = $wrapper->getResult("s"); + self::assertFalse($result->valid); + self::assertSame("null", $result->messageKey); + $result = $wrapper->getResult("f"); + self::assertFalse($result->valid); + self::assertSame("null", $result->messageKey); + $result = $wrapper->getResult("m"); + self::assertFalse($result->present); + + self::assertException(ValueException::class, function() use ($schema) { + $array = self::NSTRINGS; + $schema->getWrapper($array); + }); + } + + function testNstring() { + /** @var AssocSchema $schema */ + $schema = Schema::ns(self::NSTRING_SCHEMA); + + $array = self::STRINGS; + $wrapper = $schema->getWrapper($array, null, ["throw" => false]); + self::assertSame(["s" => "string", "f" => null, "m" => null], $array); + $result = $wrapper->getResult("s"); + self::assertTrue($result->normalized); + $result = $wrapper->getResult("f"); + self::assertTrue($result->present); + self::assertFalse($result->available); + $result = $wrapper->getResult("m"); + self::assertFalse($result->present); + + self::assertNotException(function() use ($schema) { + $array = self::STRINGS; + $schema->getWrapper($array); + }); + + $array = self::NSTRINGS; + $wrapper = $schema->getWrapper($array, null, ["throw" => false]); + self::assertSame(["s" => null, "f" => null, "m" => null], $array); + $result = $wrapper->getResult("s"); + self::assertTrue($result->normalized); + $result = $wrapper->getResult("f"); + self::assertTrue($result->normalized); + $result = $wrapper->getResult("m"); + self::assertFalse($result->present); + + self::assertNotException(function() use ($schema) { + $array = self::NSTRINGS; + $schema->getWrapper($array); + }); + } + + function testRstring() { + /** @var AssocSchema $schema */ + $schema = Schema::ns(self::RSTRING_SCHEMA); + + $array = self::STRINGS; + $wrapper = $schema->getWrapper($array, null, ["throw" => false]); + self::assertSame(["s" => "string", "f" => "", "m" => ""], $array); + $result = $wrapper->getResult("s"); + self::assertTrue($result->normalized); + $result = $wrapper->getResult("f"); + self::assertTrue($result->present); + self::assertFalse($result->available); + self::assertSame("unavailable", $result->messageKey); + $result = $wrapper->getResult("m"); + self::assertFalse($result->present); + self::assertSame("missing", $result->messageKey); + + self::assertException(ValueException::class, function() use ($schema) { + $array = self::STRINGS; + $schema->getWrapper($array); + }); + + $array = self::NSTRINGS; + $wrapper = $schema->getWrapper($array, null, ["throw" => false]); + self::assertSame(["s" => null, "f" => null, "m" => ""], $array); + $result = $wrapper->getResult("s"); + self::assertFalse($result->valid); + self::assertSame("null", $result->messageKey); + $result = $wrapper->getResult("f"); + self::assertFalse($result->valid); + self::assertSame("null", $result->messageKey); + $result = $wrapper->getResult("m"); + self::assertFalse($result->present); + self::assertSame("missing", $result->messageKey); + + self::assertException(ValueException::class, function() use ($schema) { + $array = self::NSTRINGS; + $schema->getWrapper($array); + }); + } + + function testRnstring() { + /** @var AssocSchema $schema */ + $schema = Schema::ns(self::RNSTRING_SCHEMA); + + $array = self::STRINGS; + $wrapper = $schema->getWrapper($array, null, ["throw" => false]); + self::assertSame(["s" => "string", "f" => null, "m" => null], $array); + $result = $wrapper->getResult("s"); + self::assertTrue($result->normalized); + $result = $wrapper->getResult("f"); + self::assertTrue($result->present); + self::assertFalse($result->available); + self::assertSame("unavailable", $result->messageKey); + $result = $wrapper->getResult("m"); + self::assertFalse($result->present); + self::assertSame("missing", $result->messageKey); + + self::assertException(ValueException::class, function() use ($schema) { + $array = self::STRINGS; + $schema->getWrapper($array); + }); + + $array = self::NSTRINGS; + $wrapper = $schema->getWrapper($array, null, ["throw" => false]); + self::assertSame(["s" => null, "f" => null, "m" => null], $array); + $result = $wrapper->getResult("s"); + self::assertTrue($result->normalized); + $result = $wrapper->getResult("f"); + self::assertTrue($result->normalized); + $result = $wrapper->getResult("m"); + self::assertFalse($result->present); + self::assertSame("missing", $result->messageKey); + + self::assertException(ValueException::class, function() use ($schema) { + $array = self::NSTRINGS; + $schema->getWrapper($array); + }); + } + + function testMessage() { + $schema = new AssocSchema([ + "rs" => ["string", "required" => true], + "i" => ["int"], + ]); + + $value = []; + $result = $schema->getWrapper($value, null, ["throw" => false])->getResult(null); + $expectedMessage = <<message); + + $value = [ + "rs" => null, + "i" => "abc", + ]; + $result = $schema->getWrapper($value, null, ["throw" => false])->getResult(null); + $expectedMessage = <<message); + } +} diff --git a/tests/schema/_scalar/ScalarSchemaTest.php b/tests/schema/_scalar/ScalarSchemaTest.php new file mode 100644 index 0000000..70714dc --- /dev/null +++ b/tests/schema/_scalar/ScalarSchemaTest.php @@ -0,0 +1,70 @@ + [null], + "default" => null, + "title" => null, + "required" => false, + "nullable" => true, + "desc" => null, + "analyzer_func" => null, + "extractor_func" => null, + "parser_func" => null, + "normalizer_func" => null, + "messages" => null, + "formatter_func" => null, + "format" => null, + "size" => null, + "precision" => null, + "" => [ + "scalar", + "compute_func" => null, + "validate_func" => null, + ], + "schema" => null, + "name" => null, + "pkey" => null, + "header" => null, + "computed" => null, + ]; + + static function schema(array $schema): array { + return array_merge(self::NULL_SCHEMA, $schema); + } + + function testNormalize() { + 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_definition([[]]); + }); + self::assertException(SchemaException::class, function () { + ScalarSchema::normalize_definition([[null]]); + }); + + $string = self::schema(["type" => ["string"], "nullable" => false]); + self::assertSame($string, ScalarSchema::normalize_definition("string")); + self::assertSame($string, ScalarSchema::normalize_definition(["string"])); + + $nstring = self::schema(["type" => ["string"]]); + 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_definition("string|int")); + + $nkey = self::schema(["type" => ["string", "int"], "nullable" => true]); + self::assertSame($nkey, ScalarSchema::normalize_definition("?string|int")); + self::assertSame($nkey, ScalarSchema::normalize_definition("string|?int")); + } +} diff --git a/tests/schema/_scalar/ScalarWrapperTest.php b/tests/schema/_scalar/ScalarWrapperTest.php new file mode 100644 index 0000000..fc2a046 --- /dev/null +++ b/tests/schema/_scalar/ScalarWrapperTest.php @@ -0,0 +1,296 @@ +get(), "value"); + self::assertSame($present, $wrapper->isPresent(), "present"); + self::assertSame($available, $wrapper->isAvailable(), "available"); + self::assertSame($valid, $wrapper->isValid(), "valid"); + self::assertSame($normalized, $wrapper->isNormalized(), "normalized"); + } + + function checkNormalize(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); + $this->checkValue($wrapper, $value, $present, $available, $valid, $normalized); + } + + 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, $normalize) { + $wrapper->resetParams(["normalize" => $normalize]); + $wrapper->reset($orig); + }); + } + + function testRaw() { + $schema = new ScalarSchema(); + + $this->checkNormalize($schema, false, false, false, true, true, true, true); + $this->checkNormalize($schema, false, true, false, true, true, true, true); + + $this->checkNormalize($schema, null, false, null, true, true, true, true); + $this->checkNormalize($schema, null, true, null, true, true, true, true); + + $obj = new stdClass(); + $this->checkNormalize($schema, $obj, false, $obj, true, true, true, true); + $this->checkNormalize($schema, $obj, true, $obj, true, true, true, true); + + $schema = new ScalarSchema("raw"); + + $this->checkNormalize($schema, false, false, false, true, true, true, true); + $this->checkNormalize($schema, false, true, false, true, true, true, true); + + $this->checkNormalize($schema, null, false, null, true, true, true, true); + $this->checkNormalize($schema, null, true, null, true, true, true, true); + + $obj = new stdClass(); + $this->checkNormalize($schema, $obj, false, $obj, true, true, true, true); + $this->checkNormalize($schema, $obj, true, $obj, true, true, true, true); + } + + function testMixed() { + $schema = new ScalarSchema("mixed"); + + $this->checkNormalize($schema, false, false, false, true, true, true, true); + $this->checkNormalize($schema, false, true, false, true, true, true, true); + + $this->checkNormalize($schema, null, false, null, true, true, false, false); + $this->checkException($schema, null, true, ValueException::class); + + $obj = new stdClass(); + $this->checkNormalize($schema, $obj, false, $obj, true, true, true, true); + $this->checkNormalize($schema, $obj, true, $obj, true, true, true, true); + + $schema = new ScalarSchema("?mixed"); + + $this->checkNormalize($schema, false, false, false, true, true, true, true); + $this->checkNormalize($schema, false, true, false, true, true, true, true); + + $this->checkNormalize($schema, null, false, null, true, true, true, true); + $this->checkNormalize($schema, null, true, null, true, true, true, true); + + $obj = new stdClass(); + $this->checkNormalize($schema, $obj, false, $obj, true, true, true, true); + $this->checkNormalize($schema, $obj, true, $obj, true, true, true, true); + } + + function testRawstring() { + $schema = new ScalarSchema("rawstring"); + + $this->checkNormalize($schema, false, false, null, true, false, true, true); + $this->checkNormalize($schema, false, true, null, true, false, true, true); + + $this->checkNormalize($schema, null, false, null, true, true, false, false); + $this->checkException($schema, null, true, ValueException::class); + + $this->checkNormalize($schema, "", false, "", true, true, true, true); + $this->checkNormalize($schema, "", true, "", true, true, true, true); + + $this->checkNormalize($schema, " ", false, " ", true, true, true, true); + $this->checkNormalize($schema, " ", true, " ", true, true, true, true); + + $this->checkNormalize($schema, "text", false, "text", true, true, true, true); + $this->checkNormalize($schema, "text", true, "text", true, true, true, true); + + $this->checkNormalize($schema, " text ", false, " text ", true, true, true, true); + $this->checkNormalize($schema, " text ", true, " text ", true, true, true, true); + + $this->checkNormalize($schema, true, false, true, true, true, true, false); + $this->checkNormalize($schema, true, true, "1", true, true, true, false); + + $this->checkNormalize($schema, 42, false, 42, true, true, true, false); + $this->checkNormalize($schema, 42, true, "42", true, true, true, false); + + $this->checkNormalize($schema, [], false, [], true, true, false, false); + $this->checkException($schema, [], true, ValueException::class); + + ## Tester valeur par défaut + $schema = new ScalarSchema(["rawstring", null]); + + $this->checkNormalize($schema, false, false, null, true, false, true, true); + $this->checkNormalize($schema, false, true, null, true, false, true, true); + + $this->checkNormalize($schema, null, false, null, true, true, false, false); + $this->checkException($schema, null, true, ValueException::class); + + $schema = new ScalarSchema(["rawstring", "default"]); + + $this->checkNormalize($schema, false, false, "default", true, true, true, true); + $this->checkNormalize($schema, false, true, "default", true, true, true, true); + + $this->checkNormalize($schema, null, false, null, true, true, false, false); + $this->checkException($schema, null, true, ValueException::class); + + ## Tester nullable + $schema = new ScalarSchema("?rawstring"); + + $this->checkNormalize($schema, null, false, null, true, true, true, true); + $this->checkNormalize($schema, null, true, null, true, true, true, true); + + ## Tester required + $schema = new ScalarSchema(["rawstring", "required" => true]); + + $this->checkNormalize($schema, false, false, null, true, false, false, false); + $this->checkException($schema, false, true, ValueException::class); + + ## Tester allow_empty === false + $inputParams = ["allow_empty" => false]; + $schema = new ScalarSchema("rawstring"); + + $this->checkNormalize($schema, null, false, null, true, true, false, false, $inputParams); + $this->checkException($schema, null, true, ValueException::class, $inputParams); + + $this->checkNormalize($schema, "", false, null, true, false, true, true, $inputParams); + $this->checkNormalize($schema, "", true, null, true, false, true, true, $inputParams); + + $schema = new ScalarSchema("?rawstring"); + + $this->checkNormalize($schema, null, false, null, true, true, true, true, $inputParams); + $this->checkNormalize($schema, null, true, null, true, true, true, true, $inputParams); + + $this->checkNormalize($schema, "", false, null, true, false, true, true, $inputParams); + $this->checkNormalize($schema, "", true, null, true, false, true, true, $inputParams); + } + + function testString() { + $schema = new ScalarSchema("string"); + + $this->checkNormalize($schema, false, false, null, true, false, true, true); + $this->checkNormalize($schema, false, true, null, true, false, true, true); + + $this->checkNormalize($schema, null, false, null, true, true, false, false); + $this->checkException($schema, null, true, ValueException::class); + + $this->checkNormalize($schema, "", false, "", true, true, true, true); + $this->checkNormalize($schema, "", true, "", true, true, true, true); + + $this->checkNormalize($schema, " ", false, "", true, true, true, false); + $this->checkNormalize($schema, " ", true, "", true, true, true, false); + + $this->checkNormalize($schema, "text", false, "text", true, true, true, true); + $this->checkNormalize($schema, "text", true, "text", true, true, true, true); + + $this->checkNormalize($schema, " text ", false, "text", true, true, true, false); + $this->checkNormalize($schema, " text ", true, "text", true, true, true, false); + + $this->checkNormalize($schema, true, false, true, true, true, true, false); + $this->checkNormalize($schema, true, true, "1", true, true, true, false); + + $this->checkNormalize($schema, 42, false, 42, true, true, true, false); + $this->checkNormalize($schema, 42, true, "42", true, true, true, false); + + $this->checkNormalize($schema, [], false, [], true, true, false, false); + $this->checkException($schema, [], true, ValueException::class); + + ## Tester nullable + $schema = new ScalarSchema("?string"); + + $this->checkNormalize($schema, null, false, null, true, true, true, true); + $this->checkNormalize($schema, null, true, null, true, true, true, true); + + ## Tester required + $schema = new ScalarSchema(["string", "required" => true]); + + $this->checkNormalize($schema, false, false, null, true, false, false, false); + $this->checkException($schema, false, true, ValueException::class); + + ## Tester allow_empty === false + $inputParams = ["allow_empty" => false]; + $schema = new ScalarSchema("string"); + + $this->checkNormalize($schema, null, false, null, true, true, false, false, $inputParams); + $this->checkException($schema, null, true, ValueException::class, $inputParams); + + $this->checkNormalize($schema, "", false, null, true, false, true, true, $inputParams); + $this->checkNormalize($schema, "", true, null, true, false, true, true, $inputParams); + + $schema = new ScalarSchema("?string"); + + $this->checkNormalize($schema, null, false, null, true, true, true, true, $inputParams); + $this->checkNormalize($schema, null, true, null, true, true, true, true, $inputParams); + + $this->checkNormalize($schema, "", false, null, true, false, true, true, $inputParams); + $this->checkNormalize($schema, "", true, null, true, false, true, true, $inputParams); + } + + function testInt() { + $schema = new ScalarSchema("int"); + + $this->checkNormalize($schema, false, false, null, true, false, true, true); + $this->checkNormalize($schema, false, true, null, true, false, true, true); + + $this->checkNormalize($schema, null, false, null, true, true, false, false); + $this->checkException($schema, null, true, ValueException::class); + + $this->checkNormalize($schema, 42, false, 42, true, true, true, true); + $this->checkNormalize($schema, 42, true, 42, true, true, true, true); + + $this->checkNormalize($schema, "42", false, "42", true, true, true, false); + $this->checkNormalize($schema, "42", true, 42, true, true, true, false); + + $this->checkNormalize($schema, "42.5", false, "42.5", true, true, true, false); + $this->checkNormalize($schema, "42.5", true, 42, true, true, true, false); + + $this->checkNormalize($schema, "42,5", false, "42,5", true, true, true, false); + $this->checkNormalize($schema, "42,5", true, 42, true, true, true, false); + + $this->checkNormalize($schema, " 42 ", false, "42", true, true, true, false); + $this->checkNormalize($schema, " 42 ", true, 42, true, true, true, false); + + $this->checkNormalize($schema, "", false, "", true, true, false, false); + $this->checkException($schema, "", true, ValueException::class); + + $this->checkNormalize($schema, " ", false, " ", true, true, false, false); + $this->checkException($schema, " ", true, ValueException::class); + + $this->checkNormalize($schema, "text", false, "text", true, true, false, false); + $this->checkException($schema, "text", true, ValueException::class); + + $this->checkNormalize($schema, true, false, true, true, true, true, false); + $this->checkNormalize($schema, true, true, 1, true, true, true, false); + + $this->checkNormalize($schema, [], false, [], true, true, false, false); + $this->checkException($schema, [], true, ValueException::class); + + ## Tester nullable + $schema = new ScalarSchema("?int"); + + $this->checkNormalize($schema, null, false, null, true, true, true, true); + $this->checkNormalize($schema, null, true, null, true, true, true, true); + + ## Tester required + $schema = new ScalarSchema(["int", "required" => true]); + + $this->checkNormalize($schema, false, false, null, true, false, false, false); + $this->checkException($schema, false, true, ValueException::class); + + ## Tester allow_empty === false + $inputParams = ["allow_empty" => false]; + $schema = new ScalarSchema("int"); + + $this->checkNormalize($schema, null, false, null, true, true, false, false, $inputParams); + $this->checkException($schema, null, true, ValueException::class, $inputParams); + + $this->checkNormalize($schema, "", false, null, true, false, true, true, $inputParams); + $this->checkNormalize($schema, "", true, null, true, false, true, true, $inputParams); + + $schema = new ScalarSchema("?int"); + + $this->checkNormalize($schema, null, false, null, true, true, true, true, $inputParams); + $this->checkNormalize($schema, null, true, null, true, true, true, true, $inputParams); + + $this->checkNormalize($schema, "", false, null, true, false, true, true, $inputParams); + $this->checkNormalize($schema, "", true, null, true, false, true, true, $inputParams); + } +} diff --git a/tests/wip/schema/types/boolTest.php b/tests/schema/types/boolTest.php similarity index 93% rename from tests/wip/schema/types/boolTest.php rename to tests/schema/types/boolTest.php index f4db6ce..00bccc4 100644 --- a/tests/wip/schema/types/boolTest.php +++ b/tests/schema/types/boolTest.php @@ -1,10 +1,10 @@ set($value); @@ -75,7 +75,7 @@ class boolTest extends TestCase { function testNbool() { /** @var ScalarWrapper $wrapper */ - Schema::nw($value, null, $schema, "?bool", $wrapper); + Schema::nw($value, null, "?bool", $schema, $wrapper); $wrapperSetter = function($value) use($wrapper) { return function() use($wrapper, $value) { $wrapper->set($value); diff --git a/tests/wip/schema/types/floatTest.php b/tests/schema/types/floatTest.php similarity index 91% rename from tests/wip/schema/types/floatTest.php rename to tests/schema/types/floatTest.php index a4679a4..eed9228 100644 --- a/tests/wip/schema/types/floatTest.php +++ b/tests/schema/types/floatTest.php @@ -1,10 +1,10 @@ set($value); @@ -53,10 +53,10 @@ class floatTest extends TestCase { function testRequiredFloat() { /** @var ScalarWrapper $wrapper */ - Schema::nw($value, null, $schema, [ + Schema::nw($value, null, [ "float", null, "required" => true, - ], $wrapper); + ], $schema, $wrapper); $wrapperSetter = function($value) use($wrapper) { return function() use($wrapper, $value) { $wrapper->set($value); @@ -75,7 +75,7 @@ class floatTest extends TestCase { function testNfloat() { /** @var ScalarWrapper $wrapper */ - Schema::nw($value, null, $schema, "?float", $wrapper); + Schema::nw($value, null, "?float", $schema, $wrapper); $wrapperSetter = function($value) use($wrapper) { return function() use($wrapper, $value) { $wrapper->set($value); @@ -106,10 +106,10 @@ class floatTest extends TestCase { function testRequiredNfloat() { /** @var ScalarWrapper $wrapper */ - Schema::nw($value, null, $schema, [ + Schema::nw($value, null, [ "?float", null, "required" => true, - ], $wrapper); + ], $schema, $wrapper); $wrapperSetter = function($value) use($wrapper) { return function() use($wrapper, $value) { $wrapper->set($value); diff --git a/tests/wip/schema/types/intTest.php b/tests/schema/types/intTest.php similarity index 91% rename from tests/wip/schema/types/intTest.php rename to tests/schema/types/intTest.php index c9cccbf..223716a 100644 --- a/tests/wip/schema/types/intTest.php +++ b/tests/schema/types/intTest.php @@ -1,10 +1,10 @@ set($value); @@ -53,10 +53,10 @@ class intTest extends TestCase { function testRequiredInt() { /** @var ScalarWrapper $wrapper */ - Schema::nw($value, null, $schema, [ + Schema::nw($value, null, [ "int", null, "required" => true, - ], $wrapper); + ], $schema, $wrapper); $wrapperSetter = function($value) use($wrapper) { return function() use($wrapper, $value) { $wrapper->set($value); @@ -75,7 +75,7 @@ class intTest extends TestCase { function testNint() { /** @var ScalarWrapper $wrapper */ - Schema::nw($value, null, $schema, "?int", $wrapper); + Schema::nw($value, null, "?int", $schema, $wrapper); $wrapperSetter = function($value) use($wrapper) { return function() use($wrapper, $value) { $wrapper->set($value); @@ -106,10 +106,10 @@ class intTest extends TestCase { function testRequiredNint() { /** @var ScalarWrapper $wrapper */ - Schema::nw($value, null, $schema, [ + Schema::nw($value, null, [ "?int", null, "required" => true, - ], $wrapper); + ], $schema, $wrapper); $wrapperSetter = function($value) use($wrapper) { return function() use($wrapper, $value) { $wrapper->set($value); diff --git a/tests/wip/schema/types/strTest.php b/tests/schema/types/strTest.php similarity index 89% rename from tests/wip/schema/types/strTest.php rename to tests/schema/types/strTest.php index 184d99c..9955001 100644 --- a/tests/wip/schema/types/strTest.php +++ b/tests/schema/types/strTest.php @@ -1,10 +1,10 @@ set($value); @@ -59,10 +59,10 @@ class strTest extends TestCase { function testRequiredStr() { /** @var ScalarWrapper $wrapper */ - Schema::nw($value, null, $schema, [ + Schema::nw($value, null, [ "string", null, "required" => true, - ], $wrapper); + ], $schema, $wrapper); $wrapperSetter = function($value) use($wrapper) { return function() use($wrapper, $value) { $wrapper->set($value); @@ -79,7 +79,7 @@ class strTest extends TestCase { function testNstr() { /** @var ScalarWrapper $wrapper */ - Schema::nw($value, null, $schema, "?string", $wrapper); + Schema::nw($value, null, "?string", $schema, $wrapper); $wrapperSetter = function($value) use($wrapper) { return function() use($wrapper, $value) { $wrapper->set($value); @@ -100,10 +100,10 @@ class strTest extends TestCase { function testRequiredNstr() { /** @var ScalarWrapper $wrapper */ - Schema::nw($value, null, $schema, [ + Schema::nw($value, null, [ "?string", null, "required" => true, - ], $wrapper); + ], $schema, $wrapper); $wrapperSetter = function($value) use($wrapper) { return function() use($wrapper, $value) { $wrapper->set($value); diff --git a/tests/wip/schema/types/unionTest.php b/tests/schema/types/unionTest.php similarity index 68% rename from tests/wip/schema/types/unionTest.php rename to tests/schema/types/unionTest.php index 312d3d4..1e65cfe 100644 --- a/tests/wip/schema/types/unionTest.php +++ b/tests/schema/types/unionTest.php @@ -1,9 +1,9 @@ set("12"); self::assertSame("12", $si); @@ -20,7 +20,7 @@ class unionTest extends TestCase { # int puis string /** @var ScalarWrapper $isw */ - Schema::nw($is, null, $iss, "int|string", $isw); + Schema::nw($is, null, "int|string", $iss, $isw); $isw->set("12"); self::assertSame("12", $is); diff --git a/tests/wip/php/access/KeyAccessTest.php b/tests/wip/php/access/KeyAccessTest.php deleted file mode 100644 index 2b1c8ff..0000000 --- a/tests/wip/php/access/KeyAccessTest.php +++ /dev/null @@ -1,66 +0,0 @@ - null, "false" => false, "empty" => ""]; - - # - $a = new KeyAccess($array, "inexistant"); - self::assertFalse($a->exists()); - self::assertFalse($a->available()); - self::assertSame($default, $a->get($default)); - - $a = new KeyAccess($array, "null"); - self::assertTrue($a->exists()); - self::assertTrue($a->available()); - self::assertSame(null, $a->get($default)); - - $a = new KeyAccess($array, "false"); - self::assertTrue($a->exists()); - self::assertFalse($a->available()); - self::assertSame($default, $a->get($default)); - - $a = new KeyAccess($array, "empty"); - self::assertTrue($a->exists()); - self::assertTrue($a->available()); - self::assertSame("", $a->get($default)); - - # - $a = new KeyAccess($array, "null", ["allow_null" => false]); - self::assertTrue($a->exists()); - self::assertFalse($a->available()); - self::assertSame($default, $a->get($default)); - - $a = new KeyAccess($array, "null", ["allow_null" => true]); - self::assertTrue($a->exists()); - self::assertTrue($a->available()); - self::assertSame(null, $a->get($default)); - - # - $a = new KeyAccess($array, "false", ["allow_false" => false]); - self::assertTrue($a->exists()); - self::assertFalse($a->available()); - self::assertSame($default, $a->get($default)); - - $a = new KeyAccess($array, "false", ["allow_false" => true]); - self::assertTrue($a->exists()); - self::assertTrue($a->available()); - self::assertSame(false, $a->get($default)); - - # - $a = new KeyAccess($array, "empty", ["allow_empty" => false]); - self::assertTrue($a->exists()); - self::assertFalse($a->available()); - self::assertSame($default, $a->get($default)); - - $a = new KeyAccess($array, "empty", ["allow_empty" => true]); - self::assertTrue($a->exists()); - self::assertTrue($a->available()); - self::assertSame("", $a->get($default)); - } -} diff --git a/tests/wip/php/access/ValueAccessTest.php b/tests/wip/php/access/ValueAccessTest.php deleted file mode 100644 index 3bd9333..0000000 --- a/tests/wip/php/access/ValueAccessTest.php +++ /dev/null @@ -1,69 +0,0 @@ -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)); - } -} diff --git a/tests/wip/schema/_assoc/AssocSchemaTest.php b/tests/wip/schema/_assoc/AssocSchemaTest.php deleted file mode 100644 index 4ac9fca..0000000 --- a/tests/wip/schema/_assoc/AssocSchemaTest.php +++ /dev/null @@ -1,95 +0,0 @@ - ["assoc"], - "schema" => null, - "type" => [null], - "default" => null, - "title" => null, - "required" => false, - "nullable" => true, - "desc" => null, - "analyzer_func" => null, - "extractor_func" => null, - "parser_func" => null, - "normalizer_func" => null, - "messages" => null, - "formatter_func" => null, - "format" => null, - "name" => null, - "pkey" => null, - "header" => null, - "computed" => null, - ]; - - static function schema(array $definition, array $keyDefinitions): array { - $definition = array_merge(self::NULL_SCHEMA, $definition, ["schema" => []]); - foreach ($keyDefinitions as $key => $keydef) { - $definition["schema"][$key] = array_merge(ScalarSchemaTest::NULL_SCHEMA, $keydef); - }; unset($subdef); - return $definition; - } - - function testNormalize() { - self::assertSame(self::schema([ - "type" => ["array"], "nullable" => true, - ], [ - "a" => [ - "type" => ["string"], "nullable" => false, - "name" => "a", "pkey" => "a", "header" => "a", - ], - ]), AssocSchema::normalize(["a" => "string"])); - - self::assertSame(self::schema([ - "type" => ["array"], "nullable" => true, - ], [ - "a" => [ - "type" => ["string"], "nullable" => false, - "name" => "a", "pkey" => "a", "header" => "a", - ], - "b" => [ - "type" => ["int"], "nullable" => false, - "name" => "b", "pkey" => "b", "header" => "b", - ], - "c" => [ - "type" => ["bool"], "nullable" => false, - "name" => "c", "pkey" => "c", "header" => "c", - ], - ]), AssocSchema::normalize([ - "a" => "string", - "b" => "int", - "c" => "bool", - ])); - } - - function testConstructor() { - $schema = new AssocSchema([ - "a" => "string", - "b" => "int", - "c" => "bool", - ]); - self::assertSame(self::schema([ - "type" => ["array"], "nullable" => true, - ], [ - "a" => [ - "type" => ["string"], "nullable" => false, - "name" => "a", "pkey" => "a", "header" => "a", - ], - "b" => [ - "type" => ["int"], "nullable" => false, - "name" => "b", "pkey" => "b", "header" => "b", - ], - "c" => [ - "type" => ["bool"], "nullable" => false, - "name" => "c", "pkey" => "c", "header" => "c", - ], - ]), $schema->getDefinition()); - yaml::dump($schema->getDefinition()); - } -} diff --git a/tests/wip/schema/_scalar/ScalarSchemaTest.php b/tests/wip/schema/_scalar/ScalarSchemaTest.php deleted file mode 100644 index c862fa5..0000000 --- a/tests/wip/schema/_scalar/ScalarSchemaTest.php +++ /dev/null @@ -1,64 +0,0 @@ - [null], - "default" => null, - "title" => null, - "required" => false, - "nullable" => true, - "desc" => null, - "analyzer_func" => null, - "extractor_func" => null, - "parser_func" => null, - "normalizer_func" => null, - "messages" => null, - "formatter_func" => null, - "format" => null, - "" => ["scalar"], - "schema" => null, - "name" => null, - "pkey" => null, - "header" => null, - "computed" => null, - ]; - - static function schema(array $schema): array { - return array_merge(self::NULL_SCHEMA, $schema); - } - - 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::assertException(SchemaException::class, function () { - ScalarSchema::normalize([[]]); - }); - self::assertException(SchemaException::class, function () { - ScalarSchema::normalize([[null]]); - }); - - $string = self::schema(["type" => ["string"], "nullable" => false]); - self::assertSame($string, ScalarSchema::normalize("string")); - self::assertSame($string, ScalarSchema::normalize(["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]])); - - $key = self::schema(["type" => ["string", "int"], "nullable" => false]); - self::assertSame($key, ScalarSchema::normalize("string|int")); - - $nkey = self::schema(["type" => ["string", "int"], "nullable" => true]); - self::assertSame($nkey, ScalarSchema::normalize("?string|int")); - self::assertSame($nkey, ScalarSchema::normalize("string|?int")); - } -} diff --git a/tests/wip/schema/_scalar/ScalarWrapperTest.php b/tests/wip/schema/_scalar/ScalarWrapperTest.php deleted file mode 100644 index 6e9326b..0000000 --- a/tests/wip/schema/_scalar/ScalarWrapperTest.php +++ /dev/null @@ -1,294 +0,0 @@ -get(), "value"); - self::assertSame($present, $wrapper->isPresent(), "present"); - self::assertSame($available, $wrapper->isAvailable(), "available"); - self::assertSame($valid, $wrapper->isValid(), "valid"); - 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 { - $wrapper = $schema->getWrapper(); - if ($inputParams !== null) $input = new Input($orig, $inputParams); - else $input = $orig; - $wrapper->reset($input, null, $verifix); - $this->checkValue($wrapper, $value, $present, $available, $valid, $normalized); - } - - function checkException(ScalarSchema $schema, $orig, bool $verifix, 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); - }); - } - - function testRaw() { - $schema = new ScalarSchema(); - - $this->checkVerifix($schema, false, false, false, true, true, true, true); - $this->checkVerifix($schema, false, true, false, true, true, true, true); - - $this->checkVerifix($schema, null, false, null, true, true, true, true); - $this->checkVerifix($schema, null, true, null, true, true, true, true); - - $obj = new stdClass(); - $this->checkVerifix($schema, $obj, false, $obj, true, true, true, true); - $this->checkVerifix($schema, $obj, true, $obj, true, true, true, true); - - $schema = new ScalarSchema("raw"); - - $this->checkVerifix($schema, false, false, false, true, true, true, true); - $this->checkVerifix($schema, false, true, false, true, true, true, true); - - $this->checkVerifix($schema, null, false, null, true, true, true, true); - $this->checkVerifix($schema, null, true, null, true, true, true, true); - - $obj = new stdClass(); - $this->checkVerifix($schema, $obj, false, $obj, true, true, true, true); - $this->checkVerifix($schema, $obj, true, $obj, true, true, true, true); - } - - function testMixed() { - $schema = new ScalarSchema("mixed"); - - $this->checkVerifix($schema, false, false, false, true, true, true, true); - $this->checkVerifix($schema, false, true, false, true, true, true, true); - - $this->checkVerifix($schema, null, false, null, true, true, false, false); - $this->checkException($schema, null, true, ValueException::class); - - $obj = new stdClass(); - $this->checkVerifix($schema, $obj, false, $obj, true, true, true, true); - $this->checkVerifix($schema, $obj, true, $obj, true, true, true, true); - - $schema = new ScalarSchema("?mixed"); - - $this->checkVerifix($schema, false, false, false, true, true, true, true); - $this->checkVerifix($schema, false, true, false, true, true, true, true); - - $this->checkVerifix($schema, null, false, null, true, true, true, true); - $this->checkVerifix($schema, null, true, null, true, true, true, true); - - $obj = new stdClass(); - $this->checkVerifix($schema, $obj, false, $obj, true, true, true, true); - $this->checkVerifix($schema, $obj, true, $obj, true, true, true, true); - } - - function testRawstring() { - $schema = new ScalarSchema("rawstring"); - - $this->checkVerifix($schema, false, false, null, true, false, true, true); - $this->checkVerifix($schema, false, true, null, true, false, true, true); - - $this->checkVerifix($schema, null, false, null, true, true, false, false); - $this->checkException($schema, null, true, ValueException::class); - - $this->checkVerifix($schema, "", false, "", true, true, true, true); - $this->checkVerifix($schema, "", true, "", true, true, true, true); - - $this->checkVerifix($schema, " ", false, " ", true, true, true, true); - $this->checkVerifix($schema, " ", true, " ", true, true, true, true); - - $this->checkVerifix($schema, "text", false, "text", true, true, true, true); - $this->checkVerifix($schema, "text", true, "text", true, true, true, true); - - $this->checkVerifix($schema, " text ", false, " text ", true, true, true, true); - $this->checkVerifix($schema, " text ", true, " text ", true, true, true, true); - - $this->checkVerifix($schema, true, false, true, true, true, true, false); - $this->checkVerifix($schema, true, true, "1", true, true, true, false); - - $this->checkVerifix($schema, 42, false, 42, true, true, true, false); - $this->checkVerifix($schema, 42, true, "42", true, true, true, false); - - $this->checkVerifix($schema, [], false, [], true, true, false, false); - $this->checkException($schema, [], true, ValueException::class); - - ## Tester valeur par défaut - $schema = new ScalarSchema(["rawstring", null]); - - $this->checkVerifix($schema, false, false, null, true, false, true, true); - $this->checkVerifix($schema, false, true, null, true, false, true, true); - - $this->checkVerifix($schema, null, false, null, true, true, false, false); - $this->checkException($schema, null, true, ValueException::class); - - $schema = new ScalarSchema(["rawstring", "default"]); - - $this->checkVerifix($schema, false, false, "default", true, true, true, true); - $this->checkVerifix($schema, false, true, "default", true, true, true, true); - - $this->checkVerifix($schema, null, false, null, true, true, false, false); - $this->checkException($schema, null, true, ValueException::class); - - ## Tester nullable - $schema = new ScalarSchema("?rawstring"); - - $this->checkVerifix($schema, null, false, null, true, true, true, true); - $this->checkVerifix($schema, null, true, null, true, true, true, true); - - ## Tester required - $schema = new ScalarSchema(["rawstring", "required" => true]); - - $this->checkVerifix($schema, false, false, null, true, false, false, false); - $this->checkException($schema, false, true, ValueException::class); - - ## Tester allow_empty === false - $inputParams = ["allow_empty" => false]; - $schema = new ScalarSchema("rawstring"); - - $this->checkVerifix($schema, null, false, null, true, true, false, false, $inputParams); - $this->checkException($schema, null, true, ValueException::class, $inputParams); - - $this->checkVerifix($schema, "", false, null, true, false, true, true, $inputParams); - $this->checkVerifix($schema, "", true, null, true, false, true, true, $inputParams); - - $schema = new ScalarSchema("?rawstring"); - - $this->checkVerifix($schema, null, false, null, true, true, true, true, $inputParams); - $this->checkVerifix($schema, null, true, null, true, true, true, true, $inputParams); - - $this->checkVerifix($schema, "", false, null, true, false, true, true, $inputParams); - $this->checkVerifix($schema, "", true, null, true, false, true, true, $inputParams); - } - - function testString() { - $schema = new ScalarSchema("string"); - - $this->checkVerifix($schema, false, false, null, true, false, true, true); - $this->checkVerifix($schema, false, true, null, true, false, true, true); - - $this->checkVerifix($schema, null, false, null, true, true, false, false); - $this->checkException($schema, null, true, ValueException::class); - - $this->checkVerifix($schema, "", false, "", true, true, true, true); - $this->checkVerifix($schema, "", true, "", true, true, true, true); - - $this->checkVerifix($schema, " ", false, "", true, true, true, false); - $this->checkVerifix($schema, " ", true, "", true, true, true, false); - - $this->checkVerifix($schema, "text", false, "text", true, true, true, true); - $this->checkVerifix($schema, "text", true, "text", true, true, true, true); - - $this->checkVerifix($schema, " text ", false, "text", true, true, true, false); - $this->checkVerifix($schema, " text ", true, "text", true, true, true, false); - - $this->checkVerifix($schema, true, false, true, true, true, true, false); - $this->checkVerifix($schema, true, true, "1", true, true, true, false); - - $this->checkVerifix($schema, 42, false, 42, true, true, true, false); - $this->checkVerifix($schema, 42, true, "42", true, true, true, false); - - $this->checkVerifix($schema, [], false, [], true, true, false, false); - $this->checkException($schema, [], true, ValueException::class); - - ## Tester nullable - $schema = new ScalarSchema("?string"); - - $this->checkVerifix($schema, null, false, null, true, true, true, true); - $this->checkVerifix($schema, null, true, null, true, true, true, true); - - ## Tester required - $schema = new ScalarSchema(["string", "required" => true]); - - $this->checkVerifix($schema, false, false, null, true, false, false, false); - $this->checkException($schema, false, true, ValueException::class); - - ## Tester allow_empty === false - $inputParams = ["allow_empty" => false]; - $schema = new ScalarSchema("string"); - - $this->checkVerifix($schema, null, false, null, true, true, false, false, $inputParams); - $this->checkException($schema, null, true, ValueException::class, $inputParams); - - $this->checkVerifix($schema, "", false, null, true, false, true, true, $inputParams); - $this->checkVerifix($schema, "", true, null, true, false, true, true, $inputParams); - - $schema = new ScalarSchema("?string"); - - $this->checkVerifix($schema, null, false, null, true, true, true, true, $inputParams); - $this->checkVerifix($schema, null, true, null, true, true, true, true, $inputParams); - - $this->checkVerifix($schema, "", false, null, true, false, true, true, $inputParams); - $this->checkVerifix($schema, "", true, null, true, false, true, true, $inputParams); - } - - function testInt() { - $schema = new ScalarSchema("int"); - - $this->checkVerifix($schema, false, false, null, true, false, true, true); - $this->checkVerifix($schema, false, true, null, true, false, true, true); - - $this->checkVerifix($schema, null, false, null, true, true, false, false); - $this->checkException($schema, null, true, ValueException::class); - - $this->checkVerifix($schema, 42, false, 42, true, true, true, true); - $this->checkVerifix($schema, 42, true, 42, true, true, true, true); - - $this->checkVerifix($schema, "42", false, "42", true, true, true, false); - $this->checkVerifix($schema, "42", true, 42, true, true, true, false); - - $this->checkVerifix($schema, "42.5", false, "42.5", true, true, true, false); - $this->checkVerifix($schema, "42.5", true, 42, true, true, true, false); - - $this->checkVerifix($schema, "42,5", false, "42,5", true, true, true, false); - $this->checkVerifix($schema, "42,5", true, 42, true, true, true, false); - - $this->checkVerifix($schema, " 42 ", false, "42", true, true, true, false); - $this->checkVerifix($schema, " 42 ", true, 42, true, true, true, false); - - $this->checkVerifix($schema, "", false, "", true, true, false, false); - $this->checkException($schema, "", true, ValueException::class); - - $this->checkVerifix($schema, " ", false, " ", true, true, false, false); - $this->checkException($schema, " ", true, ValueException::class); - - $this->checkVerifix($schema, "text", false, "text", true, true, false, false); - $this->checkException($schema, "text", true, ValueException::class); - - $this->checkVerifix($schema, true, false, true, true, true, true, false); - $this->checkVerifix($schema, true, true, 1, true, true, true, false); - - $this->checkVerifix($schema, [], false, [], true, true, false, false); - $this->checkException($schema, [], true, ValueException::class); - - ## Tester nullable - $schema = new ScalarSchema("?int"); - - $this->checkVerifix($schema, null, false, null, true, true, true, true); - $this->checkVerifix($schema, null, true, null, true, true, true, true); - - ## Tester required - $schema = new ScalarSchema(["int", "required" => true]); - - $this->checkVerifix($schema, false, false, null, true, false, false, false); - $this->checkException($schema, false, true, ValueException::class); - - ## Tester allow_empty === false - $inputParams = ["allow_empty" => false]; - $schema = new ScalarSchema("int"); - - $this->checkVerifix($schema, null, false, null, true, true, false, false, $inputParams); - $this->checkException($schema, null, true, ValueException::class, $inputParams); - - $this->checkVerifix($schema, "", false, null, true, false, true, true, $inputParams); - $this->checkVerifix($schema, "", true, null, true, false, true, true, $inputParams); - - $schema = new ScalarSchema("?int"); - - $this->checkVerifix($schema, null, false, null, true, true, true, true, $inputParams); - $this->checkVerifix($schema, null, true, null, true, true, true, true, $inputParams); - - $this->checkVerifix($schema, "", false, null, true, false, true, true, $inputParams); - $this->checkVerifix($schema, "", true, null, true, false, true, true, $inputParams); - } -}