데이터베이스 마이그레이션
데이터베이스 마이그레이션은 데이터베이스 스키마에 대한 버전 제어 및 되돌릴 수 있는 변경 사항을 제공합니다. 개발, 스테이징 및 프로덕션 환경 전반에서 일관된 데이터베이스 상태를 보장합니다.
마이그레이션 구조
섹션 제목: “마이그레이션 구조”파일 이름 지정
섹션 제목: “파일 이름 지정”migrations/├── 001_create_articles_table.php├── 002_add_status_column.php├── 003_create_categories_table.php├── 004_add_indexes.php└── 005_add_foreign_keys.php마이그레이션 클래스
섹션 제목: “마이그레이션 클래스”<?phpdeclare(strict_types=1);
return new class { public function up(\XoopsDatabase $db): void { $table = $db->prefix('mymodule_articles');
$sql = "CREATE TABLE IF NOT EXISTS `{$table}` ( `id` VARCHAR(26) NOT NULL COMMENT 'ULID identifier', `title` VARCHAR(255) NOT NULL, `content` MEDIUMTEXT, `status` ENUM('draft', 'published', 'archived') DEFAULT 'draft', `author_id` INT UNSIGNED NOT NULL, `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_status` (`status`), KEY `idx_author` (`author_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
$db->queryF($sql); }
public function down(\XoopsDatabase $db): void { $table = $db->prefix('mymodule_articles'); $db->queryF("DROP TABLE IF EXISTS `{$table}`"); }};일반적인 작업
섹션 제목: “일반적인 작업”열 추가
섹션 제목: “열 추가”public function up(\XoopsDatabase $db): void{ $table = $db->prefix('mymodule_articles');
// Add single column $db->queryF("ALTER TABLE `{$table}` ADD COLUMN `views` INT UNSIGNED DEFAULT 0 AFTER `status`");
// Add multiple columns $db->queryF("ALTER TABLE `{$table}` ADD COLUMN `summary` TEXT AFTER `content`, ADD COLUMN `featured` TINYINT(1) DEFAULT 0 AFTER `views`");}
public function down(\XoopsDatabase $db): void{ $table = $db->prefix('mymodule_articles'); $db->queryF("ALTER TABLE `{$table}` DROP COLUMN `views`, DROP COLUMN `summary`, DROP COLUMN `featured`");}열 수정
섹션 제목: “열 수정”public function up(\XoopsDatabase $db): void{ $table = $db->prefix('mymodule_articles');
// Change column type $db->queryF("ALTER TABLE `{$table}` MODIFY COLUMN `title` VARCHAR(500) NOT NULL");
// Rename column $db->queryF("ALTER TABLE `{$table}` CHANGE `summary` `excerpt` TEXT");}색인 추가
섹션 제목: “색인 추가”public function up(\XoopsDatabase $db): void{ $table = $db->prefix('mymodule_articles');
// Single column index $db->queryF("CREATE INDEX `idx_created` ON `{$table}` (`created_at`)");
// Composite index $db->queryF("CREATE INDEX `idx_status_date` ON `{$table}` (`status`, `created_at`)");
// Unique index $db->queryF("CREATE UNIQUE INDEX `idx_slug` ON `{$table}` (`slug`)");
// Fulltext index $db->queryF("CREATE FULLTEXT INDEX `idx_search` ON `{$table}` (`title`, `content`)");}
public function down(\XoopsDatabase $db): void{ $table = $db->prefix('mymodule_articles'); $db->queryF("DROP INDEX `idx_created` ON `{$table}`"); $db->queryF("DROP INDEX `idx_status_date` ON `{$table}`"); $db->queryF("DROP INDEX `idx_slug` ON `{$table}`"); $db->queryF("DROP INDEX `idx_search` ON `{$table}`");}외래 키
섹션 제목: “외래 키”public function up(\XoopsDatabase $db): void{ $articles = $db->prefix('mymodule_articles'); $categories = $db->prefix('mymodule_categories');
$db->queryF("ALTER TABLE `{$articles}` ADD CONSTRAINT `fk_article_category` FOREIGN KEY (`category_id`) REFERENCES `{$categories}` (`id`) ON DELETE SET NULL ON UPDATE CASCADE");}
public function down(\XoopsDatabase $db): void{ $articles = $db->prefix('mymodule_articles'); $db->queryF("ALTER TABLE `{$articles}` DROP FOREIGN KEY `fk_article_category`");}마이그레이션 실행자
섹션 제목: “마이그레이션 실행자”마이그레이션 실행
섹션 제목: “마이그레이션 실행”function xoops_module_update_mymodule(\XoopsModule $module, $previousVersion){ $db = \XoopsDatabaseFactory::getDatabaseConnection(); $migrator = new MigrationRunner($db, $module->dirname());
try { $migrator->migrate(); return true; } catch (\Exception $e) { $module->setErrors($e->getMessage()); return false; }}마이그레이션 실행자 클래스
섹션 제목: “마이그레이션 실행자 클래스”class MigrationRunner{ private string $migrationsPath; private string $table;
public function __construct( private \XoopsDatabase $db, private string $moduleName ) { $this->migrationsPath = XOOPS_ROOT_PATH . "/modules/{$moduleName}/migrations"; $this->table = $db->prefix("{$moduleName}_migrations"); }
public function migrate(): void { $this->createMigrationsTable(); $executed = $this->getExecutedMigrations();
foreach ($this->getPendingMigrations($executed) as $file) { $this->runMigration($file); } }
private function runMigration(string $file): void { $migration = require $this->migrationsPath . '/' . $file; $migration->up($this->db);
$this->db->queryF( "INSERT INTO `{$this->table}` (migration, executed_at) VALUES (?, NOW())", [$file] ); }
public function rollback(int $steps = 1): void { $migrations = $this->getExecutedMigrations(); $toRollback = array_slice(array_reverse($migrations), 0, $steps);
foreach ($toRollback as $file) { $migration = require $this->migrationsPath . '/' . $file; $migration->down($this->db);
$this->db->queryF( "DELETE FROM `{$this->table}` WHERE migration = ?", [$file] ); } }}모범 사례
섹션 제목: “모범 사례”- 마이그레이션당 하나의 변경 사항 - 마이그레이션에 집중하세요.
- 항상 메소드 기록 - 롤백 활성화
- 양방향 테스트 - up() 및 down() 확인
- 트랜잭션 사용 - 복잡한 마이그레이션 래핑
- 이전 마이그레이션을 수정하지 마세요 - 대신 새 마이그레이션을 만드세요.
- 실행 전 백업 - 특히 프로덕션 환경에서
관련 문서
섹션 제목: “관련 문서”- 데이터베이스-스키마 - 스키마 설계
- 데이터베이스 작업 - 쿼리 실행 -../xoops_version.php - 모듈 매니페스트 -../../07-XOOPS-4.0/XOOPS-4.0-Architecture - 현대 건축