# Licensed under the MIT License (MIT) import os import sys import xml.etree.ElementTree as ET from datetime import datetime INDENT = " " NEWLINE = "\n" up = [] down = [] actions = [] def parse_column_list(s): columns = s.split(",") return '[' + ', '.join(f"'{c.strip()}'" for c in columns) + ']' def parse_column_element(column_element): name = column_element.get('Name') data_type = column_element.get('Type') nullable = column_element.get('Nullable', 'false').lower() == 'true' default = column_element.get('Default', '') comment = column_element.get('Comment', '') auto_increment = column_element.get('AutoIncrement', 'false').lower() == 'true' method = f"$table->{laravel_type(data_type)}('{name}')" if auto_increment: method += '->increments()' if nullable: method += '->nullable(true)' if default: if default.lower() == 'null': method += "->default(null)" else: method += f"->default('{default}')" if comment: method += f"->comment('{comment}')" return method def laravel_type(db_type): mapping = { 'integer': 'integer', 'string': 'string', 'text': 'text', 'boolean': 'boolean', 'datetime': 'dateTime', 'date': 'date', 'float': 'float', 'double': 'double', 'bigint': 'bigInteger', 'smallint': 'smallInteger', 'tinyint': 'tinyInteger', 'char': 'char', 'decimal': 'decimal' } return mapping.get(db_type.lower(), db_type) def handle_create_table(diff): table = diff.attrib['Name'] actions.append(f"create_{table}_table") up.append(f"Schema::create('{table}', function (Blueprint $table) {{") for column in diff.findall('Column'): up.append(INDENT + parse_column_element(column) + ';') for pk in diff.findall('PrimaryKey'): cols = parse_column_list(pk.get('Columns')) up.append(INDENT + f"$table->primary({cols}, '{pk.get('Name', 'pk_' + table)}');") for idx in diff.findall('Index'): up.append(INDENT + f"$table->index({parse_column_list(idx.get('Columns'))}, '{idx.get('Name')}');") for uq in diff.findall('Unique'): up.append(INDENT + f"$table->unique({parse_column_list(uq.get('Columns'))}, '{uq.get('Name')}');") for fk in diff.findall('ForeignKey'): up.append(INDENT + handle_foreign_key_inline(fk)) up.append("});") down.append(f"Schema::dropIfExists('{table}');") def handle_drop_table(diff): table = diff.attrib['Name'] actions.append(f"drop_table_{table}") up.append(f"Schema::dropIfExists('{table}');") down.append(f"echo \"Cannot safely revert this migration: table '{table}' was dropped and data is lost.\";") down.append("return false;") def handle_alter_table(diff): table = diff.find('Name').text actions.append(f"alter_{table}_table") unable_to_revert = False rename_to = diff.attrib.get('RenameTo') if rename_to: up.append(f"Schema::rename('{table}', '{rename_to}');") down.append(f"Schema::rename('{rename_to}', '{table}');") table = rename_to for col in diff.findall('DropColumn'): name = col.get('Name') up.append(f"Schema::table('{table}', function (Blueprint $table) {{ $table->dropColumn('{name}'); }});") down.append(f"echo \"Cannot safely revert this migration: column '{name}' was dropped.\";") unable_to_revert = True for col in diff.findall('AddColumn'): up.append(f"Schema::table('{table}', function (Blueprint $table) {{ {parse_column_element(col)}; }});") down.append(f"Schema::table('{table}', function (Blueprint $table) {{ $table->dropColumn('{col.get('Name')}'); }});") for ch in diff.findall('ChangeColumn'): new_col = ch.find('NewColumn') old_col = ch.find('OldColumn') up.append(f"Schema::table('{table}', function (Blueprint $table) {{ $table->renameColumn('{old_col.get('Name')}', '{new_col.get('Name')}'); }});") down.append(f"Schema::table('{table}', function (Blueprint $table) {{ $table->renameColumn('{new_col.get('Name')}', '{old_col.get('Name')}'); }});") if unable_to_revert: down.append("return false;") def handle_foreign_key_inline(fk): name = fk.get('Name') from_col = fk.get('FromColumn') to_col = fk.get('ToColumn') to_table = fk.get('ToTable') on_delete = fk.get('OnDelete') on_update = fk.get('OnUpdate') fk_stmt = f"$table->foreign('{from_col}')->references('{to_col}')->on('{to_table}')" if on_delete: fk_stmt += f"->onDelete('{on_delete}')" if on_update: fk_stmt += f"->onUpdate('{on_update}')" fk_stmt += f"->name('{name}');" return fk_stmt def get_filename(): now = datetime.now() suffix = actions[0] if len(actions) == 1 else 'migration' return now.strftime(f"%Y_%m_%d_%H%M%S_{suffix}.php") def generate_class(): lines = [ "