From 17c6fa9b8bdc33e50d8c8af41261e280ddf01370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=80=E1=85=B2=E1=84=89?= =?UTF-8?q?=E1=85=AE=E1=86=AB?= Date: Thu, 21 May 2026 18:46:49 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20installer=5Fcompleted=3Dtrue=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EC=97=90=EC=84=9C=20=ED=85=8C=EC=9D=B4=EB=B8=94=20?= =?UTF-8?q?=EC=A1=B4=EC=9E=AC=20=EC=97=AC=EB=B6=80=20=EC=9A=B0=ED=9A=8C=20?= =?UTF-8?q?=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Extension/Traits/CachesModuleStatus.php | 19 ++++++++------ app/Extension/Traits/CachesPluginStatus.php | 20 +++++++++----- app/Extension/Traits/CachesTemplateStatus.php | 20 +++++++++----- app/Providers/LanguagePackServiceProvider.php | 6 ++--- app/Providers/ModuleRouteServiceProvider.php | 26 +++++++++---------- app/Providers/PluginRouteServiceProvider.php | 16 +++++------- config/database.php | 20 +++++++------- 7 files changed, 68 insertions(+), 59 deletions(-) diff --git a/app/Extension/Traits/CachesModuleStatus.php b/app/Extension/Traits/CachesModuleStatus.php index 95cd4c49..aa011ff8 100644 --- a/app/Extension/Traits/CachesModuleStatus.php +++ b/app/Extension/Traits/CachesModuleStatus.php @@ -76,23 +76,26 @@ public static function invalidateModuleStatusCache(): void /** * DB 연결 + 테이블 존재 여부를 확인합니다 (인스톨러 안전성). * - * 설치 완료 상태(`config('app.installer_completed')`)일 때는 테이블 존재를 - * 전제로 하여 `Schema::hasTable()` 호출을 건너뜁니다. 인스톨러 이전 환경이나 - * 테스트에서는 기존 체크 경로로 폴백합니다. + * 항상 실제 테이블 존재 여부를 확인합니다. + * 결과는 정적 캐시에 저장되어 같은 요청 내에서 중복 Schema 조회를 방지합니다. + * (신규 서버 배포 시 INSTALLER_COMPLETED=true 상태에서 마이그레이션 전 실행되는 경우 대응) */ private static function isExtensionTableReady(string $table): bool { - if (config('app.installer_completed')) { - return true; + static $cache = []; + + if (isset($cache[$table])) { + return $cache[$table]; } try { DB::connection()->getPdo(); - - return Schema::hasTable($table); + $cache[$table] = Schema::hasTable($table); } catch (\Throwable $e) { - return false; + $cache[$table] = false; } + + return $cache[$table]; } /** diff --git a/app/Extension/Traits/CachesPluginStatus.php b/app/Extension/Traits/CachesPluginStatus.php index e6c1f172..9d0a4370 100644 --- a/app/Extension/Traits/CachesPluginStatus.php +++ b/app/Extension/Traits/CachesPluginStatus.php @@ -74,22 +74,28 @@ public static function invalidatePluginStatusCache(): void } /** - * 설치 완료 상태에서는 `Schema::hasTable()` 호출을 건너뜁니다. - * 인스톨러 이전 환경이나 테스트에서는 기존 체크 경로로 폴백합니다. + * DB 연결 + 테이블 존재 여부를 확인합니다 (인스톨러 안전성). + * + * 항상 실제 테이블 존재 여부를 확인합니다. + * 결과는 정적 캐시에 저장되어 같은 요청 내에서 중복 Schema 조회를 방지합니다. + * (신규 서버 배포 시 INSTALLER_COMPLETED=true 상태에서 마이그레이션 전 실행되는 경우 대응) */ private static function isPluginTableReady(): bool { - if (config('app.installer_completed')) { - return true; + static $cache = null; + + if ($cache !== null) { + return $cache; } try { DB::connection()->getPdo(); - - return Schema::hasTable('plugins'); + $cache = Schema::hasTable('plugins'); } catch (\Throwable $e) { - return false; + $cache = false; } + + return $cache; } private static function resolvePluginStatusCache(): CacheInterface diff --git a/app/Extension/Traits/CachesTemplateStatus.php b/app/Extension/Traits/CachesTemplateStatus.php index 10294afd..36f74d2f 100644 --- a/app/Extension/Traits/CachesTemplateStatus.php +++ b/app/Extension/Traits/CachesTemplateStatus.php @@ -99,22 +99,28 @@ public static function invalidateTemplateStatusCache(): void } /** - * 설치 완료 상태에서는 `Schema::hasTable()` 호출을 건너뜁니다. - * 인스톨러 이전 환경이나 테스트에서는 기존 체크 경로로 폴백합니다. + * DB 연결 + 테이블 존재 여부를 확인합니다 (인스톨러 안전성). + * + * 항상 실제 테이블 존재 여부를 확인합니다. + * 결과는 정적 캐시에 저장되어 같은 요청 내에서 중복 Schema 조회를 방지합니다. + * (신규 서버 배포 시 INSTALLER_COMPLETED=true 상태에서 마이그레이션 전 실행되는 경우 대응) */ private static function isTemplateTableReady(): bool { - if (config('app.installer_completed')) { - return true; + static $cache = null; + + if ($cache !== null) { + return $cache; } try { DB::connection()->getPdo(); - - return Schema::hasTable('templates'); + $cache = Schema::hasTable('templates'); } catch (\Throwable $e) { - return false; + $cache = false; } + + return $cache; } private static function resolveTemplateStatusCache(): CacheInterface diff --git a/app/Providers/LanguagePackServiceProvider.php b/app/Providers/LanguagePackServiceProvider.php index a4df551c..7b1ef5ad 100644 --- a/app/Providers/LanguagePackServiceProvider.php +++ b/app/Providers/LanguagePackServiceProvider.php @@ -478,10 +478,8 @@ private function isRegistryReady(): bool return false; } - if (config('app.installer_completed') === true) { - return true; - } - + // 항상 테이블 존재 여부를 확인합니다 (installer_completed 여부와 무관). + // 신규 서버 배포 시 INSTALLER_COMPLETED=true 상태에서 마이그레이션 전 실행되는 경우 대응. try { return Schema::hasTable('language_packs'); } catch (QueryException $e) { diff --git a/app/Providers/ModuleRouteServiceProvider.php b/app/Providers/ModuleRouteServiceProvider.php index 2e28b5a0..99ba9aea 100644 --- a/app/Providers/ModuleRouteServiceProvider.php +++ b/app/Providers/ModuleRouteServiceProvider.php @@ -49,22 +49,20 @@ protected function loadModuleRoutes(): void return; } - // 설치 완료 상태에서는 Schema introspection 을 건너뜀 (매 요청 쿼리 제거). - // 인스톨러 이전 환경에서는 기존 체크 경로로 폴백. - if (! config('app.installer_completed')) { - try { - if (! Schema::hasTable('modules')) { - return; - } - - // identifier 컬럼이 없으면 스킵 (마이그레이션 미적용 상태) - if (! Schema::hasColumn('modules', 'identifier')) { - return; - } - } catch (\Exception) { - // DB 연결 실패 시 조용히 스킵 (설정 오류, 마이그레이션 전 등) + // 항상 테이블 존재 여부를 확인합니다 (installer_completed 여부와 무관). + // 신규 서버 배포 시 INSTALLER_COMPLETED=true 상태에서 마이그레이션 전 실행되는 경우 대응. + try { + if (! Schema::hasTable('modules')) { return; } + + // identifier 컬럼이 없으면 스킵 (마이그레이션 미적용 상태) + if (! Schema::hasColumn('modules', 'identifier')) { + return; + } + } catch (\Exception) { + // DB 연결 실패 시 조용히 스킵 (설정 오류, 마이그레이션 전 등) + return; } // 활성화된 모듈 identifier 목록 가져오기 diff --git a/app/Providers/PluginRouteServiceProvider.php b/app/Providers/PluginRouteServiceProvider.php index 5de99fd5..f94ee27b 100644 --- a/app/Providers/PluginRouteServiceProvider.php +++ b/app/Providers/PluginRouteServiceProvider.php @@ -45,17 +45,15 @@ protected function loadPluginRoutes(): void return; } - // 설치 완료 상태에서는 Schema introspection 을 건너뜀 (매 요청 쿼리 제거). - // 인스톨러 이전 환경에서는 기존 체크 경로로 폴백. - if (! config('app.installer_completed')) { - try { - if (! Schema::hasTable('plugins')) { - return; - } - } catch (\Exception) { - // DB 연결 실패 시 조용히 스킵 (설정 오류, 마이그레이션 전 등) + // 항상 테이블 존재 여부를 확인합니다 (installer_completed 여부와 무관). + // 신규 서버 배포 시 INSTALLER_COMPLETED=true 상태에서 마이그레이션 전 실행되는 경우 대응. + try { + if (! Schema::hasTable('plugins')) { return; } + } catch (\Exception) { + // DB 연결 실패 시 조용히 스킵 (설정 오류, 마이그레이션 전 등) + return; } $plugins = File::directories($pluginsPath); diff --git a/config/database.php b/config/database.php index da01b564..22dfe112 100644 --- a/config/database.php +++ b/config/database.php @@ -47,21 +47,21 @@ 'driver' => 'mysql', 'read' => [ 'host' => [ - env('DB_READ_HOST', '127.0.0.1'), + env('DB_READ_HOST', env('DB_HOST', '127.0.0.1')), ], - 'port' => env('DB_READ_PORT', '3306'), - 'database' => env('DB_READ_DATABASE', 'laravel'), - 'username' => env('DB_READ_USERNAME', 'root'), - 'password' => env('DB_READ_PASSWORD', ''), + 'port' => env('DB_READ_PORT', env('DB_PORT', '3306')), + 'database' => env('DB_READ_DATABASE', env('DB_DATABASE', 'laravel')), + 'username' => env('DB_READ_USERNAME', env('DB_USERNAME', 'root')), + 'password' => env('DB_READ_PASSWORD', env('DB_PASSWORD', '')), ], 'write' => [ 'host' => [ - env('DB_WRITE_HOST', '127.0.0.1'), + env('DB_WRITE_HOST', env('DB_HOST', '127.0.0.1')), ], - 'port' => env('DB_WRITE_PORT', '3306'), - 'database' => env('DB_WRITE_DATABASE', 'laravel'), - 'username' => env('DB_WRITE_USERNAME', 'root'), - 'password' => env('DB_WRITE_PASSWORD', ''), + 'port' => env('DB_WRITE_PORT', env('DB_PORT', '3306')), + 'database' => env('DB_WRITE_DATABASE', env('DB_DATABASE', 'laravel')), + 'username' => env('DB_WRITE_USERNAME', env('DB_USERNAME', 'root')), + 'password' => env('DB_WRITE_PASSWORD', env('DB_PASSWORD', '')), ], 'sticky' => true, 'url' => env('DB_URL'),