INSTALLER_COMPLETED=true 가 적힌 .env 를 빈 DB 새 서버에 복사한 뒤
php artisan migrate 로 테이블을 만들기 전에 앱이 부팅되면, installer_completed
fast-path 가 테이블 존재를 잘못 전제하여 뒤따르는 쿼리가 table not found 로
부팅을 깨뜨렸다. 마이그레이션을 실행하려는 부팅에서 그 명령이 오히려 실패하는
닭-달걀 상태가 된다.
fast-path 자체(매 요청 information_schema 쿼리 제거 성능 최적화)는 유지하되,
이미 존재하던 참조 구현(LanguagePackServiceProvider::isRegistryReady)의 방어
패턴을 InstallerContext::isSchemaMutatingCommand 로 SSoT 화하여 나머지 5곳에
통일 적용했다.
- app/Support/InstallerContext.php: 마이그레이션 계열 콘솔 명령 판정 헬퍼
(migrate/migrate:fresh/migrate:refresh/migrate:rollback/migrate:reset/db:wipe)
- Caches{Module,Plugin,Template}Status: fast-path 앞에 && 가드
- {Module,Plugin}RouteServiceProvider: hasTable 검증 진입 조건에 || 가드로
무방비 pluck 도달 차단
- LanguagePackServiceProvider: 인라인 argv 검사를 헬퍼로 치환(migrate:refresh 보강)
부류 B(NotificationHookListener, IdentityPolicyRepository)는 fast-path 이후
쿼리가 이미 try/catch 로 보호되어 빈 DB 에서도 안전하므로 수정 제외.
최초 설치 흐름은 migrate 시점에 완료 플래그가 없어(false) 이번 가드의 영향권
밖이며 원래대로 hasTable 폴백을 탄다. 가드가 실제 발동하는 유일 케이스는
"플래그 true + 빈 DB" 뿐이고, 정상 운영 웹 요청은 fast-path 성능이 유지된다.
회귀 테스트: InstallerContextTest 8건 + InstallerCompletedGuardTest 5건 추가
(수정 전 5건 fail → 수정 후 green). 관련 스위트 355 passed.
7.0.3 출시 버전 bump: 코어 버전 SSoT 7곳 + CHANGELOG 동기화.
132 lines
4.5 KiB
PHP
132 lines
4.5 KiB
PHP
<?php
|
|
|
|
namespace App\Extension\Traits;
|
|
|
|
use App\Contracts\Extension\CacheInterface;
|
|
use App\Enums\ExtensionStatus;
|
|
use App\Extension\Cache\CoreCacheDriver;
|
|
use App\Models\Template;
|
|
use App\Support\InstallerContext;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
|
|
/**
|
|
* 템플릿 상태 캐시를 관리하는 Trait
|
|
*
|
|
* 활성화된 템플릿, 설치된 템플릿 목록을 캐시하여 DB 조회 오버헤드를 줄입니다.
|
|
* TemplateManager, TemplateServiceProvider 등에서 사용됩니다.
|
|
*/
|
|
trait CachesTemplateStatus
|
|
{
|
|
/**
|
|
* 활성화된 템플릿 identifier 목록을 조회합니다.
|
|
*
|
|
* @return array<string> 활성화된 템플릿 identifier 배열
|
|
*/
|
|
public static function getActiveTemplateIdentifiers(): array
|
|
{
|
|
if (! self::isTemplateTableReady()) {
|
|
return [];
|
|
}
|
|
|
|
return self::resolveTemplateStatusCache()->remember(
|
|
'ext.templates.active_identifiers',
|
|
fn () => Template::where('status', ExtensionStatus::Active->value)
|
|
->pluck('identifier')
|
|
->toArray(),
|
|
(int) g7_core_settings('cache.extension_status_ttl', 86400),
|
|
['ext.status', 'ext.templates']
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 특정 타입의 활성화된 템플릿 identifier 목록을 조회합니다.
|
|
*
|
|
* @param string $type 템플릿 타입 (admin, user)
|
|
* @return array<string> 활성화된 템플릿 identifier 배열
|
|
*/
|
|
public static function getActiveTemplateIdentifiersByType(string $type): array
|
|
{
|
|
if (! self::isTemplateTableReady()) {
|
|
return [];
|
|
}
|
|
|
|
return self::resolveTemplateStatusCache()->remember(
|
|
"ext.templates.active_identifiers_{$type}",
|
|
fn () => Template::where('status', ExtensionStatus::Active->value)
|
|
->where('type', $type)
|
|
->pluck('identifier')
|
|
->toArray(),
|
|
(int) g7_core_settings('cache.extension_status_ttl', 86400),
|
|
['ext.status', 'ext.templates']
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 설치된 템플릿 (active + inactive) identifier 목록을 조회합니다.
|
|
*
|
|
* @return array<string> 설치된 템플릿 identifier 배열
|
|
*/
|
|
public static function getInstalledTemplateIdentifiers(): array
|
|
{
|
|
if (! self::isTemplateTableReady()) {
|
|
return [];
|
|
}
|
|
|
|
return self::resolveTemplateStatusCache()->remember(
|
|
'ext.templates.installed_identifiers',
|
|
fn () => Template::whereIn('status', [
|
|
ExtensionStatus::Active->value,
|
|
ExtensionStatus::Inactive->value,
|
|
])->pluck('identifier')->toArray(),
|
|
(int) g7_core_settings('cache.extension_status_ttl', 86400),
|
|
['ext.status', 'ext.templates']
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 템플릿 상태 캐시를 무효화합니다.
|
|
* 템플릿 상태 변경 시 (install, activate, deactivate, uninstall) 호출해야 합니다.
|
|
*/
|
|
public static function invalidateTemplateStatusCache(): void
|
|
{
|
|
$cache = self::resolveTemplateStatusCache();
|
|
$cache->forget('ext.templates.active_identifiers');
|
|
$cache->forget('ext.templates.active_identifiers_admin');
|
|
$cache->forget('ext.templates.active_identifiers_user');
|
|
$cache->forget('ext.templates.installed_identifiers');
|
|
}
|
|
|
|
/**
|
|
* 설치 완료 상태에서는 `Schema::hasTable()` 호출을 건너뜁니다.
|
|
* 인스톨러 이전 환경이나 테스트에서는 기존 체크 경로로 폴백합니다.
|
|
*
|
|
* 단, 마이그레이션 계열 명령 실행 중에는 `INSTALLER_COMPLETED=true` 라도 테이블이
|
|
* 아직 없을 수 있으므로(빈 DB 새 서버에 .env 복사 후 migrate 전) fast-path 를
|
|
* 신뢰하지 않고 실제 `Schema::hasTable()` 폴백으로 진입합니다.
|
|
*/
|
|
private static function isTemplateTableReady(): bool
|
|
{
|
|
if (! InstallerContext::isSchemaMutatingCommand() && config('app.installer_completed')) {
|
|
return true;
|
|
}
|
|
|
|
try {
|
|
DB::connection()->getPdo();
|
|
|
|
return Schema::hasTable('templates');
|
|
} catch (\Throwable $e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static function resolveTemplateStatusCache(): CacheInterface
|
|
{
|
|
try {
|
|
return app(CacheInterface::class);
|
|
} catch (\Throwable $e) {
|
|
return new CoreCacheDriver(config('cache.default', 'array'));
|
|
}
|
|
}
|
|
}
|