Files
Gnuboard7/app/Support/PackageManifestCacheHelper.php
T
HeuJung ffab451b4a fix(core,installer): dev vendor 설치본의 코어 업데이트 중단 수정 — stale 패키지 매니페스트 3계층
Laravel PackageManifest 는 bootstrap/cache/packages.php 가 있으면 stale 여부를 검사하지
않고 그대로 읽어 등재된 provider 를 new 한다. 코어 업데이트는 Step 6/8 에서 vendor 를
--no-dev 로 교체하지만 그 파일은 Step 11 까지 이전 설치본의 것이 남으므로, 옵션 없는
`composer install` 로 깔린 사이트에서는 Step 10 spawn 자식이 새 vendor 에 없는 provider 를
찾다 부팅 단계에서 죽는다. 부팅 전이라 앱 로그에 흔적이 없고 부모에게는 자식의 비정상
종료로만 보여, 운영자에게는 「Class ... not found」 와 수동 재개 안내만 남는다.
(sir.kr 커뮤니티 제보, 7.0.9 → 7.0.10)

3계층으로 막는다.

 1. 부모 — spawn 직전 PackageManifestCacheHelper::clear
 2. 자식 — bootstrap/app.php 가 G7_UPDATE_IN_PROGRESS 를 보고 스스로 정리한다.
 이미 배포된 7.0.9·7.0.10 부모는 고칠 수 없으므로 그 아래에서 도는 신버전
 자식의 유일한 방어다. App\ 클래스를 참조하지 않고 실패는 무시한다.
 3. 범위 — CoreVersionChecker 의 env APP_VERSION 우선을 CoreUpdateContext 트리 안으로
 축소한다. 업데이트 전에 뜬 artisan serve·큐 워커가 옛 값을 물고 확장을
 incompatible_core 로 끄던 경로를 닫는다(관리자 템플릿이 대상이면 복구 UI
 자체에 도달할 수 없다).

업데이트 트리 판정은 App\Support\CoreUpdateContext 가 단독 소유하고
CoreServiceProvider::isCoreUpdateInProgress 는 위임으로 남는다. bootstrap/app.php 의
복제본은 부팅 전이라 불가피한 예외이며, 두 조건의 동형성을 테스트가 단언한다.

실측 중 드러난 결함 2건을 함께 고쳤다.

 - ConfigCacheHelper::withPreservedContainer 가 파사드 애플리케이션을 되돌리지 않아
 Step 11 이 `Target class [command.tinker] does not exist` 로 실패·롤백했다.
 - updateVersionInEnv 가 프로세스 환경을 갱신하지 않아 config 캐시에 이전 버전이 구워졌다
 (Laravel env 저장소가 불변이라 재부팅으로도 덮이지 않는다).

인스톨러는 재사용 vendor 의 개발용 패키지를 installed.json 으로 감지해 설치 환경 확인
카드·설치 로그로 알리되 설치를 차단하지 않고( 결정 D1), 재사용 경로에서도 컴파일 캐시를
정리한다. 실행되는 명령만이 아니라 실패 시 안내하는 수동 명령까지 --no-dev 로 맞췄다.
코어 업데이트 완료·핸드오프·단독 재개 사후 단계에서 queue:restart 신호를 보낸다(D3).

코어 7.0.10 → 7.0.11.
2026-09-08 09:46:57 +09:00

61 lines
2.4 KiB
PHP

<?php
namespace App\Support;
use Illuminate\Support\Facades\Artisan;
/**
* 패키지 매니페스트 캐시(bootstrap/cache/packages.php · services.php) 정리·재생성 헬퍼.
*
* Laravel 의 `PackageManifest::getManifest()` 는 `packages.php` 가 존재하면 stale 여부를
* 검사하지 않고 그대로 읽고, `ProviderRepository::load()` 가 거기 등재된 eager provider 를
* `new` 한다. 그래서 vendor 를 교체한 뒤 그 파일을 남겨 두면 다음에 부팅하는 프로세스가
* 새 vendor 에 없는 클래스를 찾다 **부팅 단계에서** 죽는다 — 앱 로그가 아직 열리기 전이라
* 예외도 남지 않고, 부모 프로세스에는 자식의 비정상 종료로만 보인다.
*
* `ConfigCacheHelper`/`RouteCacheHelper` 와 같은 자리의 헬퍼로, vendor 를 바꾼 뒤 새 PHP
* 프로세스를 띄우는 지점이 이 헬퍼를 경유한다. 삭제 로직을 호출부마다 `@unlink` 로 복제하면
* 한 곳만 빠져도 그 경로가 조용히 옛 매니페스트로 부팅한다.
*/
class PackageManifestCacheHelper
{
/**
* 패키지 매니페스트 캐시 두 파일을 삭제합니다 (재생성 없음).
*
* 경로는 `Application` 의 게터로 읽어 `APP_PACKAGES_CACHE`/`APP_SERVICES_CACHE` 환경변수
* 재지정을 존중한다(테스트 격리가 이 경로 재지정에 의존한다).
*
* 삭제 실패(권한 · Windows 파일 핸들 점유)는 치명적이지 않다 — 종전 상태로 남을 뿐이며
* 코어 업데이트의 마지막 단계(`clearAllCaches()`)가 다시 시도한다.
*
* @return void
*/
public static function clear(): void
{
$app = app();
foreach ([$app->getCachedServicesPath(), $app->getCachedPackagesPath()] as $path) {
if (is_file($path)) {
@unlink($path);
}
}
clearstatcache();
}
/**
* 패키지 매니페스트 캐시를 비우고 현재 vendor 기준으로 다시 만듭니다.
*
* `package:discover` 는 새 Application 을 부팅하지 않으므로(현재 앱의 `PackageManifest` 를
* 그대로 쓴다) `ConfigCacheHelper::withPreservedContainer()` 로 감쌀 필요가 없다.
*
* @return void
*/
public static function rebuild(): void
{
self::clear();
Artisan::call('package:discover');
}
}