1121 lines
		
	
	
		
			38 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			1121 lines
		
	
	
		
			38 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| namespace nur\m\pdo\mysql;
 | |
| 
 | |
| use Exception;
 | |
| use nur\A;
 | |
| use nur\b\ValueException;
 | |
| use nur\base;
 | |
| use nur\func;
 | |
| use nur\m\base\AbstractRow;
 | |
| use nur\md;
 | |
| use nur\php\SrcGenerator;
 | |
| use nur\str;
 | |
| use ReflectionClass;
 | |
| 
 | |
| /**
 | |
|  * Class MysqlMigrations
 | |
|  *
 | |
|  * Dans la classe destination (celle à partir de laquelle sont générées les
 | |
|  * migrations), les constantes suivantes peuvent être définies:
 | |
|  * - DBTRAIT: classe qui est utilisée comme dbtrait pour *toutes* les migrations
 | |
|  * - DEFAULT_DBTRAIT: classe qui est utilisée comme dbtrait pour les migrations
 | |
|  *   qui n'en définissent pas
 | |
|  * - MERGE: une classe XxxMigration qu'il faut aussi considérer
 | |
|  * - MERGES: une liste de classes XxxMigration qu'il faut aussi considérer.
 | |
|  *
 | |
|  * Les constantes MERGE et MERGES sont calculées récursivement. Les constantes
 | |
|  * DBTRAIT et DEFAULT_DBTRAIT ne sont consultées que dans la classe destination.
 | |
|  */
 | |
| class MysqlMigrations {
 | |
|   const UPDATE_DIRECTIVES = [
 | |
|     "+name", # nom de base du fichier sql généré
 | |
|     "+desc", # description à insérer dans le fichier sql
 | |
|     "+dbtrait", # trait qui fourni verifix_conn(). Si cette valeur n'est pas fournie, la constante DBTRAIT de la classe destination donne la valeur par défaut
 | |
|     "+prod", # cette configuration a-t-elle été déployée en prod? (ne plus modifier)
 | |
|   ];
 | |
|   const TABLE_DIRECTIVES = [
 | |
|     "+name", # nom effectif de la table
 | |
|     "+desc", # description à insérer en commentaire
 | |
|     "+type", # type: table (par défaut) ou view
 | |
|     # directives spécifiques aux tables
 | |
|     "+suffix", # suffixe des colonnes de cette table
 | |
|     "+primary key",
 | |
|     "+foreign key",
 | |
|     "+index",
 | |
|     "+unique index",
 | |
|     # directives spécifiques aux vues
 | |
|     "+view_tables", # tables participant aux vues
 | |
|     "+view_def", # définition de la vue
 | |
|   ];
 | |
| 
 | |
|   ###
 | |
| 
 | |
|   private static $trace;
 | |
|   private static function trace($msg) {
 | |
|     if (self::$trace !== null) func::call(self::$trace, $msg);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * tester si le fichier doit être écrasé
 | |
|    *
 | |
|    * si $overwrite n'est pas booléen, ce doit être un nombre maximum de ligne au
 | |
|    * delà duquel le fichier n'est pas écrasé
 | |
|    */
 | |
|   private static function should_overwrite($file, $overwrite) {
 | |
|     if (!is_int($overwrite)) $overwrite = boolval($overwrite);
 | |
|     if ($overwrite === true || $overwrite === false) return $overwrite;
 | |
|     if (!is_file($file)) return true;
 | |
|     $inf = fopen($file, "rb");
 | |
|     try {
 | |
|       while ($overwrite > 0) {
 | |
|         $line = fgets($inf);
 | |
|         if ($line === false) return true;
 | |
|         $overwrite--;
 | |
|       }
 | |
|       $line = fgets($inf);
 | |
|       return $line === false || $line === "";
 | |
|     } finally {
 | |
|       fclose($inf);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   private static function get_ovsuf($file, $overwrite) {
 | |
|     if (is_file($file)) {
 | |
|       if ($overwrite) return " (OVERWRITE)";
 | |
|       else return "";
 | |
|     } else return " (NEW)";
 | |
|   }
 | |
| 
 | |
|   private static function write($line, $w, $tmpf) {
 | |
|     $r = fwrite($w, $line);
 | |
|     if ($r === false) {
 | |
|       fclose($w);
 | |
|       throw new Exception("$tmpf: erreur lors de l'écriture du fichier");
 | |
|     }
 | |
|   }
 | |
|   private static function write_lines($lines, $outf, $overwrite=true, $final_nl=true) {
 | |
|     if (is_file($outf) && !$overwrite) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     $dir = dirname($outf);
 | |
|     if (!is_dir($dir)) {
 | |
|       $r = mkdir($dir, 0777, true);
 | |
|       if ($r === false) {
 | |
|         throw new Exception("$dir: erreur lors de la création du répertoire");
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     $tmpf = "$outf.tmp";
 | |
|     $w = fopen($tmpf, "w+b");
 | |
|     if ($w === false) {
 | |
|       throw new Exception("$tmpf: erreur d'ouverture du fichier");
 | |
|     }
 | |
|     $first = true;
 | |
|     foreach ($lines as $line) {
 | |
|       if ($first) $first = false;
 | |
|       else self::write("\n", $w, $tmpf);
 | |
|       self::write($line, $w, $tmpf);
 | |
|     }
 | |
|     if ($final_nl) self::write("\n", $w, $tmpf);
 | |
|     $r = fclose($w);
 | |
|     if ($r === false) {
 | |
|       throw new Exception("$tmpf: erreur lors de la fermeture du fichier");
 | |
|     }
 | |
|     $r = rename($tmpf, $outf);
 | |
|     if ($r === false) {
 | |
|       throw new Exception("$outf: erreur lors du renommage du fichier");
 | |
|     }
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   ###
 | |
| 
 | |
|   private static function is_update(string $method) {
 | |
|     return str::starts_with("update", $method);
 | |
|   }
 | |
|   private static function get_index(string $method) {
 | |
|     return str::without_prefix("update", $method);
 | |
|   }
 | |
|   private static function update_methods(array &$methods, string $class): void {
 | |
|     $all_methods = get_class_methods($class);
 | |
|     if ($all_methods === null) {
 | |
|       throw new Exception("$class: classe introuvable");
 | |
|     }
 | |
|     $prefix = str_replace("\\", "_", $class);
 | |
|     foreach ($all_methods as $method) {
 | |
|       if (!self::is_update($method)) continue;
 | |
|       $index = self::get_index($method);
 | |
|       $name = "${prefix}_$index";
 | |
|       $methods[$name] = [
 | |
|         "class" => $class,
 | |
|         "name" => $method,
 | |
|         "index" => $index,
 | |
|         "method" => [$class, $method],
 | |
|         "prefix" => $prefix,
 | |
|       ];
 | |
|     }
 | |
|     $merge_classes = [];
 | |
|     $c = new ReflectionClass($class);
 | |
|     $merges = $c->getConstant("MERGES");
 | |
|     if (base::nz($merges)) A::merge($merge_classes, $merges);
 | |
|     $merge = $c->getConstant("MERGE");
 | |
|     if (base::nz($merge)) $merge_classes[] = $merge;
 | |
|     foreach ($merge_classes as $class) {
 | |
|       self::update_methods($methods, $class);
 | |
|     }
 | |
|   }
 | |
|   private static function get_methods($class) {
 | |
|     $methods = [];
 | |
|     self::update_methods($methods, $class);
 | |
|     ksort($methods, SORT_NATURAL);
 | |
|     return $methods;
 | |
|   }
 | |
| 
 | |
|   ###
 | |
| 
 | |
|   private static function parse_col($def): array {
 | |
|     if (preg_match('/^([a-zA-Z0-9_]+)(?:\((\d+)\s*(?:,\s*(\d+))?\))?\s*(.*)\s*(?:--\s*(.*)\s*)?(?:--\s*(.*)\s*)?$/', $def, $ms)) {
 | |
|       $size = base::vn(A::get($ms, 2));
 | |
|       if ($size !== null) $size = intval($size);
 | |
|       $precision = base::vn(A::get($ms, 3));
 | |
|       if ($precision !== null) $precision = intval($precision);
 | |
|       return [
 | |
|         "type" => A::get($ms, 1),
 | |
|         "size" => $size,
 | |
|         "precision" => $precision,
 | |
|         "options" => base::vn(A::get($ms, 4)),
 | |
|         "title" => base::vn(A::get($ms, 5)),
 | |
|         "desc" => base::vn(A::get($ms, 6)),
 | |
|       ];
 | |
|     } else {
 | |
|       throw new ValueException("erreur de syntaxe: $def");
 | |
|     }
 | |
|   }
 | |
|   private static function is_multi_fkdef($def, &$ms) {
 | |
|     return preg_match('/^\((.+)\)$/', $def, $ms);
 | |
|   }
 | |
|   private static function is_single_fkref($ref, &$ms) {
 | |
|     return preg_match('/^([a-zA-Z0-9_]+)\(([a-zA-Z0-9_]+)\)$/', $ref, $ms);
 | |
|   }
 | |
| 
 | |
|   private static $tables = [];
 | |
| 
 | |
|   private static function has_table($table) {
 | |
|     return A::get(self::$tables, $table, false) !== false;
 | |
|   }
 | |
|   private static function has_col($table, $col) {
 | |
|     return (self::has_table($table) &&
 | |
|       A::get(self::$tables[$table], $col, false) !== false);
 | |
|   }
 | |
|   private static function add_table($table, $suffix=null, $dbtrait=null, $name=null): ?array {
 | |
|     if (self::has_table($table)) return null;
 | |
|     if ($name === null) $name = $table;
 | |
|     return self::$tables[$table] = [
 | |
|       "name" => $name,
 | |
|       "suffix" => $suffix,
 | |
|       "cols" => [],
 | |
|       "pk" => null,
 | |
|       "dbtrait" => $dbtrait,
 | |
|     ];
 | |
|   }
 | |
|   private static function set_table_pk($table, $pk) {
 | |
|     self::add_table($table);
 | |
|     self::$tables[$table]["pk"] = A::with($pk);
 | |
|   }
 | |
|   private static function add_col($table, $col, $def=null): array {
 | |
|     self::add_table($table);
 | |
|     if ($def !== null) {
 | |
|       [
 | |
|         "type" => $type, "size" => $size, "precision" => $precision,
 | |
|         "options" => $options,
 | |
|         "title" => $title, "desc" => $desc,
 | |
|       ] = self::parse_col($def);
 | |
|       #XXX calculer le type du schema: bool si integer(1), integer si integer, etc.
 | |
| 
 | |
|       $pk_col = boolval(preg_match('/\bprimary\s+key\b/i', $options));
 | |
|       $auto_col = boolval(preg_match('/\bauto_increment\b/i', $options));
 | |
|       if (str::starts_with("datetime", $type)) {
 | |
|         $hot_type = "date"; #XXX ajouter le support datetime
 | |
|       } elseif (str::starts_with("date", $type)) {
 | |
|         $hot_type = "date";
 | |
|       } elseif (str::starts_with("int", $type)) {
 | |
|         $hot_type = "numeric";
 | |
|       } else {
 | |
|         $hot_type = "text";
 | |
|       }
 | |
| 
 | |
|       $cinfos = [
 | |
|         "name" => $col,
 | |
|         "def" => $def,
 | |
|         "pk" => $pk_col,
 | |
|         "auto" => $auto_col,
 | |
|         "hot_type" => $hot_type,
 | |
|         #
 | |
|         "schema" => [
 | |
|           "name" => $col,
 | |
|           "title" => $title,
 | |
|           "desc" => $desc,
 | |
|           "mysql" => [
 | |
|             "type" => $type,
 | |
|             "size" => $size,
 | |
|             "precision" => $precision,
 | |
|             "options" => $options,
 | |
|             "pk" => $pk_col,
 | |
|             "auto" => $auto_col,
 | |
|           ],
 | |
|           "hot" => [
 | |
|             "type" => $hot_type,
 | |
|           ],
 | |
|         ],
 | |
|       ];
 | |
|     } else {
 | |
|       $cinfos = [
 | |
|         "name" => $col,
 | |
|       ];
 | |
|     }
 | |
|     return self::$tables[$table]["cols"][$col] = $cinfos;
 | |
|   }
 | |
|   private static function del_table($table) {
 | |
|     unset(self::$tables[$table]);
 | |
|   }
 | |
|   private static function del_col($table, $col) {
 | |
|     if (!self::has_table($table)) return;
 | |
|     unset(self::$tables[$table]["cols"][$col]);
 | |
|   }
 | |
| 
 | |
|   private static $views = [];
 | |
| 
 | |
|   private static function has_view($view) {
 | |
|     return A::get(self::$views, $view, false) !== false;
 | |
|   }
 | |
|   private static function add_view($view, $tables, $dbtrait=null, $name=null) {
 | |
|     if (self::has_view($view)) return;
 | |
|     if ($name === null) $name = $view;
 | |
|     self::$views[$view] = [
 | |
|       "name" => $name,
 | |
|       "tables" => $tables,
 | |
|       "dbtrait" => $dbtrait,
 | |
|     ];
 | |
|   }
 | |
|   private static function del_view($view) {
 | |
|     unset(self::$views[$view]);
 | |
|   }
 | |
| 
 | |
|   private static $t1links = [];
 | |
|   private static $tmlinks = [];
 | |
| 
 | |
|   private static function add_t1link($scol, $stable, $dcol, $dtable) {
 | |
|     self::$t1links[] = [$scol, $stable, $dcol, $dtable];
 | |
|   }
 | |
| 
 | |
|   private static function reset($options=null, $tables_only=false) {
 | |
|     self::$tables = [];
 | |
|     self::$views = [];
 | |
|     self::$t1links = [];
 | |
|     self::$tmlinks = [];
 | |
|     if (!$tables_only) {
 | |
|       self::$trace = A::get($options, "trace");
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   ###
 | |
| 
 | |
|   static function gensql($class, $options=null) {
 | |
|     self::reset($options);
 | |
|     $destdir = A::get($options, "destdir", ".");
 | |
|     $overwrite = A::get($options, "overwrite");
 | |
|     $verbose = A::get($options, "verbose");
 | |
|     $prefix = A::get($options, "prefix");
 | |
| 
 | |
|     self::trace("# gensql");
 | |
|     self::trace("  class: $class");
 | |
|     self::trace("  destdir: $destdir");
 | |
| 
 | |
|     $methods = self::get_methods($class);
 | |
|     foreach ($methods as $minfos) {
 | |
|       self::reset(null, true);
 | |
|       $update = func::call($minfos["method"]);
 | |
| 
 | |
|       # par défaut, ne pas écraser les fichiers qui sont en +prod
 | |
|       $prod = A::get($update, "+prod");
 | |
|       $overwrite_sql = $overwrite === null? !$prod: $overwrite;
 | |
| 
 | |
|       self::trace("  ## $minfos[class]::$minfos[name]");
 | |
| 
 | |
|       $lines = [
 | |
|         "-- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8",
 | |
|         "-- fichier autogénéré: toute modification locale risque d'être perdue",
 | |
|       ];
 | |
|       $desc = A::get($update, "+desc");
 | |
|       if ($desc) $lines[] = "-- $desc";
 | |
| 
 | |
|       foreach ($update as $uname => $uinfos) {
 | |
|         # ignorer les directives de mise à jour: elles ont été traitées ci-dessus
 | |
|         if (str::starts_with("+", $uname)) {
 | |
|           if (!in_array($uname, self::UPDATE_DIRECTIVES)) {
 | |
|             trigger_error("$uname: unknown update directive", E_USER_WARNING);
 | |
|           }
 | |
|           continue;
 | |
|         }
 | |
| 
 | |
|         $type = A::get($uinfos, "+type", "table");
 | |
|         if ($type === "table") {
 | |
|           # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | |
|           $table = $uname;
 | |
|           $table_name = A::get($uinfos, "+name", $table);
 | |
|           $cols = $uinfos;
 | |
| 
 | |
|           #####################################################################
 | |
|           # suppression de table
 | |
|           if ($cols === null) {
 | |
|             $lines[] = "drop table $table_name;";
 | |
|             self::del_table($table);
 | |
|             continue;
 | |
|           }
 | |
| 
 | |
|           # liste de champs "normale"
 | |
|           if (!self::has_table($table)) {
 | |
|             ###################################################################
 | |
|             # nouvelle table
 | |
|             $lines[] = "";
 | |
|             $desc = A::get($cols, "+desc");
 | |
|             if ($desc) $lines[] = "-- $desc";
 | |
|             $lines[] = "create table $table_name (";
 | |
|             $suffix = A::get($cols, "+suffix");
 | |
|             self::add_table($table, $suffix, null, $table_name);
 | |
|             $first = true;
 | |
|             foreach ($cols as $col => $def) {
 | |
|               # les directives seront traitées plus tard
 | |
|               if (str::starts_with("+", $col)) continue;
 | |
|               $lines[] = ($first? " ": ",")."$col $def";
 | |
|               $cinfos = self::add_col($table, $col, $def);
 | |
|               if ($cinfos["pk"]) self::set_table_pk($table, $col);
 | |
|               $first = false;
 | |
|             }
 | |
|             foreach ($cols as $col => $value) {
 | |
|               if (!str::starts_with("+", $col)) continue;
 | |
|               switch ($col) {
 | |
|               case "+primary key":
 | |
|                 $pk = A::with($value);
 | |
|                 self::set_table_pk($table, $pk);
 | |
|                 $pk = implode(", ", $pk);
 | |
|                 $lines[] = ",primary key ($pk)";
 | |
|                 break;
 | |
|               case "+foreign key":
 | |
|                 foreach (A::with($value) as $fk => $ref) {
 | |
|                   $ms = null;
 | |
|                   if (self::is_multi_fkdef($fk, $ms)) $fk = $ms[1];
 | |
|                   $lines[] = ",foreign key ($fk) references $ref";
 | |
|                   $ms = null;
 | |
|                   if (self::is_single_fkref($ref, $ms)) {
 | |
|                     self::add_t1link($fk, $table, $ms[2], $ms[1]);
 | |
|                   }
 | |
|                 }
 | |
|                 break;
 | |
|               case "+index":
 | |
|                 foreach (A::with($value) as $name => $cols) {
 | |
|                   if (is_numeric($name)) $name = false;
 | |
|                   $cols = implode(", ", A::with($cols));
 | |
|                   $lines[] = ",index $name($cols)";
 | |
|                 }
 | |
|                 break;
 | |
|               case "+unique index":
 | |
|                 foreach (A::with($value) as $name => $cols) {
 | |
|                   if (is_numeric($name)) $name = false;
 | |
|                   $cols = implode(", ", A::with($cols));
 | |
|                   $lines[] = ",unique index $name($cols)";
 | |
|                 }
 | |
|                 break;
 | |
|               default:
 | |
|                 if (!in_array($col, self::TABLE_DIRECTIVES)) {
 | |
|                   trigger_error("$table: $col: unknown table directive", E_USER_WARNING);
 | |
|                 }
 | |
|                 break;
 | |
|               }
 | |
|             }
 | |
|             $lines[] = ");";
 | |
|           } else {
 | |
|             ###################################################################
 | |
|             # maj d'une table existante
 | |
|             $lines[] = "";
 | |
|             $desc = A::get($cols, "+desc");
 | |
|             if ($desc) $lines[] = "-- $desc";
 | |
|             foreach ($cols as $col => $def) {
 | |
|               # les directives seront traitées plus tard
 | |
|               if (!str::starts_with("+", $col)) {
 | |
|                 if ($def === null) {
 | |
|                   ## suppression d'un champ
 | |
|                   if (!self::has_col($table, $col)) {
 | |
|                     trigger_error("$table.$col: deleting a non-existent column", E_USER_WARNING);
 | |
|                   }
 | |
|                   $lines[] = "alter table table_name drop column $col;";
 | |
|                   self::del_col($table, $col);
 | |
|                 } else {
 | |
|                   if (self::has_col($table, $col)) {
 | |
|                     ## maj d'un champ
 | |
|                     $lines[] = "alter table table_name change column $col $col $def;";
 | |
|                     # NB: pour simplifier, ne pas tenir compte du fait que ce
 | |
|                     # nouveau champ pourrait maintenant être une clé primaire
 | |
|                   } else {
 | |
|                     ## ajout d'un champ
 | |
|                     $lines[] = "alter table table_name add column $col $def;";
 | |
|                     self::add_col($table, $col, $def);
 | |
|                     # NB: pour simplifier, ne pas tenir compte du fait que ce
 | |
|                     # nouveau champ pourrait être une clé primaire
 | |
|                   }
 | |
|                 }
 | |
|               } else {
 | |
|                 ## maj directive
 | |
|                 trigger_error("$table: $col: table directive update is not (yet) supported", E_USER_WARNING);
 | |
|               }
 | |
|             }
 | |
|           }
 | |
| 
 | |
|         } elseif ($type === "view") {
 | |
|           # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | |
|           $view = $uname;
 | |
|           $view_name = A::get($uinfos, "+name", $view);
 | |
|           $vinfos = $uinfos;
 | |
| 
 | |
|           #####################################################################
 | |
|           # suppression de vue
 | |
|           if ($vinfos === null) {
 | |
|             $lines[] = "drop view $view_name;";
 | |
|             self::del_view($view);
 | |
|             continue;
 | |
|           }
 | |
| 
 | |
|           # définition "normale"
 | |
|           if (!self::has_view($view)) {
 | |
|             ###################################################################
 | |
|             # nouvelle vue
 | |
|             $tables = A::get($vinfos, "+view_tables");
 | |
|             $def = A::get($vinfos, "+view_def");
 | |
|             if ($def === null) trigger_error("$view: missing +view_def", E_USER_WARNING);
 | |
|             self::add_view($view, $tables, null, $view_name);
 | |
| 
 | |
|             $lines[] = "";
 | |
|             $desc = A::get($vinfos, "+desc");
 | |
|             if ($desc) $lines[] = "-- $desc";
 | |
|             $lines[] = "create view $view_name as";
 | |
|             $lines[] = $def;
 | |
|             $lines[] = ";";
 | |
|           } else {
 | |
|             ###################################################################
 | |
|             # maj d'une vue existante
 | |
|             trigger_error("$view: view update is not (yet) supported", E_USER_WARNING);
 | |
|           }
 | |
| 
 | |
|         } else {
 | |
|           trigger_error("$type: unknown type, expected table or view", E_USER_WARNING);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       if (self::$tables || self::$views) {
 | |
|         $name = A::get($update, "+name", "update");
 | |
|         $index = $minfos["index"];
 | |
|         $outfname = "$index-$name.sql";
 | |
|         if ($prefix) $outfname = "$minfos[prefix]--$outfname";
 | |
|         $outf = "$destdir/$outfname";
 | |
|         $ovsuf = self::get_ovsuf($outf, $overwrite_sql);
 | |
|         if ($ovsuf || $verbose) {
 | |
|           self::trace("    sql output: $outfname$ovsuf");
 | |
|           self::write_lines($lines, "$outf", $overwrite_sql);
 | |
|         }
 | |
| 
 | |
|         # les fichiers csv ne sont écrasés que s'ils n'ont pas de données
 | |
|         # automatiquement
 | |
|         $overwrite_csv_threshold = $overwrite_sql? 1: false;
 | |
|         foreach (self::$tables as $table => $tinfos) {
 | |
|           $table_name = $tinfos["name"];
 | |
|           $cols = array_keys($tinfos["cols"]);
 | |
|           $lines = [implode(",", $cols)];
 | |
|           $index++;
 | |
|           $outfname = "$index-$table_name.csv";
 | |
|           if ($prefix) $outfname = "$minfos[prefix]--$outfname";
 | |
|           $outf = "$destdir/$outfname";
 | |
|           $overwrite_csv = self::should_overwrite($outf, $overwrite_csv_threshold);
 | |
|           $ovsuf = self::get_ovsuf($outf, $overwrite_csv);
 | |
|           if ($ovsuf || $verbose) {
 | |
|             self::trace("    csv output: $outfname$ovsuf");
 | |
|             self::write_lines($lines, "$outf", $overwrite_csv);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static function gendoc($class, $options=null) {
 | |
|     self::reset($options);
 | |
|     $destdir = A::get($options, "destdir", ".");
 | |
|     $overwrite = A::get($options, "overwrite");
 | |
|     $verbose = A::get($options, "verbose");
 | |
| 
 | |
|     # par défaut, écraser les fichiers lors de la génération de la doc
 | |
|     if ($overwrite === null) $overwrite = true;
 | |
| 
 | |
|     self::trace("# gendoc");
 | |
|     self::trace("  class: $class");
 | |
|     self::trace("  destdir: $destdir");
 | |
| 
 | |
|     $methods = self::get_methods($class);
 | |
|     foreach ($methods as $minfos) {
 | |
|       $update = func::call($minfos["method"]);
 | |
| 
 | |
|       foreach ($update as $uname => $uinfos) {
 | |
|         # ignorer les directives de mise à jour: elles ont été traitées ci-dessus
 | |
|         if (str::starts_with("+", $uname)) {
 | |
|           if (!in_array($uname, self::UPDATE_DIRECTIVES)) {
 | |
|             trigger_error("$uname: unknown update directive", E_USER_WARNING);
 | |
|           }
 | |
|           continue;
 | |
|         }
 | |
| 
 | |
|         $type = A::get($uinfos, "+type", "table");
 | |
|         if ($type === "table") {
 | |
|           # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | |
|           $table = $uname;
 | |
|           $cols = $uinfos;
 | |
| 
 | |
|           #####################################################################
 | |
|           # suppression de table
 | |
|           if ($cols === null) {
 | |
|             self::del_table($table);
 | |
|             continue;
 | |
|           }
 | |
| 
 | |
|           # liste de champs "normale"
 | |
|           if (!self::has_table($table)) {
 | |
|             ###################################################################
 | |
|             # nouvelle table
 | |
|             self::add_table($table);
 | |
|             foreach ($cols as $col => $value) {
 | |
|               if (!str::starts_with("+", $col)) {
 | |
|                 self::add_col($table, $col);
 | |
|               } else {
 | |
|                 switch ($col) {
 | |
|                 case "+foreign key":
 | |
|                   foreach (A::with($value) as $fk => $ref) {
 | |
|                     $ms = null;
 | |
|                     if (self::is_multi_fkdef($fk, $ms)) $fk = $ms[1];
 | |
|                     if (self::is_single_fkref($ref, $ms)) {
 | |
|                       self::add_t1link($fk, $table, $ms[2], $ms[1]);
 | |
|                     }
 | |
|                   }
 | |
|                   break;
 | |
|                 default:
 | |
|                   if (!in_array($col, self::TABLE_DIRECTIVES)) {
 | |
|                     trigger_error("$table: $col: unknown table directive", E_USER_WARNING);
 | |
|                   }
 | |
|                   break;
 | |
|                 }
 | |
|               }
 | |
|             }
 | |
|           } else {
 | |
|             ###################################################################
 | |
|             # maj d'une table existante
 | |
|             foreach ($cols as $col => $def) {
 | |
|               # les directives seront traitées plus tard
 | |
|               if (!str::starts_with("+", $col)) {
 | |
|                 if ($def === null) {
 | |
|                   ## suppression d'un champ
 | |
|                   if (!self::has_col($table, $col)) {
 | |
|                     trigger_error("$table.$col: deleting a non-existent column", E_USER_WARNING);
 | |
|                   }
 | |
|                   self::del_col($table, $col);
 | |
|                 } else {
 | |
|                   if (!self::has_col($table, $col)) {
 | |
|                     self::add_col($table, $col);
 | |
|                   }
 | |
|                 }
 | |
|               } else {
 | |
|                 ## maj directive
 | |
|                 trigger_error("$table: $col: table directive update is not (yet) supported", E_USER_WARNING);
 | |
|               }
 | |
|             }
 | |
|           }
 | |
| 
 | |
|         } elseif ($type === "view") {
 | |
|           # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | |
|           $view = $uname;
 | |
|           $vinfos = $uinfos;
 | |
| 
 | |
|           #####################################################################
 | |
|           # suppression de vue
 | |
|           if ($vinfos === null) {
 | |
|             self::del_view($view);
 | |
|             continue;
 | |
|           }
 | |
| 
 | |
|           # définition "normale"
 | |
|           if (!self::has_view($view)) {
 | |
|             ###################################################################
 | |
|             # nouvelle vue
 | |
|             $tables = A::get($vinfos, "+view_tables", null);
 | |
|             self::add_view($view, $tables);
 | |
| 
 | |
|           } else {
 | |
|             ###################################################################
 | |
|             # maj d'une vue existante
 | |
|             trigger_error("$view: view update is not (yet) supported", E_USER_WARNING);
 | |
|           }
 | |
| 
 | |
|         } else {
 | |
|           trigger_error("$type: unknown type, expected table or view", E_USER_WARNING);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     $lines = [
 | |
|       "# -*- coding: utf-8 mode: plantuml -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8",
 | |
|       "# fichier autogénéré: toute modification locale risque d'être perdue",
 | |
|       "",
 | |
|       "##@startuml",
 | |
|       "hide empty members",
 | |
|       "hide empty methods",
 | |
|       "hide circle",
 | |
|       "!ifndef SHOW_DETAILS",
 | |
|       "hide members",
 | |
|       "!endif",
 | |
|     ];
 | |
|     foreach (self::$tables as $table => $tinfos) {
 | |
|       $lines[] = "";
 | |
|       $lines[] = "class $table {";
 | |
|       $cols = $tinfos["cols"];
 | |
|       foreach (array_keys($cols) as $col) {
 | |
|         $lines[] = "  $col";
 | |
|       }
 | |
|       $lines[] = "}";
 | |
|     }
 | |
|     foreach (self::$views as $view => $vinfos) {
 | |
|       $lines[] = "";
 | |
|       $lines[] = "class $view<view> {";
 | |
|       $tables = $vinfos["tables"];
 | |
|       foreach ($tables as $table) {
 | |
|         $cols = self::$tables[$table]["cols"];
 | |
|         foreach (array_keys($cols) as $col) {
 | |
|           $lines[] = "  $col";
 | |
|         }
 | |
|       }
 | |
|       $lines[] = "}";
 | |
|     }
 | |
|     $lines[] = "";
 | |
|     $lines[] = "##@enduml";
 | |
| 
 | |
|     $outfname = "classes.iuml";
 | |
|     $outf = "$destdir/$outfname";
 | |
|     $ovsuf = self::get_ovsuf($outf, $overwrite);
 | |
|     if ($ovsuf || $verbose) {
 | |
|       self::trace("  puml output: $outfname$ovsuf");
 | |
|       self::write_lines($lines, $outf, $overwrite, true);
 | |
|     }
 | |
| 
 | |
|     $lines = [];
 | |
|     foreach (self::$tables as $table => $tinfos) {
 | |
|       $lines[] = "'hide $table";
 | |
|     }
 | |
|     $first = true;
 | |
|     foreach (self::$t1links as $link) {
 | |
|       if ($first) $lines[] = "";
 | |
|       list($scol, $stable, $dcol, $dtable) = $link;
 | |
|       $lines[] = "$stable \"*\" -- \"1\" $dtable";
 | |
|       $first = false;
 | |
|     }
 | |
|     A::merge($lines, [
 | |
|       "~~~",
 | |
|       "",
 | |
|       "-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary",
 | |
|     ]);
 | |
| 
 | |
|     $slines = [
 | |
|       "",
 | |
|       "~~~puml",
 | |
|       "# output=model-simple.png",
 | |
|       "!include classes.iuml",
 | |
|     ];
 | |
|     A::merge($slines, $lines);
 | |
|     $outfname = "model-simple.md";
 | |
|     $outf = "$destdir/$outfname";
 | |
|     $ovsuf = self::get_ovsuf($outf, $overwrite);
 | |
|     if ($ovsuf || $verbose) {
 | |
|       self::trace("  md output: $outfname$ovsuf");
 | |
|       self::write_lines($slines, $outf, $overwrite, false);
 | |
|     }
 | |
| 
 | |
|     $dlines = [
 | |
|       "",
 | |
|       "~~~puml",
 | |
|       "# output=model-details.png",
 | |
|       "!define SHOW_DETAILS",
 | |
|       "!include classes.iuml",
 | |
|     ];
 | |
|     A::merge($dlines, $lines);
 | |
|     $outfname = "model-details.md";
 | |
|     $outf = "$destdir/$outfname";
 | |
|     $ovsuf = self::get_ovsuf($outf, $overwrite);
 | |
|     if ($ovsuf || $verbose) {
 | |
|       self::trace("  md output: $outfname$ovsuf");
 | |
|       self::write_lines($dlines, $outf, $overwrite, false);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   private static function add_vtables(&$lines, $cols) {
 | |
|     $lines[] = "";
 | |
|     $lines[] = "  const VTABLES_HEADERS = self::COLUMNS;";
 | |
|     foreach ($cols as $col => $cinfos) {
 | |
|       $vcol = strtoupper($col)."_VCOL";
 | |
|       $lines[] = "  const $vcol = [";
 | |
|       $lines[] = "    \"data\" => \"$col\",";
 | |
|       $lines[] = "    \"type\" => \"$cinfos[hot_type]\",";
 | |
|       if ($cinfos["hot_type"] == "date") {
 | |
|         $lines[] = "    \"dateFormat\" => \"DD/MM/YYYY\",";
 | |
|         $lines[] = "    \"correctFormat\" => true,";
 | |
|       }
 | |
|       $lines[] = "  ];";
 | |
|     }
 | |
|     $lines[] = "  const VTABLES_SCHEMA = [";
 | |
|     foreach (array_keys($cols) as $col) {
 | |
|       $vcol = strtoupper($col)."_VCOL";
 | |
|       $lines[] = "    self::$vcol,";
 | |
|     }
 | |
|     $lines[] = "  ];";
 | |
|   }
 | |
|   private static function add_data_schema(&$lines, $cols) {
 | |
|     $cols = array_keys($cols);
 | |
|     $lines[] = "";
 | |
|     foreach ($cols as $col) {
 | |
|       $dcol = strtoupper($col)."_DCOL";
 | |
|       $lines[] = "  const $dcol = [";
 | |
|       $lines[] = "    \"name\" => \"$col\",";
 | |
|       $lines[] = "    \"key\" => \"$col\",";
 | |
|       $lines[] = "  ];";
 | |
|     }
 | |
|     $lines[] = "  const DATA_SCHEMA = [";
 | |
|     foreach ($cols as $col) {
 | |
|       $dcol = strtoupper($col)."_DCOL";
 | |
|       $lines[] = "    self::$dcol,";
 | |
|     }
 | |
|     $lines[] = "  ];";
 | |
|     $lines[] = "  const CSV_SCHEMA = self::DATA_SCHEMA;";
 | |
|     $lines[] = "  const HTML_SCHEMA = self::DATA_SCHEMA;";
 | |
|   }
 | |
|   private static function add_schema(array &$lines, array $cols, string $indent): void {
 | |
|     $schema = [];
 | |
|     foreach ($cols as $col) {
 | |
|       $schema[$col["name"]] = $col["schema"];
 | |
|     }
 | |
|     md::normalize_mschema($schema);
 | |
|     $generator = new SrcGenerator($indent);
 | |
|     $generator->genConst("SCHEMA", $schema);
 | |
|     $generator->mergeInto($lines);
 | |
|   }
 | |
| 
 | |
|   static function genclass($class, $options=null) {
 | |
|     $c = new ReflectionClass($class);
 | |
|     $force_dbtrait = $c->getConstant("DBTRAIT");
 | |
|     $default_dbtrait = $c->getConstant("DEFAULT_DBTRAIT");
 | |
| 
 | |
|     self::reset($options);
 | |
|     $destdir = A::get($options, "destdir", ".");
 | |
|     $package = A::get($options, "package");
 | |
|     $baserow = A::get($options, "baserow");
 | |
|     $overwrite = A::get($options, "overwrite");
 | |
|     $verbose = A::get($options, "verbose");
 | |
|     if ($baserow === null) $baserow = AbstractRow::class;
 | |
| 
 | |
|     $pos = strrpos($class, "\\");
 | |
|     $default_package = $pos !== false? substr($class, 0, $pos): false;
 | |
|     if ($package === null) $package = $default_package;
 | |
|     elseif (substr($package, 0, 1) != "\\") $package = "$default_package\\$package";
 | |
|     else $package = substr($package, 1);
 | |
| 
 | |
|     # par défaut ne pas écraser les fichiers lors de la génération des classes
 | |
|     if ($overwrite === null) $overwrite = false;
 | |
|     # ne pas écraser les classes dont la définition est en +prod
 | |
|     $no_overwrites = [];
 | |
| 
 | |
|     self::trace("# genclass");
 | |
|     self::trace("  class: $class");
 | |
|     self::trace("  destdir: $destdir");
 | |
| 
 | |
|     $methods = self::get_methods($class);
 | |
|     foreach ($methods as $minfos) {
 | |
|       $update = func::call($minfos["method"]);
 | |
|       $dbtrait = $force_dbtrait;
 | |
|       if (!$dbtrait) $dbtrait = A::get($update, "+dbtrait");
 | |
|       if (!$dbtrait) $dbtrait = $default_dbtrait;
 | |
|       $prod = A::get($update, "+prod");
 | |
| 
 | |
|       foreach ($update as $uname => $uinfos) {
 | |
|         # ignorer les directives de mise à jour: elles ont été traitées ci-dessus
 | |
|         if (str::starts_with("+", $uname)) {
 | |
|           if (!in_array($uname, self::UPDATE_DIRECTIVES)) {
 | |
|             trigger_error("$uname: unknown update directive", E_USER_WARNING);
 | |
|           }
 | |
|           continue;
 | |
|         }
 | |
| 
 | |
|         $type = A::get($uinfos, "+type", "table");
 | |
|         if ($type === "table") {
 | |
|           # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | |
|           $table = $uname;
 | |
|           $table_name = A::get($uinfos, "+name", $table);
 | |
|           $cols = $uinfos;
 | |
| 
 | |
|           #######################################################################
 | |
|           # suppression de table
 | |
|           if ($cols === null) {
 | |
|             self::del_table($table);
 | |
|             if ($prod) unset($no_overwrites[$table]);
 | |
|             continue;
 | |
|           }
 | |
| 
 | |
|           # liste de champs "normale"
 | |
|           if (!self::has_table($table)) {
 | |
|             #####################################################################
 | |
|             # nouvelle table
 | |
|             $suffix = A::get($cols, "+suffix");
 | |
|             self::add_table($table, $suffix, $dbtrait, $table_name);
 | |
|             if ($prod) $no_overwrites[$table] = true;
 | |
|             foreach ($cols as $col => $value) {
 | |
|               if (!str::starts_with("+", $col)) {
 | |
|                 $def = $value;
 | |
|                 $cinfos = self::add_col($table, $col, $def);
 | |
|                 if ($cinfos["pk"]) self::set_table_pk($table, $col);
 | |
|               } else {
 | |
|                 switch ($col) {
 | |
|                 case "+primary key":
 | |
|                   $pk = A::with($value);
 | |
|                   self::set_table_pk($table, $pk);
 | |
|                   break;
 | |
|                 case "+foreign key":
 | |
|                   foreach (A::with($value) as $fk => $ref) {
 | |
|                     $ms = null;
 | |
|                     if (self::is_multi_fkdef($fk, $ms)) $fk = $ms[1];
 | |
|                     if (self::is_single_fkref($ref, $ms)) {
 | |
|                       self::add_t1link($fk, $table, $ms[2], $ms[1]);
 | |
|                     }
 | |
|                   }
 | |
|                   break;
 | |
|                 default:
 | |
|                   if (!in_array($col, self::TABLE_DIRECTIVES)) {
 | |
|                     trigger_error("$table: $col: unknown table directive", E_USER_WARNING);
 | |
|                   }
 | |
|                   break;
 | |
|                 }
 | |
|               }
 | |
|             }
 | |
|           } else {
 | |
|             #####################################################################
 | |
|             # maj d'une table existante
 | |
|             foreach ($cols as $col => $def) {
 | |
|               # les directives seront traitées plus tard
 | |
|               if (!str::starts_with("+", $col)) {
 | |
|                 if ($def === null) {
 | |
|                   ## suppression d'un champ
 | |
|                   if (!self::has_col($table, $col)) {
 | |
|                     trigger_error("$table.$col: deleting a non-existent column", E_USER_WARNING);
 | |
|                   }
 | |
|                   self::del_col($table, $col);
 | |
|                 } else {
 | |
|                   if (!self::has_col($table, $col)) {
 | |
|                     self::add_col($table, $col, $def);
 | |
|                   }
 | |
|                 }
 | |
|               } else {
 | |
|                 ## maj directive
 | |
|                 trigger_error("$table: $col: table directive update is not (yet) supported", E_USER_WARNING);
 | |
|               }
 | |
|             }
 | |
|           }
 | |
| 
 | |
|         } elseif ($type === "view") {
 | |
|           # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | |
|           $view = $uname;
 | |
|           $view_name = A::get($uinfos, "+name", $view);
 | |
|           $vinfos = $uinfos;
 | |
| 
 | |
|           #####################################################################
 | |
|           # suppression de vue
 | |
|           if ($vinfos === null) {
 | |
|             self::del_view($view);
 | |
|             if ($prod) unset($no_overwrites[$view]);
 | |
|             continue;
 | |
|           }
 | |
| 
 | |
|           # définition "normale"
 | |
|           if (!self::has_view($view)) {
 | |
|             ###################################################################
 | |
|             # nouvelle vue
 | |
|             $tables = A::get($vinfos, "+view_tables");
 | |
|             self::add_view($view, $tables, $dbtrait, $view_name);
 | |
|             if ($prod) $no_overwrites[$view] = true;
 | |
| 
 | |
|           } else {
 | |
|             ###################################################################
 | |
|             # maj d'une vue existante
 | |
|             trigger_error("$view: view update is not (yet) supported", E_USER_WARNING);
 | |
|           }
 | |
| 
 | |
|         } else {
 | |
|           trigger_error("$type: unknown type, expected table or view", E_USER_WARNING);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     foreach (self::$tables as $table => $tinfos) {
 | |
|       $class = str::upperw($table);
 | |
|       $class = preg_replace('/[^a-zA-Z0-9]*/', "", $class);
 | |
| 
 | |
|       $suffix = $tinfos["suffix"];
 | |
|       $suffix = $suffix? "\"$suffix\"": "null";
 | |
| 
 | |
|       $pk = $tinfos["pk"];
 | |
|       $pk_key = [];
 | |
|       if ($pk !== null) {
 | |
|         foreach ($pk as $key) {
 | |
|           $pk_key[] = "\"$key\"";
 | |
|         }
 | |
|       }
 | |
|       if (!$pk_key) {
 | |
|         $pk_key = "false";
 | |
|       } elseif (\count($pk_key) == 1) {
 | |
|         $pk_key = $pk_key[0];
 | |
|       } else {
 | |
|         $pk_key = "[".implode(", ", $pk_key)."]";
 | |
|       }
 | |
| 
 | |
|       $pk_auto = false;
 | |
|       $cols = $tinfos["cols"];
 | |
|       foreach ($cols as $cinfos) {
 | |
|         if ($cinfos["auto"]) {
 | |
|           $pk_auto = true;
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|       $pk_auto = $pk_auto? "true": "false";
 | |
| 
 | |
|       $dbtrait = $tinfos["dbtrait"];
 | |
|       $table_name = $tinfos["name"];
 | |
| 
 | |
|       ###
 | |
| 
 | |
|       $lines = ["<?php"];
 | |
|       if ($package) $lines[] = "namespace $package;";
 | |
|       $lines[] = "";
 | |
|       $lines[] = "class _$class extends \\$baserow {";
 | |
|       if ($dbtrait) {
 | |
|         $lines[] = "  use \\$dbtrait;";
 | |
|         $lines[] = "";
 | |
|       }
 | |
|       $lines[] = "  const TABLE_NAME = \"$table_name\";";
 | |
|       $lines[] = "  const COL_SUFFIX = $suffix;";
 | |
|       $lines[] = "  const PK_KEY = $pk_key;";
 | |
|       $lines[] = "  const PK_AUTO = $pk_auto;";
 | |
|       $lines[] = "  const COLUMNS = [";
 | |
|       foreach (array_keys($cols) as $col) {
 | |
|         $lines[] = "    \"$col\",";
 | |
|       }
 | |
|       $lines[] = "  ];";
 | |
| 
 | |
|       #XXX désactiver pour le moment
 | |
|       #self::add_vtables($lines, $cols);
 | |
|       #self::add_data_schema($lines, $cols);
 | |
|       $lines[] = "}";
 | |
| 
 | |
|       $overwrite_class = $overwrite || !A::has($no_overwrites, $table);
 | |
|       $outfname = "_$class.php";
 | |
|       $outf = "$destdir/$outfname";
 | |
|       $ovsuf = self::get_ovsuf($outf, $overwrite_class);
 | |
|       if ($ovsuf || $verbose) {
 | |
|         self::trace("  php output: $outfname$ovsuf");
 | |
|         self::write_lines($lines, $outf, $overwrite_class, true);
 | |
|       }
 | |
| 
 | |
|       ###
 | |
| 
 | |
|       $lines = ["<?php"];
 | |
|       if ($package) $lines[] = "namespace $package;";
 | |
|       $lines[] = "";
 | |
|       $lines[] = "class $class extends _$class {";
 | |
|       $lines[] = "}";
 | |
| 
 | |
|       # les fichiers Class ne sont jamais écrasés automatiquement
 | |
|       $overwrite_class = false; #$overwrite && !A::has($no_overwrites, $table);
 | |
|       $outfname = "$class.php";
 | |
|       $outf = "$destdir/$outfname";
 | |
|       $ovsuf = self::get_ovsuf($outf, $overwrite_class);
 | |
|       if ($ovsuf || $verbose) {
 | |
|         self::trace("  php output: $outfname$ovsuf");
 | |
|         self::write_lines($lines, $outf, $overwrite_class, true);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     foreach (self::$views as $view => $vinfos) {
 | |
|       $class = str::upperw($view);
 | |
|       $class = preg_replace('/[^a-zA-Z0-9]*/', "", $class);
 | |
| 
 | |
|       $cols = [];
 | |
|       foreach ($vinfos["tables"] as $table) {
 | |
|         $cols = array_merge($cols, self::$tables[$table]["cols"]);
 | |
|       }
 | |
| 
 | |
|       $dbtrait = $tinfos["dbtrait"];
 | |
|       $view_name = $vinfos["name"];
 | |
| 
 | |
|       ###
 | |
| 
 | |
|       $lines = ["<?php"];
 | |
|       if ($package) $lines[] = "namespace $package;";
 | |
|       $lines[] = "";
 | |
|       $lines[] = "class _$class extends $baserow {";
 | |
|       if ($dbtrait) {
 | |
|         $lines[] = "  use \\$dbtrait;";
 | |
|         $lines[] = "";
 | |
|       }
 | |
|       $lines[] = "  const TABLE_NAME = \"$view_name\";";
 | |
|       $lines[] = "  const COL_SUFFIX = null;";
 | |
|       $lines[] = "  const PK_KEY = false;";
 | |
|       $lines[] = "  const PK_AUTO = false;";
 | |
|       $lines[] = "  const COLUMNS = [";
 | |
|       foreach (array_keys($cols) as $col) {
 | |
|         $lines[] = "    \"$col\",";
 | |
|       }
 | |
|       $lines[] = "  ];";
 | |
| 
 | |
|       #XXX désactiver pour le moment
 | |
|       #self::add_vtables($lines, $cols);
 | |
|       #self::add_data_schema($lines, $cols);
 | |
|       $lines[] = "}";
 | |
| 
 | |
|       $overwrite_class = $overwrite || !A::has($no_overwrites, $table);
 | |
|       $outfname = "_$class.php";
 | |
|       $outf = "$destdir/$outfname";
 | |
|       $ovsuf = self::get_ovsuf($outf, $overwrite_class);
 | |
|       if ($ovsuf || $verbose) {
 | |
|         self::trace("  php output: $outfname$ovsuf");
 | |
|         self::write_lines($lines, $outf, $overwrite_class, true);
 | |
|       }
 | |
| 
 | |
|       ###
 | |
| 
 | |
|       $lines = ["<?php"];
 | |
|       if ($package) $lines[] = "namespace $package;";
 | |
|       $lines[] = "";
 | |
|       $lines[] = "class $class extends _$class {";
 | |
|       $lines[] = "}";
 | |
| 
 | |
|       # les fichiers Class ne sont jamais écrasés automatiquement
 | |
|       $overwrite_class = false; #$overwrite && !A::has($no_overwrites, $view);
 | |
|       $outfname = "$class.php";
 | |
|       $outf = "$destdir/$outfname";
 | |
|       $ovsuf = self::get_ovsuf($outf, $overwrite_class);
 | |
|       if ($ovsuf || $verbose) {
 | |
|         self::trace("  php output: $outfname$ovsuf");
 | |
|         self::write_lines($lines, $outf, $overwrite_class, true);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 |