321 lines
11 KiB
PHP
321 lines
11 KiB
PHP
<?php
|
|
namespace nur\sery\wip\schema;
|
|
|
|
use ArrayAccess;
|
|
use nulib\AccessException;
|
|
use nulib\cl;
|
|
use nulib\ref\schema\ref_schema;
|
|
use nulib\ref\schema\ref_types;
|
|
use nur\sery\wip\schema\_assoc\AssocSchema;
|
|
use nur\sery\wip\schema\_list\ListSchema;
|
|
use nur\sery\wip\schema\_scalar\ScalarSchema;
|
|
use nur\sery\wip\schema\types\IType;
|
|
use nur\sery\wip\schema\types\tarray;
|
|
use nur\sery\wip\schema\types\tbool;
|
|
use nur\sery\wip\schema\types\tcallable;
|
|
use nur\sery\wip\schema\types\tcontent;
|
|
use nur\sery\wip\schema\types\tpkey;
|
|
use nur\sery\wip\schema\types\trawstring;
|
|
|
|
/**
|
|
* Class Schema
|
|
*
|
|
* @property-read array|IType $type
|
|
* @property-read mixed $default
|
|
* @property-read string|null $title
|
|
* @property-read bool $required
|
|
* @property-read bool $nullable
|
|
* @property-read string|array|null $desc
|
|
* @property-read callable|null $analyzerFunc
|
|
* @property-read callable|null $extractorFunc
|
|
* @property-read callable|null $parserFunc
|
|
* @property-read callable|null $normalizerFunc
|
|
* @property-read array|null $messages
|
|
* @property-read callable|null $formatterFunc
|
|
* @property-read mixed $format
|
|
* @property-read array $nature
|
|
* @property-read array|null $schema
|
|
* @property-read string|int|null $name
|
|
* @property-read string|array|null $pkey
|
|
* @property-read string|null $header
|
|
* @property-read bool|null $computed
|
|
*/
|
|
abstract class Schema implements ArrayAccess {
|
|
/**
|
|
* créer le cas échéant une nouvelle instance de {@link Schema} à partir d'une
|
|
* définition de schéma
|
|
*
|
|
* - si $schema est une instance de schéma, la retourner
|
|
* - si $schema est un array, c'est une définition, et elle est remplacée par
|
|
* l'instance de Schema nouvelle créée
|
|
* - sinon, prendre $definition comme définition
|
|
*/
|
|
static function ns(&$schema, $definition=null, $definitionKey=null, bool $normalize=true): self {
|
|
if (is_array($schema)) {
|
|
$definition = $schema;
|
|
$schema = null;
|
|
}
|
|
if ($schema === null) {
|
|
if (AssocSchema::isa_definition($definition)) {
|
|
$schema = new AssocSchema($definition, $definitionKey, $normalize);
|
|
} elseif (ListSchema::isa_definition($definition)) {
|
|
$schema = new ListSchema($definition, $definitionKey, $normalize);
|
|
} elseif (ScalarSchema::isa_definition($definition)) {
|
|
$schema = new ScalarSchema($definition, $definitionKey, $normalize);
|
|
} else {
|
|
throw SchemaException::invalid_schema();
|
|
}
|
|
}
|
|
return $schema;
|
|
}
|
|
|
|
/**
|
|
* Créer une nouvelle instance de {@link Wrapper} qui référence la
|
|
* variable $value (si $valueKey===null) ou $value[$valueKey] si $valueKey
|
|
* n'est pas null
|
|
*/
|
|
static function nw(&$value=null, $valueKey=null, &$schema=null, $definition=null, ?Wrapper &$wrapper=null): Wrapper {
|
|
if ($definition === null) {
|
|
# bien que techniquement, $definition peut être null (il s'agit alors du
|
|
# schéma d'un scalaire quelconque), on ne l'autorise pas ici
|
|
throw SchemaException::invalid_schema("definition is required");
|
|
}
|
|
return self::ns($schema, $definition)->getWrapper($value, $valueKey, null, $wrapper);
|
|
}
|
|
|
|
protected static function have_nature(array $definition, ?string &$nature=null): bool {
|
|
$definitionNature = $definition[""] ?? null;
|
|
if (is_string($definitionNature)) {
|
|
$nature = $definitionNature;
|
|
return true;
|
|
}
|
|
if (is_array($definitionNature)
|
|
&& array_key_exists(0, $definitionNature)
|
|
&& is_string($definitionNature[0])) {
|
|
$nature = $definitionNature;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
protected static function _normalize_definition(&$definition, $definitionKey=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) {
|
|
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];
|
|
}
|
|
}
|
|
}
|
|
# réordonner les clés numériques
|
|
if (cl::have_num_keys($definition)) {
|
|
$keys = array_keys($definition);
|
|
$index = 0;
|
|
foreach ($keys as $key) {
|
|
if (!is_int($key)) continue;
|
|
if ($key !== $index) {
|
|
$definition[$index] = $definition[$key];
|
|
unset($definition[$key]);
|
|
}
|
|
$index++;
|
|
}
|
|
}
|
|
# type
|
|
$types = [];
|
|
$deftype = $definition["type"];
|
|
$nullable = $definition["nullable"] ?? false;
|
|
if ($deftype === null) {
|
|
$types[] = null;
|
|
$nullable = true;
|
|
} else {
|
|
if (!is_array($deftype)) {
|
|
if (!is_string($deftype)) throw SchemaException::invalid_type($deftype);
|
|
$deftype = explode("|", $deftype);
|
|
}
|
|
foreach ($deftype as $type) {
|
|
if ($type !== null) $type = trim($type);
|
|
if ($type === null || $type === "null") {
|
|
$nullable = true;
|
|
continue;
|
|
}
|
|
if (!is_string($type)) throw SchemaException::invalid_type($type);
|
|
if (substr($type, 0, 1) == "?") {
|
|
$type = substr($type, 1);
|
|
$nullable = true;
|
|
}
|
|
if ($type === "") throw SchemaException::invalid_type($type);
|
|
$type = cl::get(ref_types::ALIASES, $type, $type);
|
|
$types = array_merge($types, explode("|", $type));
|
|
}
|
|
if (!$types) throw SchemaException::invalid_schema("scalar: type is required");
|
|
$types = array_keys(array_fill_keys($types, true));
|
|
}
|
|
$definition["type"] = $types;
|
|
$definition["nullable"] = $nullable;
|
|
# nature
|
|
$nature = $definition[""];
|
|
tarray::ensure_array($nature);
|
|
$definition[""] = $nature;
|
|
# name, pkey, header
|
|
$name = $definition["name"];
|
|
$pkey = $definition["pkey"];
|
|
$header = $definition["header"];
|
|
if ($name === null) $name = $definitionKey;
|
|
trawstring::ensure_nstring($name);
|
|
tpkey::ensure_npkey($pkey);
|
|
trawstring::ensure_nstring($header);
|
|
if ($pkey === null) $pkey = $name;
|
|
if ($header === null) $header = $name;
|
|
$definition["name"] = $name;
|
|
$definition["pkey"] = $pkey;
|
|
$definition["header"] = $header;
|
|
# autres éléments
|
|
tarray::ensure_narray($definition["schema"]);
|
|
trawstring::ensure_nstring($definition["title"]);
|
|
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"]);
|
|
tarray::ensure_narray($definition["messages"]);
|
|
tcallable::ensure_ncallable($definition["formatter_func"]);
|
|
tbool::ensure_nbool($definition["computed"]);
|
|
|
|
switch ($nature[0] ?? null) {
|
|
case "assoc":
|
|
foreach ($definition["schema"] as $key => &$keydef) {
|
|
self::_normalize_definition($keydef, $key);
|
|
}; unset($keydef);
|
|
break;
|
|
case "list":
|
|
self::_normalize_definition($definition["schema"]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
protected static function _ensure_nature(array $definition, string $expectedNature, ?string $expectedType=null): void {
|
|
$nature = $definition[""];
|
|
if (!array_key_exists(0, $nature) || $nature[0] !== $expectedNature) {
|
|
throw SchemaException::invalid_schema("$nature: invalid nature. expected $expectedNature");
|
|
}
|
|
if ($expectedType !== null) {
|
|
$types = $definition["type"];
|
|
if (count($types) !== 1 || $types[0] !== $expectedType) {
|
|
throw new SchemaException("{$types[O]}: invalide type. expected $expectedType");
|
|
}
|
|
}
|
|
}
|
|
|
|
protected static function _ensure_type(array &$definition): void {
|
|
$types = $definition["type"];
|
|
$nullable = $definition["nullable"];
|
|
# s'il n'y a qu'une seul type, l'instancier tout de suite
|
|
if (is_array($types) && count($types) == 1 && $types[0] !== null) {
|
|
foreach ($types as $key => $name) {
|
|
if ($key === 0) {
|
|
$args = null;
|
|
} else {
|
|
$args = $name;
|
|
$name = $key;
|
|
}
|
|
$definition["type"] = types::get($nullable, $name, $args, $definition);
|
|
}
|
|
}
|
|
switch ($definition[""][0]) {
|
|
case "assoc":
|
|
foreach ($definition["schema"] as &$keydef) {
|
|
self::_ensure_type($keydef);
|
|
}; unset($keydef);
|
|
break;
|
|
case "list":
|
|
self::_ensure_type($definition["schema"]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
protected static function _ensure_schema_instances(array &$definition): void {
|
|
switch ($definition[""][0]) {
|
|
case "assoc":
|
|
foreach ($definition["schema"] as &$keydef) {
|
|
self::_ensure_schema_instances($keydef);
|
|
Schema::ns($keydef, null, null, false);
|
|
}; unset($keydef);
|
|
break;
|
|
case "list":
|
|
Schema::ns($definition["schema"], null, null, false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @var array définition du schéma, à redéfinir le cas échéant dans une classe
|
|
* dérivée
|
|
*/
|
|
const SCHEMA = null;
|
|
|
|
protected array $_definition;
|
|
|
|
protected array $definition;
|
|
|
|
function getDefinition(): array {
|
|
return $this->_definition;
|
|
}
|
|
|
|
/**
|
|
* retourner la liste des clés valides pour l'accès aux valeurs et résultats
|
|
*/
|
|
abstract function getKeys(): array;
|
|
|
|
abstract function getSchema($key): Schema;
|
|
|
|
/** retourner true si le schéma est de nature tableau associatif */
|
|
function isAssoc(?AssocSchema &$schema=null): bool { return false; }
|
|
/** retourner true si le schéma est de nature liste */
|
|
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 protected function newWrapper(): Wrapper;
|
|
|
|
abstract function getWrapper(&$value=null, $valueKey=null, ?array $params=null, ?Wrapper &$wrapper=null): Wrapper;
|
|
|
|
#############################################################################
|
|
# key & properties
|
|
|
|
function offsetExists($offset): bool {
|
|
return array_key_exists($offset, $this->definition);
|
|
}
|
|
function offsetGet($offset) {
|
|
if (!array_key_exists($offset, $this->definition)) return null;
|
|
else return $this->definition[$offset];
|
|
}
|
|
function offsetSet($offset, $value): void {
|
|
throw AccessException::read_only(null, $offset);
|
|
}
|
|
function offsetUnset($offset): void {
|
|
throw AccessException::read_only(null, $offset);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|