281 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			281 lines
		
	
	
		
			9.6 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;
 | |
| 
 | |
| 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, $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, $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($keydef, $key);
 | |
|       }; unset($keydef);
 | |
|       break;
 | |
|     case "list":
 | |
|       self::_normalize($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 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 getWrapper(&$value=null, $valueKey=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 = [];
 | |
|   function __get($name) {
 | |
|     $pkey = cl::get(static::_PROPERTY_PKEYS, $name, $name);
 | |
|     return cl::pget($this->definition, $pkey);
 | |
|   }
 | |
| }
 |