├── LICENSE ├── README.md └── mysql2phinx.php /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Great White Ark 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MySQL2Phinx 2 | 3 | A simple cli php script to generate a [phinx](https://github.com/robmorgan/phinx) migration from an existing MySQL database. 4 | 5 | ## Usage 6 | 7 | ``` 8 | $ php -f mysql2phinx.php [database] [user] [password] > migration.php 9 | ``` 10 | 11 | Will create an initial migration class in the file `migration.php` for all tables in the database passed. 12 | 13 | ## Caveat 14 | 15 | The `id` column will be unsigned. Phinx does not currently supported unsigned primary columns. There is [a workaround](https://github.com/robmorgan/phinx/issues/250). 16 | 17 | ### TODOs 18 | 19 | Not all phinx functionality is covered! **Check your migration code before use!** 20 | 21 | Currently **not supported**: 22 | 23 | * Column types: 24 | * [ ] `float` 25 | * [ ] `decimal` 26 | * [ ] `time` 27 | * [ ] `binary` 28 | * [ ] `boolean` 29 | -------------------------------------------------------------------------------- /mysql2phinx.php: -------------------------------------------------------------------------------- 1 | migration.php 8 | * ``` 9 | */ 10 | 11 | if ($argc < 4) { 12 | echo '===============================' . PHP_EOL; 13 | echo 'Phinx MySQL migration generator' . PHP_EOL; 14 | echo '===============================' . PHP_EOL; 15 | echo 'Usage:' . PHP_EOL; 16 | echo 'php -f ' . $argv[0] . ' [database] [user] [password] > migration.php'; 17 | echo PHP_EOL; 18 | exit; 19 | } 20 | 21 | $config = array( 22 | 'name' => $argv[1], 23 | 'user' => $argv[2], 24 | 'pass' => $argv[3], 25 | 'host' => $argc === 5 ? $argv[6] : 'localhost', 26 | 'port' => $argc === 6 ? $argv[5] : '3306' 27 | ); 28 | 29 | function createMigration($mysqli, $indent = 2) 30 | { 31 | $output = array(); 32 | foreach (getTables($mysqli) as $table) { 33 | $output[] = getTableMigration($table, $mysqli, $indent); 34 | } 35 | return implode(PHP_EOL, $output) . PHP_EOL ; 36 | } 37 | 38 | function getMysqliConnection($config) 39 | { 40 | $v = new mysqli($config['host'], $config['user'], $config['pass'], $config['name']); 41 | if ($v === false) { 42 | throw new Exception('Could not connect'); 43 | } 44 | return $v; 45 | } 46 | 47 | function getTables($mysqli) 48 | { 49 | $res = $mysqli->query('SHOW TABLES'); 50 | return array_map(function($a) { return $a[0]; }, $res->fetch_all()); 51 | } 52 | 53 | function getTableMigration($table, $mysqli, $indent) 54 | { 55 | $ind = getIndentation($indent); 56 | 57 | $output = array(); 58 | $output[] = $ind . '// Migration for table ' . $table; 59 | $output[] = $ind . '$table = $this->table(\'' . $table . '\');'; 60 | $output[] = $ind . '$table'; 61 | 62 | foreach (getColumns($table, $mysqli) as $column) { 63 | if ($column['Field'] !== 'id') { 64 | $output[] = getColumnMigration($column['Field'], $column, $indent + 1); 65 | } 66 | } 67 | 68 | if ($foreign_keys = getForeignKeysMigrations(getForeignKeys($table, $mysqli), $indent + 1)) { 69 | $output[] = $foreign_keys; 70 | } 71 | 72 | $output[] = $ind . ' ->create();'; 73 | $output[] = PHP_EOL; 74 | 75 | return implode(PHP_EOL, $output); 76 | } 77 | 78 | function getColumnMigration($column, $columndata, $indent) 79 | { 80 | $ind = getIndentation($indent); 81 | 82 | $phinxtype = getPhinxColumnType($columndata); 83 | $columnattributes = getPhinxColumnAttibutes($phinxtype, $columndata); 84 | $output = $ind . '->addColumn(\'' . $column . '\', \'' . $phinxtype . '\', ' . $columnattributes . ')'; 85 | return $output; 86 | } 87 | 88 | function getIndexMigrations($indexes, $indent) 89 | { 90 | $ind = getIndentation($indent); 91 | 92 | $keyedindexes = array(); 93 | foreach($indexes as $index) { 94 | if ($index['Column_name'] === 'id') { 95 | continue; 96 | } 97 | 98 | $key = $index['Key_name']; 99 | if (!isset($keyedindexes[$key])) { 100 | $keyedindexes[$key] = array(); 101 | $keyedindexes[$key]['columns'] = array(); 102 | $keyedindexes[$key]['unique'] = $index['Non_unique'] !== '1'; 103 | } 104 | 105 | $keyedindexes[$key]['columns'][] = $index['Column_name']; 106 | } 107 | 108 | $output = []; 109 | 110 | foreach ($keyedindexes as $index) { 111 | $columns = 'array(\'' . implode('\', \'', $index['columns']) . '\')'; 112 | $options = $index['unique'] ? 'array(\'unique\' => true)' : 'array()'; 113 | $output[] = $ind . '->addIndex(' . $columns . ', ' . $options . ')'; 114 | } 115 | 116 | return implode(PHP_EOL, $output); 117 | } 118 | 119 | function getForeignKeysMigrations($foreign_keys, $indent) 120 | { 121 | $ind = getIndentation($indent); 122 | $output = []; 123 | foreach ($foreign_keys as $foreign_key) { 124 | $output[] = $ind . "->addForeignKey('" . $foreign_key['COLUMN_NAME'] . "', '" . $foreign_key['REFERENCED_TABLE_NAME'] . "', '" . $foreign_key['REFERENCED_COLUMN_NAME'] . "', array(" 125 | . "'delete' => '" . str_replace(' ', '_', $foreign_key['DELETE_RULE']) . "'," 126 | . "'update' => '" . str_replace(' ', '_', $foreign_key['UPDATE_RULE']) . "'" 127 | . "))"; 128 | } 129 | return implode(PHP_EOL, $output); 130 | } 131 | 132 | /* ---- */ 133 | 134 | function getMySQLColumnType($columndata) 135 | { 136 | $type = $columndata['Type']; 137 | $pattern = '/^[a-z]+/'; 138 | preg_match($pattern, $type, $match); 139 | return $match[0]; 140 | } 141 | 142 | function getPhinxColumnType($columndata) 143 | { 144 | $type = getMySQLColumnType($columndata); 145 | 146 | switch($type) { 147 | case 'tinyint': 148 | case 'smallint': 149 | case 'int': 150 | case 'mediumint': 151 | return 'integer'; 152 | 153 | case 'timestamp': 154 | return 'timestamp'; 155 | 156 | case 'date': 157 | return 'date'; 158 | 159 | case 'datetime': 160 | return 'datetime'; 161 | 162 | case 'enum': 163 | return 'enum'; 164 | 165 | case 'char': 166 | return 'char'; 167 | 168 | case 'text': 169 | case 'tinytext': 170 | return 'text'; 171 | 172 | case 'varchar': 173 | return 'string'; 174 | 175 | default: 176 | return '[' . $type . ']'; 177 | } 178 | } 179 | 180 | function getPhinxColumnAttibutes($phinxtype, $columndata) 181 | { 182 | $attributes = array(); 183 | 184 | // var_dump($columndata); 185 | 186 | // has NULL 187 | if ($columndata['Null'] === 'YES') { 188 | $attributes[] = '\'null\' => true'; 189 | } 190 | 191 | // default value 192 | if ($columndata['Default'] !== null) { 193 | $default = is_int($columndata['Default']) ? $columndata['Default'] : '\'' . $columndata['Default'] . '\''; 194 | $attributes[] = '\'default\' => ' . $default; 195 | } 196 | 197 | // on update CURRENT_TIMESTAMP 198 | if ($columndata['Extra'] === 'on update CURRENT_TIMESTAMP') { 199 | $attributes[] = '\'update\' => \'CURRENT_TIMESTAMP\''; 200 | } 201 | 202 | // limit / length 203 | $limit = 0; 204 | switch (getMySQLColumnType($columndata)) { 205 | case 'tinyint': 206 | $limit = 'MysqlAdapter::INT_TINY'; 207 | break; 208 | 209 | case 'smallint': 210 | $limit = 'MysqlAdapter::INT_SMALL'; 211 | break; 212 | 213 | case 'mediumint': 214 | $limit = 'MysqlAdapter::INT_MEDIUM'; 215 | break; 216 | 217 | case 'bigint': 218 | $limit = 'MysqlAdapter::INT_BIG'; 219 | break; 220 | 221 | case 'tinytext': 222 | $limit = 'MysqlAdapter::TEXT_TINY'; 223 | break; 224 | 225 | case 'mediumtext': 226 | $limit = 'MysqlAdapter::TEXT_MEDIUM'; 227 | break; 228 | 229 | case 'longtext': 230 | $limit = 'MysqlAdapter::TEXT_LONG'; 231 | break; 232 | 233 | default: 234 | $pattern = '/\((\d+)\)$/'; 235 | if (1 === preg_match($pattern, $columndata['Type'], $match)) { 236 | $limit = $match[1]; 237 | } 238 | } 239 | if ($limit) { 240 | $attributes[] = '\'limit\' => ' . $limit; 241 | } 242 | 243 | // unsigned 244 | $pattern = '/\(\d+\) unsigned$/'; 245 | if (1 === preg_match($pattern, $columndata['Type'], $match)) { 246 | $attributes[] = '\'signed\' => false'; 247 | } 248 | 249 | // enum values 250 | if ($phinxtype === 'enum') { 251 | $attributes[] = '\'values\' => ' . str_replace('enum', 'array', $columndata['Type']); 252 | } 253 | 254 | return 'array(' . implode(', ', $attributes) . ')'; 255 | } 256 | 257 | function getColumns($table, $mysqli) 258 | { 259 | $res = $mysqli->query($query = 'SHOW COLUMNS FROM `' . $table . '`'); 260 | if ($res === false) { 261 | throw new Exception("Failed: $query"); 262 | } 263 | return $res->fetch_all(MYSQLI_ASSOC); 264 | } 265 | 266 | function getIndexes($table, $mysqli) 267 | { 268 | $res = $mysqli->query('SHOW INDEXES FROM ' . $table); 269 | return $res->fetch_all(MYSQLI_ASSOC); 270 | } 271 | 272 | function getForeignKeys($table, $mysqli) 273 | { 274 | $res = $mysqli->query("SELECT 275 | cols.TABLE_NAME, 276 | cols.COLUMN_NAME, 277 | refs.REFERENCED_TABLE_NAME, 278 | refs.REFERENCED_COLUMN_NAME, 279 | cRefs.UPDATE_RULE, 280 | cRefs.DELETE_RULE 281 | FROM INFORMATION_SCHEMA.COLUMNS as cols 282 | LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS refs 283 | ON refs.TABLE_SCHEMA=cols.TABLE_SCHEMA 284 | AND refs.REFERENCED_TABLE_SCHEMA=cols.TABLE_SCHEMA 285 | AND refs.TABLE_NAME=cols.TABLE_NAME 286 | AND refs.COLUMN_NAME=cols.COLUMN_NAME 287 | LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS cons 288 | ON cons.TABLE_SCHEMA=cols.TABLE_SCHEMA 289 | AND cons.TABLE_NAME=cols.TABLE_NAME 290 | AND cons.CONSTRAINT_NAME=refs.CONSTRAINT_NAME 291 | LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS cRefs 292 | ON cRefs.CONSTRAINT_SCHEMA=cols.TABLE_SCHEMA 293 | AND cRefs.CONSTRAINT_NAME=refs.CONSTRAINT_NAME 294 | WHERE 295 | cols.TABLE_NAME = '" . $table . "' 296 | AND cols.TABLE_SCHEMA = DATABASE() 297 | AND refs.REFERENCED_TABLE_NAME IS NOT NULL 298 | AND cons.CONSTRAINT_TYPE = 'FOREIGN KEY' 299 | ;"); 300 | return $res->fetch_all(MYSQLI_ASSOC); 301 | } 302 | 303 | function getIndentation($level) 304 | { 305 | return str_repeat(' ', $level); 306 | } 307 | 308 | echo '