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 @@
+ * $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 @@ access->ensureAssoc($keys, $params); } - function ensureKeys(array $defaults, ?array $params=null): void { + function ensureKeys(array $defaults, ?array $missings, ?array $params=null): void { #XXX fonction de $accessType? #$this->access->ensureKeys($defaults, $params); } diff --git a/src/php/access/FormAccess.php b/src/php/access/FormAccess.php index 2368e75..039ec50 100644 --- a/src/php/access/FormAccess.php +++ b/src/php/access/FormAccess.php @@ -1,5 +1,5 @@ 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]); } } } diff --git a/src/php/access/PostAccess.php b/src/php/access/PostAccess.php index e9c7f13..24c9e22 100644 --- a/src/php/access/PostAccess.php +++ b/src/php/access/PostAccess.php @@ -1,7 +1,5 @@ dest; if ($dest === null) { # comme ne connait pas la classe de l'objet destination, on n'essaie pas diff --git a/src/php/access/ShadowAccess.php b/src/php/access/ShadowAccess.php index bdd12d6..accb960 100644 --- a/src/php/access/ShadowAccess.php +++ b/src/php/access/ShadowAccess.php @@ -1,5 +1,5 @@ writer->ensureAssoc($keys, $params); } - function ensureKeys(array $defaults, ?array $params=null): void { - $this->writer->ensureKeys($defaults, $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 { diff --git a/src/php/access/ValueAccess.php b/src/php/access/ValueAccess.php new file mode 100644 index 0000000..88ea8bd --- /dev/null +++ b/src/php/access/ValueAccess.php @@ -0,0 +1,8 @@ +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(); } - /** - * 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 aef756d..fbb441b 100644 --- a/src/schema/Schema.php +++ b/src/schema/Schema.php @@ -1,21 +1,21 @@ getWrapper($value, $valueKey, null, $wrapper); + return self::ns($definition, null, $schema)->getWrapper($value, $valueKey, null, $wrapper); } protected static function have_nature(array $definition, ?string &$nature=null): bool { @@ -187,12 +187,12 @@ abstract class Schema implements ArrayAccess { tbool::ensure_bool($definition["required"]); tbool::ensure_bool($definition["nullable"]); tcontent::ensure_ncontent($definition["desc"]); - tcallable::ensure_ncallable($definition["analyzer_func"]); - tcallable::ensure_ncallable($definition["extractor_func"]); - tcallable::ensure_ncallable($definition["parser_func"]); - tcallable::ensure_ncallable($definition["normalizer_func"]); + tfunc::ensure_nfunc($definition["analyzer_func"]); + tfunc::ensure_nfunc($definition["extractor_func"]); + tfunc::ensure_nfunc($definition["parser_func"]); + tfunc::ensure_nfunc($definition["normalizer_func"]); tarray::ensure_narray($definition["messages"]); - tcallable::ensure_ncallable($definition["formatter_func"]); + tfunc::ensure_nfunc($definition["formatter_func"]); tbool::ensure_nbool($definition["computed"]); switch ($nature[0] ?? null) { @@ -252,11 +252,11 @@ abstract class Schema implements ArrayAccess { case "assoc": foreach ($definition["schema"] as &$keydef) { self::_ensure_schema_instances($keydef); - Schema::ns($keydef, null, null, false); + Schema::ns(null, null, $keydef, false); }; unset($keydef); break; case "list": - Schema::ns($definition["schema"], null, null, false); + Schema::ns(null, null, $definition["schema"], false); break; } } @@ -280,14 +280,7 @@ abstract class Schema implements ArrayAccess { */ abstract function getKeys(): array; - abstract function getSchema($key): Schema; - - /** retourner true si le schéma est de nature tableau associatif */ - function isAssoc(?AssocSchema &$schema=null): bool { return false; } - /** retourner true si le schéma est de nature liste */ - function isList(?ListSchema &$schema=null): bool { return false; } - /** retourner true si le schéma est de nature scalaire */ - function isScalar(?ScalarSchema &$schema=null): bool { return false; } + abstract function getSchema($key=false): Schema; abstract protected function newWrapper(): Wrapper; 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 @@ ensureKeys] [--> orderKeys]` - * rajouter l'attribut "size" pour spécifier la taille maximale des valeurs * cela pourrait servir pour générer automatiquement des tables SQL * ou pour modéliser un schéma FSV +* support allowed_values + * valeurs composite/computed * analyse / vérification de la valeur complète après calcul du résultat, si tous les résultats sont bons @@ -18,7 +14,8 @@ * fonction getter_func, setter_func, deleter_func pour les propriétés de type computed * tdate et tdatetime. qu'en est-il des autres classes (delay, etc.) - * possibilité de spécifier le format de la date à analyser + * parse_format pour spécifier le format d'analyse au lieu de l'auto-détecter + * ScalarSchema::from_property() * l'argument $format de AssocWrapper::format() est un tableau associatif diff --git a/src/schema/Wrapper.php b/src/schema/Wrapper.php index 7c558e8..7ea7d1e 100644 --- a/src/schema/Wrapper.php +++ b/src/schema/Wrapper.php @@ -1,21 +1,14 @@ context->resetParams($params); } - protected function resetContext($resetSelectedKey): void { + protected function resetContext(bool $resetSelectedKey): void { $context = $this->context; - $context->type = null; + $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, $resetSelectedKey=false): void { + 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) { - if ($params["normalize"] ?? $context->normalize) { - $this->normalize($params); - } + if ($context->analyzed && ($params["normalize"] ?? $context->normalize)) { + $this->normalize($params); } } @@ -80,6 +74,10 @@ abstract class Wrapper implements ArrayAccess, IteratorAggregate { /** 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; @@ -95,11 +93,11 @@ abstract class Wrapper implements ArrayAccess, IteratorAggregate { $modified = false; } - /** @var ScalarResult $result */ - $result = $context->result; + $result = $this->getConsolidatedResult(); if (!$result->valid) { $result->throw($params["throw"] ?? $context->throw); } + return $modified; } @@ -112,16 +110,22 @@ 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(); } /** @@ -129,9 +133,18 @@ abstract class Wrapper implements ArrayAccess, IteratorAggregate { * * 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 */ function getResult($key=false): Result { - return $this->context->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 */ @@ -149,6 +162,11 @@ abstract class Wrapper implements ArrayAccess, IteratorAggregate { 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 */ function isValid($key=false): bool { return $this->getResult($key)->valid; diff --git a/src/schema/WrapperContext.php b/src/schema/WrapperContext.php index d22a81f..a47bfd7 100644 --- a/src/schema/WrapperContext.php +++ b/src/schema/WrapperContext.php @@ -1,13 +1,11 @@ resetParams($params); @@ -23,9 +21,9 @@ class WrapperContext { function resetParams(?array $params): void { $this->params = $params; - $this->analyze = $params["analyze"] ?? self::DEFAULT_ANALYZE; - $this->normalize = $params["normalize"] ?? self::DEFAULT_NORMALIZE; - $this->throw = $params["throw"] ?? self::DEFAULT_THROW; + $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 */ @@ -39,8 +37,6 @@ class WrapperContext { /** @var mixed */ public $value = null; - /** @var string|int|null clé sélectionnée */ - public $selectedKey = null; /** type de la valeur de la clé sélectionnée après analyse */ public ?IType $type = null; /** résultat de l'analyse de la valeur de la clé sélectionnée */ diff --git a/src/schema/_assoc/AssocSchema.php b/src/schema/_assoc/AssocSchema.php index b9fac49..8f05843 100644 --- a/src/schema/_assoc/AssocSchema.php +++ b/src/schema/_assoc/AssocSchema.php @@ -1,11 +1,11 @@ _definition = $definition; self::_ensure_type($definition); self::_ensure_schema_instances($definition); + } else { + # ici, $definition contient un schema déjà instancié, mais c'est le mieux + # qu'on puisse faire + $this->_definition = $definition; } $this->definition = $definition; $keys = []; @@ -56,19 +60,14 @@ class AssocSchema extends Schema { $this->keys = $keys; } - function isAssoc(?AssocSchema &$schema=null): bool { - $schema = $this; - return true; - } - protected array $keys; function getKeys(): array { return $this->keys; } - function getSchema($key): Schema { - if ($key === null) return $this; + function getSchema($key=false): Schema { + if ($key === null || $key === false) return $this; $schema = $this->definition["schema"][$key] ?? null; if ($schema === null) throw ValueException::invalid_key($key); return $schema; @@ -83,7 +82,15 @@ class AssocSchema extends Schema { # cf le code similaire dans AssocWrapper::__construct() $dontAnalyze = $value === null && $wrapper === null; if (!($wrapper instanceof AssocWrapper)) $wrapper = $this->newWrapper(); + + # 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 1add86c..dddb067 100644 --- a/src/schema/_assoc/AssocWrapper.php +++ b/src/schema/_assoc/AssocWrapper.php @@ -1,53 +1,54 @@ context = $context = new AssocWrapperContext($schema, null, null, $params); + $paramsNoThrow = cl::merge($params, [ + "throw" => false, + ]); + $keys = $schema->getKeys(); $keyWrappers = []; foreach ($keys as $key) { - $keyWrappers[$key] = $schema->getSchema($key)->getWrapper(); + $keyDummy = null; + $keyWrappers[$key] = $schema->getSchema($key)->getWrapper($keyDummy, null, $paramsNoThrow); } - $this->context = $context = new AssocWrapperContext($schema, null, null, $params); - $context->arrayWrapper = new ScalarWrapper($schema, $dummy, null, null, $context); $context->keys = $keys; $context->keyWrappers = $keyWrappers; - # calculer manuellemet throw ici parce que WrapperContext le met à true par - # défaut. on veut pouvoir mettre temporairement throw à false si jamais il - # n'est pas spécifié par l'utilisateur - $throw = $params["throw"] ?? null; - # Si $value est null, ne pas lancer d'exception, parce qu'on considère que - # c'est une initialisation sans conséquences - if ($throw === null && $value !== null) $throw = true; - $context->throw = $throw ?? false; - $this->reset($value, $valueKey); - $context->throw = $throw ?? true; - } + $arrayDummy = null; + $context->arrayWrapper = new ScalarWrapper($schema, $arrayDummy, null, $paramsNoThrow, $context); - function isAssoc(?AssocWrapper &$wrapper=null): bool { $wrapper = $this; return true; } + $context->consolidatedResult = new ConsolidatedResult(); + + if ($value !== null) { + # n'initialiser que si $value n'est pas null + $this->reset($value, $valueKey); + } + } /** @var AssocWrapperContext */ protected WrapperContext $context; - protected function resetContext($resetSelectedKey): void { + protected function resetContext(bool $resetSelectedKey): void { + parent::resetContext($resetSelectedKey); $context = $this->context; $context->arrayWrapper->getResult()->reset(); foreach ($context->keyWrappers as $wrapper) { $wrapper->getResult()->reset(); } - $context->analyzed = false; - $context->normalized = false; if ($resetSelectedKey) $context->selectedKey = null; } @@ -70,7 +71,8 @@ class AssocWrapper extends Wrapper { } protected function _getWrapper($key): Wrapper { - if ($key === null) return $this->context->arrayWrapper; + $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; @@ -88,22 +90,40 @@ class AssocWrapper extends Wrapper { * @param AssocWrapper $wrapper */ static function _analyze(WrapperContext $context, Wrapper $wrapper, ?array $params): int { - if ($context->ensureArray) { + if ($params["ensure_array"] ?? $context->ensureArray) { $valueKey = $context->valueKey; $array = $context->input->get($valueKey); if ($array === null) $context->input->set([], $valueKey); } + + if ($params["ensure_assoc"] ?? $context->ensureAssoc) { + $context->input->ensureAssoc($context->schema->getKeys()); + } + $what = ScalarWrapper::_analyze($context, $wrapper, $params); - /** @var ScalarResult $result */ - $result = $context->result; - if (!$result->valid) return $what; - foreach ($context->keyWrappers as $keyWrapper) { + if (!$context->result->valid) return $what; + + $result = $context->consolidatedResult; + $result->setValid(); + foreach ($context->keyWrappers as $key => $keyWrapper) { $keyWrapper->analyze($params); - if (!$keyWrapper->isValid()) { - $what = ref_analyze::INVALID; - $result->addInvalidMessage($keyWrapper); + 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; } @@ -112,6 +132,29 @@ class AssocWrapper extends Wrapper { * @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; @@ -119,7 +162,12 @@ class AssocWrapper extends Wrapper { return $modified; } + protected function getConsolidatedResult(): Result { + return $this->context->consolidatedResult; + } + function getResult($key=false): Result { + if ($key === null) return $this->getConsolidatedResult(); if ($key === false) $key = $this->context->selectedKey; return $this->_getWrapper($key)->getResult(); } diff --git a/src/schema/_assoc/AssocWrapperContext.php b/src/schema/_assoc/AssocWrapperContext.php index c504197..38c7182 100644 --- a/src/schema/_assoc/AssocWrapperContext.php +++ b/src/schema/_assoc/AssocWrapperContext.php @@ -1,28 +1,27 @@ ensureArray = $params["ensure_array"] ?? self::DEFAULT_ENSURE_ARRAY; - $this->ensureKeys = $params["ensure_keys"] ?? self::DEFAULT_ENSURE_KEYS; - $this->ensureOrder = $params["ensure_order"] ?? self::DEFAULT_ENSURE_ORDER; - } public bool $ensureArray; + public bool $ensureAssoc; public bool $ensureKeys; public bool $ensureOrder; + public function resetParams(?array $params): void { + parent::resetParams($params); + $this->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 */ @@ -30,4 +29,10 @@ class AssocWrapperContext extends WrapperContext { /** @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 4887e95..02a137b 100644 --- a/src/schema/_list/ListSchema.php +++ b/src/schema/_list/ListSchema.php @@ -1,10 +1,10 @@ definition = $definition; } - function isList(?ListSchema &$schema=null): bool { - $schema = $this; - return true; - } - const KEYS = [null]; function getKeys(): array { return self::KEYS; } - public function getSchema($key): Schema { + public function getSchema($key=false): Schema { if ($key !== null) throw ValueException::invalid_key($key); return $this; } 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 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 = $this->getMessage($messageKey, $schema); - if ($exception !== null) { - $tmessage = ValueException::get_message($exception); - if ($tmessage) $message = $tmessage; - } - $this->message = $message; - $this->exception = $exception; - return ref_analyze::INVALID; - } - - function addInvalidMessage(Wrapper $wrapper): void { - $this->resultAvailable = true; - $this->present = true; - $this->available = true; - $this->null = false; - $this->valid = false; - $this->messageKey = "invalid"; - $result = $wrapper->getResult(); - $resultException = $result->exception; - $resultMessage = $result->message; - if ($resultException !== null) { - $tmessage = ValueException::get_message($resultException); - if ($tmessage) { - if ($resultMessage !== null) $resultMessage .= ": "; - $resultMessage .= $tmessage; - } - } - $message = $this->message; - if ($message) $message .= "\n"; - $message .= $resultMessage; - $this->message = $message; - } - - function setValid($normalizedValue=null): int { - $this->resultAvailable = true; - $this->present = true; - $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/_scalar/ScalarSchema.php b/src/schema/_scalar/ScalarSchema.php index 597a34b..c1e5125 100644 --- a/src/schema/_scalar/ScalarSchema.php +++ b/src/schema/_scalar/ScalarSchema.php @@ -1,11 +1,10 @@ _definition = $definition; self::_ensure_type($definition); self::_ensure_schema_instances($definition); + } else { + # ici, $definition contient un schema déjà instancié, mais c'est le mieux + # qu'on puisse faire + $this->_definition = $definition; } $this->definition = $definition; } - function isScalar(?ScalarSchema &$schema=null): bool { - $schema = $this; - return true; - } - - const KEYS = [null]; + const KEYS = []; function getKeys(): array { return self::KEYS; } - function getSchema($key): Schema { - if ($key !== null) throw ValueException::invalid_key($key); - return $this; + function getSchema($key=false): Schema { + if ($key === false || $key === null || $key === "") return $this; + throw ValueException::invalid_key($key); } protected function newWrapper(): ScalarWrapper { @@ -85,7 +83,15 @@ class ScalarSchema extends Schema { # cf le code similaire dans ScalarWrapper::__construct() $dontAnalyze = $value === null && $wrapper === null; if (!($wrapper instanceof ScalarWrapper)) $wrapper = $this->newWrapper(); + + # 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); } } diff --git a/src/schema/_scalar/ScalarWrapper.php b/src/schema/_scalar/ScalarWrapper.php index 588e8b3..00f7896 100644 --- a/src/schema/_scalar/ScalarWrapper.php +++ b/src/schema/_scalar/ScalarWrapper.php @@ -1,45 +1,38 @@ result = new ScalarResult(); + $context->result = new Result(); $this->context = $context; - # calculer manuellemet throw ici parce que WrapperContext le met à true par - # défaut. on veut pouvoir mettre temporairement throw à false si jamais il - # n'est pas spécifié par l'utilisateur - $throw = $params["throw"] ?? null; - # Si $value est null, ne pas lancer d'exception, parce qu'on considère que - # c'est une initialisation sans conséquences - if ($throw === null && $value !== null) $throw = true; - $context->throw = $throw ?? false; - $this->reset($value, $valueKey); - $context->throw = $throw ?? true; + if ($value !== null) { + # n'initialiser que si $value n'est pas null + $this->reset($value, $valueKey); + } else { + # il faut au moins que le type soit disponible + $this->resetContext(false); + } } - function isScalar(?ScalarWrapper &$wrapper=null): bool { $wrapper = $this; return true; } - protected WrapperContext $context; function getKeys(): array { @@ -47,9 +40,9 @@ class ScalarWrapper extends Wrapper { } /** @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 */ @@ -58,7 +51,6 @@ class ScalarWrapper extends Wrapper { $schema = $context->schema; $input = $context->input; $valueKey = $context->valueKey; - /** @var ScalarResult $result */ $result = $context->result; $default = $schema->default; @@ -131,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); @@ -152,7 +154,6 @@ class ScalarWrapper extends Wrapper { $schema = $context->schema; $input = $context->input; $valueKey = $context->valueKey; - /** @var ScalarResult $result */ $result = $context->result; /** @var func $analyzerFunc */ @@ -201,7 +202,6 @@ class ScalarWrapper extends Wrapper { $schema = $context->schema; $input = $context->input; $valueKey = $context->valueKey; - /** @var ScalarResult $result */ $result = $context->result; $normalize = false; diff --git a/src/schema/input/FormInput.php b/src/schema/input/FormInput.php index 1b043d5..5bc39f8 100644 --- a/src/schema/input/FormInput.php +++ b/src/schema/input/FormInput.php @@ -1,11 +1,11 @@ access = new PropertyAccess($dest, null, [ "allow_empty" => $allowEmpty, "allow_null" => true, ]); - } elseif ($accessType == self::ACCESS_KEY) { + } elseif ($accessType == ref_input::ACCESS_KEY) { $this->access = new KeyAccess($dest, null, [ "allow_empty" => $allowEmpty, "allow_null" => true, @@ -72,4 +71,20 @@ class Input { $input->access = $this->access->addKey($key); return $input; } + + function ensureAssoc(array $keys, ?array $params=null): void { + $this->access->ensureAssoc($keys, $params); + } + + function ensureKeys(array $defaults, ?array $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 9b5fef6..47afe22 100644 --- a/src/schema/input/PostInput.php +++ b/src/schema/input/PostInput.php @@ -1,8 +1,8 @@ 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 $normalized || is_scalar($value); @@ -46,7 +48,6 @@ class tarray extends _tstring { } /** - * @var ScalarResult $result * @var ScalarSchema $schema */ function normalize(&$value, Result $result, Schema $schema): bool { diff --git a/src/schema/types/tbool.php b/src/schema/types/tbool.php index dffa9c4..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,7 +98,6 @@ class tbool extends _tformatable { } /** - * @var ScalarResult $result * @var ScalarSchema $schema */ function normalize(&$value, Result $result, Schema $schema): bool { diff --git a/src/schema/types/tcontent.php b/src/schema/types/tcontent.php index 0a600f4..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 $normalized || is_scalar($value); @@ -33,7 +36,6 @@ abstract class tcontent extends _tunion { } /** - * @var ScalarResult $result * @var ScalarSchema $schema */ function normalize(&$value, Result $result, Schema $schema): bool { diff --git a/src/schema/types/tfloat.php b/src/schema/types/tfloat.php index 85767b6..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,7 +43,6 @@ class tfloat extends _tformatable { } /** - * @var ScalarResult $result * @var ScalarSchema $schema */ function normalize(&$value, Result $result, Schema $schema): bool { diff --git a/src/schema/types/tcallable.php b/src/schema/types/tfunc.php similarity index 61% rename from src/schema/types/tcallable.php rename to src/schema/types/tfunc.php index 76e2656..e846049 100644 --- a/src/schema/types/tcallable.php +++ b/src/schema/types/tfunc.php @@ -1,31 +1,34 @@ class; } - function isAvailable(Input $input, $valueKey): bool { - return $input->isAvailable($valueKey); + function getNullValue() { + return null; } public function isNull($value): bool { @@ -38,7 +36,6 @@ class tgeneric extends _tsimple { } /** - * @var ScalarResult $result * @var ScalarSchema $schema */ function normalize(&$value, Result $result, Schema $schema): bool { diff --git a/src/schema/types/tint.php b/src/schema/types/tint.php index eddab99..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,7 +45,6 @@ class tint extends _tformatable { } /** - * @var ScalarResult $result * @var ScalarSchema $schema */ function normalize(&$value, Result $result, Schema $schema): bool { diff --git a/src/schema/types/tkey.php b/src/schema/types/tkey.php index f1cca27..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 $normalized || is_scalar($value); @@ -33,7 +36,6 @@ class tkey extends _tunion { } /** - * @var ScalarResult $result * @var ScalarSchema $schema */ function normalize(&$value, Result $result, Schema $schema): bool { diff --git a/src/schema/types/tmixed.php b/src/schema/types/tmixed.php index f549204..4bb4e6a 100644 --- a/src/schema/types/tmixed.php +++ b/src/schema/types/tmixed.php @@ -1,11 +1,10 @@ isAvailable($valueKey); } @@ -32,7 +40,6 @@ class tmixed extends _tsimple { } /** - * @var ScalarResult $result * @var ScalarSchema $schema */ function normalize(&$value, Result $result, Schema $schema): bool { diff --git a/src/schema/types/tpkey.php b/src/schema/types/tpkey.php index 7b2234b..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 $normalized || is_scalar($value); @@ -38,7 +41,6 @@ class tpkey extends _tunion { } /** - * @var ScalarResult $result * @var ScalarSchema $schema */ function normalize(&$value, Result $result, Schema $schema): bool { 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,7 +44,6 @@ class trawstring extends _tstring { } /** - * @var ScalarResult $result * @var ScalarSchema $schema */ function normalize(&$value, Result $result, Schema $schema): bool { 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/wip/php/access/KeyAccessTest.php b/tests/php/access/KeyAccessTest.php similarity index 98% rename from tests/wip/php/access/KeyAccessTest.php rename to tests/php/access/KeyAccessTest.php index 8911ccd..6c17fef 100644 --- a/tests/wip/php/access/KeyAccessTest.php +++ b/tests/php/access/KeyAccessTest.php @@ -1,5 +1,5 @@ ensureKeys($defaults, $params); + $a->ensureKeys($defaults, $missings, $params); self::assertSame($expected, $v); } function testEnsureKeys() { @@ -187,7 +187,7 @@ class KeyAccessTest extends TestCase { $v = $orig; $a = new KeyAccess($v); $keys = array_keys($defaults); $a->ensureAssoc($keys, $params); - $a->ensureKeys($defaults, $params); + $a->ensureKeys($defaults, $missings, $params); $a->ensureOrder($keys, $params); self::assertSame($expected, $v); } 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 = <<