diff --git a/notice/db-schema/1.0.md b/notice/db-schema/1.0.md index c6688b4..a4f3fab 100644 --- a/notice/db-schema/1.0.md +++ b/notice/db-schema/1.0.md @@ -35,7 +35,7 @@ The aim of this package is to make your life easier working with database creati Features: - Manage multiple databases (according to the database-driver) - Push a json configuration to a database (mysql) -- (todo) Pull a database schema to a json file +- Pull a database schema to a json file 2 - Usage ---- @@ -66,25 +66,42 @@ use \dbschema\core\dBuilder; ```php // loads the default schema -dBuilder::parse(); +$dbuilder = dBuilder::load(); // loads the custom schema -dBuilder::parse('custom'); +$dbuilder = dBuilder::load('custom'); ``` > ### 2) Push the loaded schema to the database ```php // push to the default database -dBuilder::push(); +$dbuilder->push(); // push a custom database -dBuilder::push('custom-db'); +$dbuilder->push('custom-db'); ``` Note: In the first line, it will use the database-driver default configuration. In the second line, it will use the database-driver `custom` configuration. +> ### 3) Pull a configuration from a database + +```php +// pulls into the default schema (default database-driver config) +$dbuilder = dBuilder::pull(); + +// pulls into the custom schema +$dbuilder = dBuilder::pull('custom'); + +// pulls into the default schema ('custom-db' database-driver config) +$dbuilder = dBuilder::pull(null, 'custom-db'); + +// pulls into the custom schema ('custom-db' database-driver config) +$dbuilder = dBuilder::pull('custom', 'custom-db); +``` + + 3 - configuration ---- @@ -136,31 +153,4 @@ Note: In the first line, it will use the database-driver default configuration. ## (2) Field types -### Default types -|Type|Example|Description| -|---|---|---| -|`bool`|`true`|Boolean (true or false)| -|`boolean`|`true`|Boolean (true or false)| -|`bit`|`true`|Boolean (true or false)| -|`char`|`a`|Any character| -|`int`|`10`|Positive integer number between `0` and `2147483647`| -|`integer`|`10`|Positive integer number between `0` and `2147483647`| -|`number`|`10`|Positive integer number between `0` and `2147483647`| -|`float`|`-10.2`, `23.4`|Any real number| -|`double`|`-10.2`, `23.4`|Any real number| -|`decimal`|`-10.2`, `23.4`|Any real number| -|`real`|`-10.2`, `23.4`|Any real number| -|`time`|`12:23,32`|Time| -|`date`|`12-08-2017`|Date| -|`datetime`|`12-08-2017 12:23,32`|Datetime| -|`timestamp`|`1504954629`|Timestamp| - -### Complex types - -|Type|Description| -|---|---| -|`varchar(a)`|Text of a maximum length of `a`| -|`double(a,b)`|Decimal number with a size of `a` and a decimal size of `b`| -|`float(a,b)`|Decimal number with a size of `a` and a decimal size of `b`| -|`decimal(a,b)`|Decimal number with a size of `a` and a decimal size of `b`| -|`real(a,b)`|Decimal number with a size of `a` and a decimal size of `b`| +You must use the default MySQL field types. diff --git a/src/config/db-schema/1.0/db-schema.json b/src/config/db-schema/1.0/db-schema.json new file mode 100644 index 0000000..65c0e12 --- /dev/null +++ b/src/config/db-schema/1.0/db-schema.json @@ -0,0 +1,18 @@ +{ + "default": { + + "user": { + "id_user": { "type": "int", "primary": true, "auto_increment": true }, + "username": { "type": "varchar(30)", "unique": true, "null": false }, + "description": { "type": "text", "default": null } + }, + + "article": { + "id_article": { "type": "int", "primary": true, "auto_increment": true }, + "#id_writer": { "type": "int", "ref": [ "user", "id_user" ] }, + "title": { "type": "varchar(50)", "null": false }, + "body": { "type": "text", "null": false } + } + + } +} \ No newline at end of file diff --git a/src/packages/db-schema/1.0/core/dBuilder.php b/src/packages/db-schema/1.0/core/dBuilder.php index fdab823..5e2694a 100644 --- a/src/packages/db-schema/1.0/core/dBuilder.php +++ b/src/packages/db-schema/1.0/core/dBuilder.php @@ -9,20 +9,33 @@ // Constantes - private static function config_path(){ return __ROOT__.'/config/dbschema.json'; } + private static function config_path(){ return __ROOT__.'/config/db-schema.json'; } // Private attributes - private static $raw = null; + private $raw = null; + + + /* [0] Constructor + * + * @raw Creates an instance from a json + * + =========================================================*/ + private function __construct($raw=[]){ + $this->raw = $raw; + } /* [1] Builds a local schema from the json file * - * @return status False on error + * @label The database label + * + * @return instance dBuilder instance -> NULL on error + * =========================================================*/ - public static function parse(){ + public static function load($label=null){ /* [1] Extract config file =========================================================*/ @@ -30,22 +43,28 @@ /* (1) Check if file exists */ if( !file_exists($conf) ) - return false; + return null; /* (2) Try to read file */ - self::$raw = file_get_contents($conf); + $RAW = file_get_contents($conf); - if( self::$raw === false ) - return false; + if( $RAW === false ) + return null; /* (3) Try to parse JSON */ - self::$raw = json_decode(self::$raw, true); + $RAW = json_decode($RAW, true); - if( is_null(self::$raw) ) - return false; + if( is_null($RAW) ) + return null; - /* (4) Add 'constrain' flag according to '#' before column name */ - foreach(self::$raw as &$table){ + /* (4) If label does not exist -> abort */ + is_null($label) && ($label = 'default'); + + if( !isset($RAW[$label]) ) + return null; + + /* (5) Add 'constraint' flag according to '#' before column name */ + foreach($RAW[$label] as &$table){ foreach($table as $column_name=>$column){ if( $column_name[0] == '#' ){ @@ -60,7 +79,7 @@ - return true; + return new self($RAW[$label]); } @@ -69,20 +88,22 @@ /* [2] Populates a database * - * @label DatabaseDriver label + * @dblabel DatabaseDriver label * * @return status False on error =========================================================*/ - public static function push($label=null){ + public function push($ddlabel=null){ - $PDO = DatabaseDriver::getPDO($label); + /* [0] Initialize useful variables + =========================================================*/ + $PDO = DatabaseDriver::getPDO($ddlabel); $QUERY = ''; $foreign_keys = []; // global foreign keys /* [1] For each table create an entry =========================================================*/ - foreach(self::$raw as $table_name=>$table){ + foreach($this->raw as $table_name=>$table){ /* (1) Initialise variables ---------------------------------------------------------*/ @@ -114,12 +135,8 @@ if( !isset($column['type']) || !is_string($column['type']) ) continue; - /* (2) If incorrect type, ignore column */ - if( ($SQLType = self::checkType($column['type'])) == false ) - continue; - - /* (3) Add corresponding SQL type to the query */ - $QUERY .= "$SQLType"; + /* (2) Add corresponding SQL type to the query */ + $QUERY .= $column['type']; /* (2.2) Manage inline constraints (not null, auto_increment) @@ -147,12 +164,12 @@ $fk = $column['ref']; // {1} Check if the 'ref' field exists in JSON structure // - if( isset(self::$raw[$fk[0]]) && isset(self::$raw[$fk[0]][$fk[1]]) ){ + if( isset($this->raw[$fk[0]]) && isset($this->raw[$fk[0]][$fk[1]]) ){ // {2} Check if the type matches // - $refSQLType = self::checkType(self::$raw[$fk[0]][$fk[1]]['type']); + $refType = $this->raw[$fk[0]][$fk[1]]['type']; - if( $refSQLType == $SQLType ) + if( $refType == $column['type'] ) $foreign_keys[] = [ $table_name, $column_name, $fk[0], $fk[1] ]; } @@ -233,76 +250,118 @@ - - /* [3] Checks if a given type is correct + /* [3] Fills a config file from a database * - * @type The human-readable type + * @label The database label + * @dblabel DatabaseDriver label * - * @return SQLType The corresponding SQL type - * FALSE on error + * @return instance dBuilder instance -> NULL on error * =========================================================*/ - private static function checkType($type){ - /* [1] Check complex types + public static function pull($label=null, $ddlabel=null){ + /* [1] Get basic information (tables) =========================================================*/ - /* (1) Varchar - ---------------------------------------------------------*/ - if( preg_match("@^varchar\((\d+)\)$@", $type, $match) ) - return "VARCHAR(${match[1]})"; + /* (1) Initialize raw data */ + $RAW = []; - /* (2) Double - ---------------------------------------------------------*/ - if( preg_match("@^(?:double|float|decimal|real)\((\d+), ?(\d+)\)$@", $type, $match) ) - return "DOUBLE(${match[1]},${match[2]})"; + /* (2) Get database name */ + $db_name = DatabaseDriver::get()->getConfig()['dbname']; + + /* (3) Get the table list */ + $tabList = DatabaseDriver::getPDO($ddlabel)->query("SHOW TABLES"); + $tabList = $tabList->fetchAll(); + + /* (4) Populate raw data */ + foreach($tabList as $field){ + $table_name = $field['Tables_in_'.$db_name]; + $RAW[$table_name] = []; + } - /* [2] Check simple types + /* [2] Get each tables' columns =========================================================*/ - /* (1) Boolean - ---------------------------------------------------------*/ - if( $type == 'bool' || $type == 'boolean' || $type == 'bit' ) - return 'BIT'; + foreach($RAW as $table_name=>&$table){ - /* (2) Character - ---------------------------------------------------------*/ - if( $type == 'char' || $type == 'byte' ) - return 'CHAR'; + /* (1) Get column list */ + $colList = DatabaseDriver::getPDO($ddlabel)->query("SHOW COLUMNS FROM $table_name"); + $colList = $colList->fetchAll(); - /* (3) Integer - ---------------------------------------------------------*/ - if( $type == 'int' || $type == 'integer' || $type == 'number' ) - return 'INT'; + /* (2) Add each column to $table array */ + foreach($colList as $column){ - /* (4) Float - ---------------------------------------------------------*/ - if( $type == 'float' || $type == 'double' || $type == 'decimal' || $type == 'real' ) - return 'DOUBLE'; + /* (2.1) Foreign key management */ + $col_name = ($column['Key'] == 'MUL') ? '#'.$column['Field'] : $column['Field']; - /* (5) Text - ---------------------------------------------------------*/ - if( $type == 'text' ) - return 'TEXT'; + /* (2.2) Fill basic data*/ + $table[$col_name] = [ + 'type' => $column['Type'] + ]; - /* (6) Date & time - ---------------------------------------------------------*/ - if( $type == 'date' ) - return 'DATE'; + // {2.2.1} PRIMARY KEY // + if( $column['Key'] == 'PRI' ) + $table[$col_name]['primary'] = true; - if( $type == 'time' ) - return 'TIME'; + // {2.2.2} NOT NULL // + if( $column['Null'] == 'NO' ) + $table[$col_name]['null'] = false; - if( $type == 'datetime' ) - return 'DATETIME'; + // {2.2.3} AUTO_INCREMENT // + if( $column['Extra'] == 'auto_increment' ) + $table[$col_name]['auto_increment'] = true; - if( $type == 'timestamp' ) - return 'TIMESTAMP'; + } + + /* (3) Get each tables' constraints */ + // {3.1} Get table constraints // + $conList = DatabaseDriver::getPDO($ddlabel)->query("SHOW CREATE TABLE $table_name"); + $conList = $conList->fetch()['Create Table']; + $conLines = explode("\n", $conList); + + // {3.2} For each column -> get constraints // + foreach($conLines as $i=>$line) + if( preg_match('/CONSTRAINT `.+` FOREIGN KEY \(`(.+)`\) REFERENCES `(.+)` \(`(.+)`\)+/i', $line, $m) ) + if( isset($table['#'.$m[1]]) ) + $table['#'.$m[1]]['ref'] = [ $m[2], $m[3] ]; + + } - return false; + /* [3] Extract config file + =========================================================*/ + $conf = self::config_path(); - } + /* (1) Check if file exists */ + if( !file_exists($conf) ) + return 1; + + /* (2) Try to read file */ + $OUTDATED = file_get_contents($conf); + + if( $OUTDATED === false ) + return 2; + + /* (3) Try to parse JSON */ + $OUTDATED = json_decode($OUTDATED, true); + + if( is_null($OUTDATED) ) + return 3; + /* [4] Adds pulled schema + writes + =========================================================*/ + /* (1) Set $label to default if missing */ + is_null($label) && ($label = 'default'); + + /* (2) Adds pulled conf to conf */ + var_dump($RAW); + $OUTDATED[$label] = $RAW; + + /* (3) Writes down new config */ + file_put_contents(self::config_path(), json_encode($OUTDATED, JSON_PRETTY_PRINT) ); + + + return self::load($label); + } } \ No newline at end of file